← Back to docs

Tools API - Changelog

Language: EN | EN | SV

Tools API - Changelog

This changelog highlights user-facing improvements across the Tools platform.


2026-05-24

  • Public /feed/category-content rendering no longer crashes when cached article/category text contains local development URLs such as http://127.0.0.1/... or http://localhost/.... Host-reference cleanup in the public URL normalizer now uses a corrected pattern again.
  • Public .net/alias hosts that currently run on the file cache are now stricter about using one runtime-safe file-cache directory instead of reusing stale storage/framework/cache/data trees with bad ownership, which avoids the repeated Unable to create lockable file failures that could break endpoints such as public RSS JSON reads.
  • The Tools-side Facebook Admin Tools moderation charts now show a small Last update hint based on the latest available ingest time for the currently selected group/source scope, so operators can tell more quickly whether the dashboard is still receiving fresh Facebook admin activity data.
  • The Tools-side Facebook Admin Tools dashboard now uses additional Facebook moderation-data hot-path indexes for date, group/source, outcome, and normalized actor/target-name lookups. That makes the heavier statistics views and participant-history lookups noticeably less likely to stall on larger moderation datasets.
  • The public RSS/feed views now also have one more database hot-path index for “latest row per link inside one feed/category” lookups. That reduces the risk of the public /feed, /feed/c/*, and /feed/cards/* pages stalling when one category/feed has grown a very large article history.
  • The SocialGPT browser extension no longer shows the Facebook admin activity or Facebook admin debug master switches in the popup/config page. Those switches now live only on the Tools-side Facebook Admin Tools dashboard, and the extension fetches the current on/off state from Tools before it starts working on those Facebook pages.
  • The SocialGPT browser extension now suppresses its selected-text Verify fact / Open Toolbox shortcut buttons inside editable iframe-based rich-text bodies such as forum/CKEditor compose fields. That keeps those floating labels from being mistaken for editor content or leaking into the text that gets submitted.
  • The Browserbot/SocialGPT setup now has one canonical host-side installer again: bin/install-browserbot-stack.sh. It now focuses on the runtime that the current Socdemo flow actually uses today — Node from NodeSource, filtered apt-based browser dependencies, Xvfb, Playwright, and one project-local Playwright Chromium cache under storage/app/browser-automation/playwright-browsers — instead of relying on a split chain of older helper installers or the current Unix user's ~/.cache/ms-playwright folder.
  • The Socdemo playback path itself is now a separate direct runner again: play/socdemo-play.sh no longer uses browserbot.sh for the actual playback step. It now starts the recorded Playwright script directly, blocks pure headless mode for this extension-backed flow, refuses to create a fresh empty login profile silently, can restore the runtime profile from a matching profile seed when available, and can self-heal the local Playwright Chromium cache when that browser download is missing.
  • Ubuntu's snap-style /usr/bin/chromium-browser launcher is now treated as broken for this flow instead of as a valid browser. The current Socdemo runner explicitly rejects Snap stubs and prefers a verified real Chromium binary, including the project-local Playwright Chromium cache, while the prepare/doctor helpers can still fall back to a verified Google Chrome binary for manual profile setup when Chromium is unavailable on that host.
  • SocialGPT's ordinary Toolbox generation no longer looks idle while waiting for Tools. The inline busy state for Generate, Refresh, and Verify fact now cycles through visible step labels, elapsed time, and a small moving progress bar in both the on-page Toolbox and the browser companion.
  • Browserbot and the SocialGPT socdemo helpers no longer tell operators to run a raw apt-get update && apt-get install -y xvfb when xvfb-run is missing. They now point to a dedicated bin/install-xvfb-safe.sh helper that uses only the normal distro package sources, so one broken unrelated PPA does not block Xvfb installation on remote hosts.
  • The Laravel Playwright installer now avoids Playwright's raw --with-deps apt flow on Linux hosts. It installs the common browser dependency packages through Tools' filtered apt wrapper first, then downloads the Playwright browser normally, which prevents one broken unrelated PPA from aborting the full browser setup. That dependency pass now also resolves newer Ubuntu t64/virtual package names automatically (for example libasound2libasound2t64 when needed) instead of stopping on no installation candidate, and browser downloads now default to one project-local cache under storage/app/browser-automation/playwright-browsers instead of the current user's ~/.cache/ms-playwright. The browserbot/install repair messages now also use explicit project-root script paths instead of assuming the current shell is already in the Tools root.
  • Browserbot and the Laravel Playwright installer now also verify that the project-local Playwright browser binary is really present and executable before one SocialGPT/browserbot run starts. If that cache is missing or has lost its execute bit, Tools now repairs permissions and can reinstall the browser into the project-local cache instead of failing immediately with a raw Playwright Executable doesn't exist or spawn ... EACCES error.
  • More host-aware runtime checks now treat the public .net aliases as the same environment family as tools.tornevall.net, not just for cache-driver selection but also for public URL fallback logic and the DISABLE_SSL_VERIFY_TEST host detection used by the shared HTTP client diagnostics.

2026-05-23

  • The SocialGPT browser extension now keeps Verify fact separate from ordinary Toolbox panel mode. Users can choose whether fact-check results should stay in the normal on-page result box or open in the browser companion, and verify-result cards now show clearer web-search metadata such as whether web search was actually used, whether independent verification is still missing, how many source links were attached, and whether Tools returned a verification notice.
  • The SocialGPT browser extension popup and config page now expose the local Toolbox Panel mode selector directly, so users can choose docked or companion/sidebar layouts before opening Toolbox in the active tab. Companion mode now means the real browser companion side panel when that API is available, so popup/context-menu/selection launches no longer have to cover the page with a larger overlay. The selected-text Open Toolbox / Verify fact overlay buttons are also draggable again instead of feeling stuck in place.
  • The DNS editor can now recognize likely Cloudflare-backed zones from their authoritative nameservers even when no explicit provider-mapping row was saved yet. That means zones whose NS set points entirely at *.ns.cloudflare.com can switch straight to the Cloudflare-backed read/write path instead of first failing with a raw AXFR-style transfer error.
  • The public /feed overview can now show significantly more feeds per page before pagination kicks in, which makes the new lazy-loaded category-card layout more practical when many feeds would otherwise spill onto page 2 immediately.
  • Lazy-loaded category cards on /feed now include their cached category AI summaries again, and those summaries are opened directly in the loaded category content instead of disappearing behind the feed list.
  • The feed-question panel on /feed now makes follow-up questions clearer: choosing one recent answer as follow-up context now opens the question panel, focuses the input field, and shows an explicit notice explaining that the next question should be written in the field below. The recent-questions block also now has a refresh button.
  • The public /feed category cards now expose their lazy-load helper safely to the global page script again, so opening one collapsed category no longer fails with a browser-side ensureCategoryContentLoaded is not defined error.
  • Memcached fallback is now only used when the real PHP memcached extension is actually loaded. Plain PHP aliases/stubs no longer count as a valid Memcached client, which prevents false-positive fallback attempts that could otherwise crash during cache bootstrap.
  • The maintenance helper that repairs Laravel runtime permissions now also checks the actual file-cache and session directories under storage/framework/*, so file-cache lock errors are easier to catch immediately after maintenance instead of later during page requests.
  • Host-aware cache selection can now fail over more gracefully when one cache backend is unavailable: if one host prefers Redis but Redis is not usable there, Tools now falls back to Memcached first when the Memcached extension/client is available and configured, and only then falls back to the file cache store.
  • Cache-store selection can now follow the current public host more explicitly: tools.tornevall.com keeps the ordinary default cache driver (currently file-based in local/dev setups), while tools.tornevall.net can switch to a separate CACHE_DRIVER_NET value such as Redis when that host/runtime supports it.
  • Public documentation pages (/docs, /docs/en, /docs/sv, and individual docs pages) plus RSS outbound redirects (/out/{contentId}) now skip the nonessential database-backed web extras such as footer online-session counters, per-request job triggering, and visit tracking. That means those lightweight public pages can still open even when the ordinary site database is under temporary pressure.
  • Session configuration now respects the SESSION_DRIVER and SESSION_CONNECTION environment settings again instead of forcing database sessions everywhere, so local/dev environments can switch to file sessions without code edits.
  • The Swedish and English documentation landing aliases (/docs/sv and /docs/en) now open their real language indexes again instead of sometimes falling into a 404 by being mistaken for an ordinary documentation page slug.
  • Public pages such as /feed and /out/{contentId} no longer fall into a 500-style bootstrap crash on Windows/WAMP hosts just because Laravel's cache-manifest path was interpreted incorrectly. Tools now keeps the normal local bootstrap/cache path untouched when it is already writable, and the public error page can also fall back to a minimal built-in HTML response if the view layer never finished bootstrapping.
  • Local Windows/WAMP or CLI environments that use the default phpredis client but do not actually have the Redis PHP extension loaded now fall back away from Redis automatically, so pages like /feed can still use the normal file/array cache path instead of crashing with Class "Redis" not found.

2026-05-22

  • The public /feed overview now keeps category cards lightweight on first load. Feed rows, category AI summaries, and per-site AI summaries are fetched only when one category is actually opened, which reduces the initial page weight and makes the collapsed overview noticeably faster.
  • The firewall shortcut on the signed-in dashboard and on the administrative part of /services now opens the live-control page directly, so the gateway runtime routing overview and generated run-after config are easier to find.
  • VirtualBox Manager now shows one host-memory summary on each configured server card, including total host memory, currently used host memory, and currently available host memory when the VirtualBox WebService exposes those values.
  • VirtualBox Manager now keeps one stricter raw-WSDL SOAP session flow from login through the first inventory calls, so Tools no longer treats an empty or unusable managed-object reference as a successful login.
  • The /admin/virtualbox overview now consolidates each host into one inventory inspection instead of opening several noisy follow-up sessions per page load, which makes SOAP failures easier to read and reduces duplicate error spam.
  • The VirtualBox admin cards now show a dedicated diagnostics block with endpoint/WSDL/login-operation/reference visibility, while still redacting passwords and other sensitive SOAP data.
  • Empty inventories are now treated as normal states in VirtualBox Manager: zero VMs, zero registered ISOs, or zero bridge adapters no longer show up as automatic hard errors by themselves.

2026-05-21

  • The browserbot install helpers (bin/fixnode.sh, bin/install-playwright-laravel.sh, and bin/install-chrome-headless.sh) are now more resilient on Ubuntu/Debian hosts with broken third-party APT repositories. They now isolate their package-manager work to the needed distro/vendor sources instead of failing just because one unrelated PPA is unavailable, and the Chrome installer now auto-detects the current Laravel app root instead of assuming one older default path.
  • Firewall Live Control now also exposes internal runtime route-config endpoints for hosts such as the gateway (/api/firewall/runtime/{host} plus /api/firewall/runtime/{host}/run-after.conf). The /firewall/live admin page can now show the grouped generated run-after route config together with synced netplan / legacy network-interface snapshots when the shared serverinformation path is mounted on the Tools host.
  • The DNS editor can now keep selected zones visible even when those zones should be managed through Cloudflare instead of the local BIND files. External zone mappings can be registered in Tools admin, the zone list now shows those mapped zones, and editor reads/writes for those zones are routed through the Cloudflare API while the existing refresh flow stays in place.
  • The shared footer that already shows live online-user counts can now also show a small server load-average indicator, so it is easier to spot host pressure without opening the full admin runtime pages.
  • The public /feed overview now does less repeated counting work per page load. Feed entry totals, first/last discovery timestamps, and the edited-post counters are now aggregated more efficiently and cached for short periods, which reduces the slowdown that could happen when the overview page had to recalculate those counts for every visible feed row.
  • Browserbot SocialGPT playback can now survive Snap-managed Chromium on Linux hosts where the saved Tools browser profile lives outside Snap's writable sandbox. The wrapper now mirrors that profile into a Snap-safe runtime path for the run and syncs the updated session data back afterwards, which avoids the common Chromium cannot read and write to its data directory failure.

2026-05-20

  • The main exception handler no longer risks crashing a second time just because Tools bug-report forwarding or the framework logger itself fails during exception reporting. Failed logging now falls back safely instead of turning one real error into a fatal Class "Log" not found cascade.
  • Immutable-host fallbacks now cover more than Blade compiled views: when the normal Laravel cache manifest or log file paths are not writable, Tools can now redirect those runtime files to a writable temporary runtime area instead of crashing on a read-only bootstrap/cache or storage/logs path.
  • The maintenance helpers that clean/repair Laravel runtime state now also pre-create the resolved fallback log/cache/view directories and verify web-user write access, so one old read-only runtime path is less likely to survive a normal cleanup pass unnoticed.
  • Public RSS outbound redirects (/out/{contentId}) no longer risk turning one optional RSS entry clicked analytics log write into an internal error when the ordinary Laravel log channel is unavailable on a read-only host.
  • The lightweight /me.php callback no longer tries to write its temporary IP marker directly into the public web root, so that endpoint now keeps working even when the deployed public/ mount itself is read-only.
  • VirtualBox Manager's SOAP login is now more WSDL-aware for wrapped/document-literal operations. It can inspect __getFunctions() plus __getTypes(), try wrapped login/logout request objects before raw positional arguments, and expose an admin-only Debug SOAP action with sanitized WSDL/function/type/request diagnostics when one host still refuses login.
  • Tools now falls back to a writable temporary compiled-view directory automatically when the normal Blade cache path under storage/framework/views is read-only on the host, which prevents those requests from crashing just because the deploy mount itself is immutable.
  • The admin-only User mailout tool is now easier to find: the old Mail all users entry has clearer wording, and admins now also get direct shortcuts from the main dashboard and the administrative part of the public Services page.
  • The User mailout page itself now has a more polished send-workspace layout, with clearer composer hierarchy, side-panel workflow guidance, visible recipient counters, and a larger dedicated preview/review area.
  • The AI helper draft action on User mailout now runs asynchronously, so the draft, helper status, and preview panel can refresh in place without reloading the whole page.
  • Real User mailout broadcasts and Send test to me only now queue onto the dedicated background user-mailout queue, and the browser-side send/test actions can now return immediately instead of waiting for the whole mail run inline.
  • User mailout now includes an AI helper draft action that can generate a first email draft from an instruction plus the selected recipient scope, while keeping the normal preview / dry run / test-send safety flow.
  • User mailouts and test sends now go out from the configured support sender address (SUPPORT_EMAIL, support@tornevall.net by default) instead of relying only on the generic framework sender.
  • Opening /openai/access/request directly now sends signed-in users back to the built-in request card instead of falling through to a generic error-looking page, and failed request submissions now return a user-facing error message instead of an unhelpful 500-style dead end.
  • Admins/reviewers now get a small fixed reminder overlay on normal signed-in pages when one or more OpenAI access requests are still pending, with a direct link back to /admin/openai.
  • My API Keys can now create multiple separate Tools OpenAI forwarder tokens (provider_tools_openai) for different internal AI jobs. Each token can be named per assignment and rotated or killed individually, while the real upstream OpenAI key remains server-side inside Tools.
  • The new Tools OpenAI forwarder tokens are gated by the existing admin-managed OpenAI access right (provider_openai), so approved users can split internal Tools AI usage across multiple task-specific bearer tokens without reusing one shared token everywhere.

2026-05-19

  • The public vBulletin invite-complete endpoint (POST /api/vbulletin/onboarding/invite/complete) is now explicitly documented as a token-free frontend hook endpoint, and it now accepts either a raw invite code or a full onboarding URL in invite_code by stripping the URL down to the real token before lookup.
  • Public docs now also include a dedicated vBulletin frontend hook guide for skipping the separate onboarding page after forum registration, including the recommended header_head hook arguments, adapted field66 bootstrap code, a direct fetch() example, and a styled success-popup example.
  • That frontend guide now also includes the full finished Snowball autoverify example with session caching, silent invite code not found stop behavior, FormData POST transport to Tools, and popup feedback for both successful and failed verification.
  • SocialGPT Toolbox no longer stays visually trapped inside some rich-text editor boxes. When the editor lives inside an editable same-origin iframe, the extension now tries to place Toolbox on the surrounding page surface instead of leaving it embedded inside the editor area.
  • SocialGPT Toolbox now also has two taller companion-style dock modes: Companion right and Companion left.

2026-05-18

  • Public documentation now includes a dedicated technical note for the Snöbollseffekten vBulletin integration, covering the /snowball -> /snowball/forum redirect, the header_head-driven logo swap, early logo-hide CSS, Tools bundle preconnect/preload hints, and the current member-widget setup.
  • Tools now exposes POST /api/vbulletin/onboarding/invite/complete for a future vBulletin frontend completion script. The endpoint can reuse or create the matching onboarding request from an invite code plus forum identity hints, validates any configured invite-code profile-field match before it trusts the resolved forum account, and only auto-activates forum access when the existing onboarding config already allows safe automatic approval.
  • vBulletin onboarding invite keys can now be marked as Test code / dry-run only. Those keys still let operators and invitees walk through the full onboarding flow, but approval no longer grants any real forum access or live vBulletin group membership when the key is in dry-run mode.
  • The vBulletin invite-outreach composer and template renderer now also support {{tools_support_email}}, so invite emails can point to the shared support address (support@tornevall.net by default) without replacing the existing {{tools_user_email}} placeholder for the current Tools user.
  • The public vBulletin onboarding pages now use simpler wording for invitees, prefer “invite code” consistently, and explicitly show which profile field older users should paste the code into before they continue.
  • The Group / source filter in Facebook Admin Tools now explains cross-owner scope more clearly: acknowledged admins can see owner names there when the dashboard spans multiple owners, while ordinary non-admin users still only see their own tracked groups.
  • Mail Support Assistant's standalone web dashboard now renders a lightweight shell first and then loads the heavier Tools config + live unread IMAP preview immediately after page paint, so logging in no longer feels blocked by the full dashboard build.
  • Multipart inbound mail parsing in the standalone Mail Support Assistant now prefers the richest readable MIME body part instead of blindly keeping the first non-empty part, which fixes cases where some messages previously appeared to lose most of their visible body content.
  • Recent synced-thread cards on /admin/mail-support-assistant now disappear immediately after delete instead of waiting for a full page reload, and the threaded Tools case page is more defensive about older over-trimmed reply-aware bodies by falling back to the fuller stored plain-text body when needed.

2026-05-17

  • SocialGPT Toolbox and the other floating SocialGPT controls no longer become editable page content when they are opened from rich-text editors or other editable iframe/document surfaces. The extension now mounts that floating UI inside a dedicated non-editable overlay root instead.
  • Mail Support Assistant now lets operators ignore recurring senders per mailbox directly from the threaded case view, the recent-case cards, or the mailbox section in /admin/mail-support-assistant. Operators can also choose to delete already stored synced threads for that sender at the same time, and standalone clients now receive the same mailbox-level ignore list through GET /api/mail-support-assistant/config so future mail from those sender emails is skipped immediately.

2026-05-16

  • The admin user editor now keeps a visible password-reset activity history. Operators can see when a reset link was sent, whether it came from the admin user editor or the normal Forgot password page, and when the user actually completed the reset. That makes manually triggered reset mails much easier to follow up.
  • The vBulletin invite-outreach composer now opens the normal Facebook Messages page (facebook.com/messages) instead of the separate messenger.com site when operators use the Messenger shortcut button.
  • The vBulletin invite-key pages now support a This is a trusted user toggle for email-based invites. When such an invite is later approved, Tools can unlock /vbulletin/onboarding/keys for that same recipient email and send one follow-up mail with the delegated key page plus the currently visible invite list for that slug. The invite-outreach composer also waits until you leave the current template/sender field before it saves, so typing in longer message templates no longer jumps the cursor out of the textarea mid-edit.
  • The dedicated vBulletin key-generator page now includes a Google Docs-friendly export block for active invite keys. Operators can copy a ready-made section with a heading, two short registration instructions, and a real table containing Kod, Länk, and Person?, either as rich HTML for Google Docs paste or as plain-text fallback.
  • Whisper uploads now report much clearer file-upload failures. When a media upload is too large for the current host, only partially uploaded, or blocked by PHP/temp-storage limits, the Whisper UI and APIs now return a direct media_file validation error instead of leaving users with the generic “failed to upload” wording. Whisper status payloads also expose the effective upload limit after PHP/body limits have been taken into account.

2026-05-14

  • SocialGPT's Facebook participant-request helper now keeps auto-scanning matched preview/original-post dialog content even when Facebook mounts those dialogs outside body in separate mount_* roots. That means later preview/original-post text is much less likely to stop updating until the operator presses Find preview element manually.
  • Added a new admin-only Support & AI Budget control room where Tools operators can link one user to a Ko-fi-style membership tier, store Ko-fi name/email/reference metadata, register membership/tip/manual ledger rows, and track internal Tools AI reserve in both euro amounts and token counts.
  • The public Donate and Contact pages now both link directly to the Ko-fi crowdfunding page in addition to the existing PayPal route, and the donate flow now has its own /donate landing page instead of sending the navbar straight to PayPal only.
  • Social Media Tools and My Profile now show a compact membership / Tools AI reserve summary when one account has been linked to internal support-accounting data, so SocialGPT-facing users can see their current tier, internal AI Budget, reserve left, and tracked token balance in one place.
  • The public Support Tools documentation now includes a dedicated AI Budget section/table for all six membership tiers, plus explicit wording that all AI-assisted usage runs server-side through Tornevall Networks Tools and that Ko-fi tips can be used as internal Tools AI reserve top-ups rather than as direct OpenAI access.
  • Browserbot/SocialGPT playback and other Playwright-backed browserbot runs now handle one common Linux server failure mode more clearly: when chromium-browser only exists as a Snap wrapper and the service account's configured home directory does not actually exist, browserbot now skips that broken Chromium path when possible, falls back to real Chrome if it is installed, and otherwise explains that the real fix is to create the account's home directory or run under a user with a valid home.
  • SocialGPT's Facebook participant-request Analyze user box now closes itself when you leave /groups/*/participant_requests, and stale participant-analysis follow-up state is cleared so an older analysis does not linger above unrelated Facebook pages.
  • SocialGPT's Facebook participant-request helper now also captures structured original-post thread comments from later preview/image GraphQL responses (for example CometPhotoRootContentQuery), and the floating detected-context/debug views now show explicit comment-thread counts plus captured comment excerpts so it is clearer that deeper thread context really made it into Analyze user.
  • SocialGPT's Facebook participant-request helper no longer crashes on the missing getParticipantHistoryForSummary / participant-history helper chain when it tries to render earlier moderation-history badges or inject that same history into Analyze user context.
  • SocialGPT's Facebook participant-request helper now also uses the in-card Facebook profile URLs (/groups/<id>/user/...) as a fallback card-identity signal and climbs farther up through Facebook's deeper moderation markup, so visible request cards are less likely to disappear when Meta changes the surrounding wrappers again.
  • SocialGPT's Facebook participant-request helper now keeps rechecking clicked preview/original-post dialogs for a few extra seconds, so slower Facebook loads are less likely to miss newly opened View original post content after the first quick preview match.
  • SocialGPT no longer lets the browser extension choose its own Verify fact model. Tools now decides the verification model server-side, and when web search is part of the verification flow it may prefer gpt-4o as the primary model.
  • The Facebook admin moderation dashboard is now lighter both on filter refreshes and on the first chart render: hidden charts no longer force a full chart-data rebuild on every filter submit, and the repeat-rejections chart no longer spends extra PHP time resorting large Eloquent result sets in memory before it can draw.
  • RSS subscription notification passes and X-bot auto-post scans now use more index-friendly hot paths for their version-count / queued-reply checks, which reduces some of the slow-query pressure seen during feed update bursts and X-bot reply cycles.
  • Browserbot's interactive --open mode now forces a sane visible browser window by default (1600x1000 at 40,40) so older persistent profiles that remembered a tiny or off-screen Chrome window no longer reopen as a nearly invisible blank box. The geometry can still be tuned or disabled through BROWSERBOT_WINDOW_SIZE, BROWSERBOT_WINDOW_POSITION, and BROWSERBOT_DISABLE_DEFAULT_WINDOW_BOUNDS.
  • SocialGPT's Facebook participant-request preview helper now keeps following and rescanning the same matched preview dialog after the first hit, so later preview changes — including when an operator clicks through to View original post inside that preview — can still extend the user-analysis context.
  • Preview-dialog context selection is now more participant-name-aware too, which makes scattered comment/post lines tied to the analyzed person more likely to stay in the analysis context while generic preview UI noise is filtered harder.
  • Facebook participant-request Analyze user now races one no-web-search lookup against one web-search lookup both on the first visible-card pass and on later preview/original-post follow-up passes, so the fastest useful answer can be shown first while richer later results can still replace it when they add more verified value.
  • The helper's Find preview element action now also forces another preview/comment rescan after focusing the matched dialog, which helps the extension recover when Facebook has already loaded more of the thread but the normal DOM-change watcher did not notice it quickly enough.
  • The first visible-card-only Facebook participant analysis now stays on a faster no-web-search path, while later preview/original-post follow-up reruns can still use web search again when the richer comment context contains claims worth checking. Those follow-up reruns also put extra analytical weight on the participant's own visible comment versus the saved group rules/context, and the browser notice is less likely to sit forever on one static “wait a few more seconds” message when Facebook has already stopped changing.
  • SocialGPT's Facebook participant-request helper now also checks Tools for earlier approved/rejected moderation history when a visible participant name/profile matches rows already logged through the separate Facebook admin-activity ingest. Matching request cards can show compact earlier-decision badges, and Analyze user now receives the same summary as extra moderation context.
  • The participant-request helper no longer crashes on a missing collectParticipantContextNeighborhoodLines function while preview/comment follow-up context is being assembled.
  • Facebook participant-request card detection is now more tolerant again: the helper climbs farther up from approve buttons, ignores preview-dialog surfaces during card matching, and falls back to a broader whole-page action scan when Facebook's participant-request root becomes too strict, so visible request cards are less likely to stay stuck at Cards enhanced: 0.
  • The participant helper also exposes direct Open Toolbox and Verify fact actions again on detected request cards and from the floating fallback list, instead of leaving only the newer Analyze user path available there.
  • Browserbot/SocialGPT playback now uses the same common Chromium-family startup flags as open mode: --no-first-run plus --no-default-browser-check always, and --no-sandbox plus --disable-setuid-sandbox automatically when the launch runs as root. The same shared launch builder now also keeps explicit unpacked SocialGPT extension loading (--disable-extensions-except=... / --load-extension=...) aligned across browserbot open mode, SocialGPT playback, and the persistent Playwright record/run helpers.
  • VirtualBox Manager now retries a VirtualBox endpoint through a non-WSDL SOAP client automatically when the generated ?wsdl is reachable but PHP SOAP fails with a Parsing WSDL / Couldn't bind to service style bind error.
  • VirtualBox Manager now prefers the explicit interface-qualified SOAP operation names from the VirtualBox API before falling back to shorter aliases such as plain logon, which fixes connection attempts against hosts that answered with Method 'ns1:logon' not implemented even though the WebService itself was reachable.
  • Public documentation now includes a dedicated Support Tools page linked from the docs index and the public services overview, explaining what already exists in the platform, why operating cost is growing, and how the Ko-fi support levels are intended to help.
  • RSS feed endpoints no longer crash just because one feed row has an empty stored title. When Tools cannot derive a usable non-empty feed title from the source row, category metadata, or selector, it now falls back safely to a generic feed label instead of failing inside the Atom writer.
  • Browserbot headed open/record/SocialGPT playback runs now auto-wrap themselves in xvfb-run when no DISPLAY/WAYLAND_DISPLAY is available on the host, so server-side socdemo-play and prepare-socdemo-profile flows no longer fall straight into Chrome/Playwright's raw “Missing X server or $DISPLAY” crash when xvfb is installed.

2026-05-13

  • SocialGPT's Facebook participant-request Analyze current preview flow now treats the opened preview dialog as the primary text source when that dialog is matched, so full card-only answer/comment text is no longer pulled into preview updates unless it is also visible in the preview context itself.
  • Preview-driven SocialGPT participant user-analysis boxes now anchor themselves to the actual Facebook preview dialog instead of drifting toward a broader surrounding DOM block, which keeps the floating analysis box beside the preview much more reliably.
  • SocialGPT's Facebook participant-request helper now initializes its floating panel/rules box more defensively, so odd DOM races should no longer crash the extension with Cannot set properties of undefined (setting 'position') while the helper starts.
  • SocialGPT's Facebook participant-request Rules / group info is now scoped per /groups/<id> instead of reusing one shared rule text for every group. The per-group rules map is now also saved through Tools extension settings so it can sync between installs for the same user/token, and on the actual participant-request page only the exact current group's saved rules are shown/used there.
  • Browser Automation/SocialGPT for fb-socdemo2-stats now explains much more clearly what a fresh host actually needs: the profile seed under storage/app/browser-automation/profile-seeds/... only carries cookies/session/local state, while the SocialGPT extension itself is still loaded as unpacked code from projects/socialgpt-chrome. The helper scripts now point more directly at submodule bootstrap, and play/socdemo-doctor.sh shows whether the missing piece is the submodule, the profile seed, or the runtime profile.
  • Browser Automation's plain browserbot.sh --open flow is now more forgiving on Linux hosts where chromium is missing but Chrome is installed: a request for chromium can now fall back to the installed Chrome binary instead of stopping immediately, and the startup output now shows the requested browser plus the effective browser/executable. The SocialGPT socdemo helper text was also corrected so it no longer assumes every checkout git-ignores full runtime profiles; profile seeds remain the recommended cache-stripped format for moving sessions between hosts.
  • The SocialGPT socdemo helpers now resolve that same browser fallback before they even call browserbot, so play/prepare-socdemo-profile.sh and play/socdemo-play.sh no longer die immediately just because chromium is unavailable on a host that already has Chrome installed.
  • The live playback entrypoint play/socdemo-play.sh is now self-contained for the operational path instead of depending on the extra shared socdemo helper file just to restore the seed, find the SocialGPT extension, and resolve the browser fallback before browserbot starts.
  • Browser Automation open sessions now also handle the common Linux/root case more cleanly: browserbot.sh --open automatically adds the Chromium-family no-sandbox flags when it is launched as root, and play/prepare-socdemo-profile.sh no longer aborts the whole clone/export workflow just because one browser-open step failed.
  • The Laravel/browserbot installer can now also provision a real system Chromium binary when requested: INSTALL_CHROMIUM=true bash bin/install-playwright-laravel.sh (or the same through bin/install-browserbot-stack.sh) now tries the host's chromium-browser / chromium apt package names instead of leaving every host to install Chromium manually.
  • Browserbot's interactive unpacked-extension open sessions now also add Chrome's unpacked-extension debugging flags, and the runtime log explains that a Chrome Web Store banner such as Installation is not enabled is expected in this sideload mode rather than being proof that the already loaded unpacked extension failed.
  • The SocialGPT profile-preparation flow now stops pretending that a temporary --load-extension sideload is the same thing as a real profile installation. play/prepare-socdemo-profile.sh now opens the source profile on chrome://extensions/ for a real Load unpacked install, and the later target-profile verification opens without --with-socialgpt so the copied profile is tested in the same persistent-extension state that playback will actually use.
  • SocialGPT/OpenAI timeout handling is now clearer for client apps: POST /api/ai/socialgpt/respond can now return a friendlier user_message plus additive retryable, error_code, and upstream metadata when Tools hits a temporary OpenAI timeout/connection problem, instead of only exposing a raw transport error. Verify/source-check flows can also keep a preliminary answer visible and attach a notice when only the last OpenAI refinement step times out.
  • Browser Automation can now export a cache-stripped profile seed for Git-friendly reuse: browserbot.sh --export-profile-seed writes the reusable part of one prepared browser profile under storage/app/browser-automation/profile-seeds/<name> while skipping browser caches and runtime lock files, and browserbot.sh --copy-profile-from-seed can restore a live runtime profile from that exported seed on a fresh host. The SocialGPT helper scripts now use the same flow automatically.
  • The browser automation installer chain is now more robust on remote Linux hosts: bin/fixnode.sh reinstalls the NodeSource apt repository with an explicit keyring and signed-by= source entry instead of relying on the older setup-script pipe, which fixes the common NO_PUBKEY 2F59B5F99B1BE0B4 failure, and the Chrome helper now ships with normalized shell line endings so bin/install-chrome-headless.sh no longer trips over a parser error before the browser install even starts.
  • VirtualBox Manager now talks to VirtualBox over the reverse-proxied vboxwebsrv SOAP endpoint instead of SSH/VBoxManage. Server cards now default to port 8443, the login fields are labeled as API credentials, and the admin help text/docs explain that the configured host/port should point to the WebService proxy endpoint.
  • RSS Watch feed-question answers now keep their concrete reference hits together with the saved answer, and follow-up questions can reuse those stored references instead of only reusing the earlier question text and retrieval terms. The latest answer boxes on /feed and on site-specific feed pages now also show when web search was actually used and list any returned citation links.
  • RSS Watch's vBulletin-style RSS2 import is now more robust: item-level content:encoded, dc:creator, guid, pubDate, and category data are handled more carefully, entity-escaped HTML bodies are normalized without aggressive stripping, and duplicate checks can now prefer GUID identity before falling back to legacy link-based matching.
  • RSS Watch's feed-question progress UI is now lighter on the server during long-running answers: browser polling no longer stacks overlapping /status requests, stuck polls are aborted and retried with backoff, background-tab throttling is handled more cleanly, and the browser now accepts a completed background process response immediately when it already contains the final answer.
  • Repeated browserbot/Playwright dependency failures are now condensed after the first full warning block, so cron logs no longer fill up with the exact same Node/npm/Playwright repair message on every failed run.
  • SocialGPT's Facebook participant-request Analyze user flow now keeps listening to the preview-dialog GraphQL response even when the separate admin-activity collector is off, and the helper now extracts a compact participant-specific preview/comment/post summary from that response instead of dragging large raw payload dumps into the analysis context.
  • SocialGPT's Facebook participant-request helper now keeps tracking an open Preview comment dialog as the active participant surface even if Facebook rerenders the underlying request-card list, so the helper no longer drops back to a false “no visible cards matched” state while the matching GraphQL preview context is still visible.
  • SocialGPT's Facebook participant helper now also shows a small Detected context list in the floating panel, including the current participant name/profile info plus visible group/friend/profile clues, preview-comment hints, and original-post links when available, and the helper can now run Analyze current preview from that fallback context even when no visible request cards are currently matched.
  • SocialGPT's Facebook participant helper now also finds root-mounted preview dialogs more reliably by reading dialog label metadata inside Facebook's separate mount_* roots, and the floating helper exposes a new Find preview element action that scrolls to and highlights the currently matched preview dialog.
  • SocialGPT's verify-style Analyzing user… loading box now keeps a much more obvious live progress display with step count, elapsed time, and a moving progress bar instead of mostly sitting on the old static Checking now… spinner.
  • SocialGPT's Facebook participant-preview parser now also extracts richer GraphQL text/message fields and source metadata — including preferred_body.text, body_renderer.text, original-post message text, author names, timestamps, comment/post ids, and Facebook URLs — and merges that normalized preview context into the user-analysis request before DOM-only fallback is used.
  • browserbot.sh now prefers an installed Chrome binary for SocialGPT/extension-backed Playwright runs when a script still says chromium but Playwright's bundled Chromium is missing, and the startup log now shows both the requested browser and the effective browser/path used for that run.

2026-05-12

  • SocialGPT's floating browser UI now mounts more defensively when a page is still in an early/transient DOM state, which reduces appendChild-style extension crashes on pages such as forum threads.
  • SocialGPT fact/user-analysis result boxes now also tolerate slightly messier AI formatting better, including indented headings, numbered lists, and simple HTML line breaks mixed into markdown-style answers.
  • The SocialGPT popup's Use dev / beta server checkbox now stays off by default and no longer turns itself on accidentally because of older truthy-looking saved values.
  • Protected web pages such as admin/social-media-tools now send signed-out visitors to the normal login flow again instead of showing the generic branded 500-style public error page.
  • Public/browser 500-style error reference codes are now reused in the internal error mail and Laravel log context, so support can match a screenshot's reference value to the corresponding backend failure more directly.
  • SocialGPT's Facebook participant-request Analyze user now includes the visible request-card text from the beginning, including membership questions, visible answers/rules, group/profile clues, and preview/comment markers. The analysis box now shows more interactive progress steps and can offer Update analysis when Facebook loads extra comment/post preview or relevant Graph/XHR context after the card is clicked.
  • DNSBL removal audit mails are now limited to externally handled site/plugin integrations such as WordPress plugin requests instead of mailing every internal already-not-listed/no-op cleanup. Those mails can also include side DNS resolution details, such as reverse DNS for the submitted IP and resolved addresses for the source site host, to make site-origin review easier.
  • Public/browser error handling is now stricter: non-admin visitors no longer receive framework debug details even if debug mode is accidentally enabled, while internal server-side exception details can be mailed to the configured support address for follow-up.
  • SocialGPT now has a dedicated personal token flow (provider_socialgpt) that can be generated for the Chrome extension without granting the account direct OpenAI API access. Direct OpenAI-backed endpoints still require the separate OpenAI access request and approval flow.
  • Direct OpenAI access requests now send a support notification when users submit or update a request, so pending API-access needs are visible outside the admin page.
  • The administrator mailout tool now supports personalization variables, loads its default draft from docs/pre-email.md, and includes a Send test to me only action so admins can dry-run a real message to themselves before broadcasting to users.
  • dnsbl-engine operational reports can now be delivered immediately, hourly, or daily through SPAM_REPORTS_FREQUENCY, allowing noisy per-run reports to be grouped into digest mails.
  • Public Tools browser error pages now use a generic branded page instead of the framework debug renderer on public hosts. The page shows a friendly message, HTTP status, and a short reference code without exposing stack traces, SQL queries, local paths, or server infrastructure.
  • User management now includes an administrator-only Mail all users workflow with recipient counts, preview, dry run, and a required SEND TO USERS confirmation phrase before any real broadcast is sent.
  • SocialGPT verification and user-analysis flows now use a safer three-step pattern: a normal preliminary AI answer, a separate web-search verification lookup when the request requires it, and a final answer pass that can revise or strengthen the preliminary answer with source evidence. If web search fails or adds no useful evidence, the preliminary answer can still be returned, but the response metadata clearly marks that independent verification is missing or failed instead of pretending the answer was verified.
  • SocialGPT fact/user-analysis result boxes now render safe markdown links as clickable links and also show structured citation links returned by Tools, while blocking unsafe URL schemes and script-style markup.
  • SocialGPT fact/user-analysis result boxes now also render the first markdown answer as safe HTML (for example headings, bold text, and lists) instead of exposing raw markdown, and the initial Analyzing user… / Verifying facts… state now uses a cleaner styled preview card instead of flattening all imported context into one wall of text.
  • The Facebook participant-request helper now also exposes an inline Rules / group info box in the browser itself, so operators can update the extra group-specific moderation context used by Analyze user without leaving the current Facebook moderation page. When a Tools bearer token is configured, the same rule text syncs back to Tools.
  • SocialGPT fact/user-analysis result boxes now keep long answers readable by scrolling only the answer and citation area while leaving the header and action buttons accessible. Verification requests also use a larger response budget, so source-backed fact checks are less likely to stop mid-sentence.
  • ToolGPT/X bot documentation and support questions now also search the public Tools docs/changelog evidence more explicitly before answering, so disputed “does documentation exist / what does ToolGPT support?” prompts are less likely to fall back to incorrect generic guesses.
  • Browser automation / Playwright setup on Linux hosts now fails more clearly when the server still has an old Node runtime or a broken npm/npx toolchain, and SocialGPT browserbot playback now stops with a direct “project-local Playwright is not installed” message instead of only bubbling a raw Node Cannot find module 'playwright' stack trace.
  • The Social Media Tools start page is now available to all signed-in users, so ordinary extension users can open the install guide and generate or rotate their personal Tools AI token without needing the Social Media Tools management permission. Facebook/X operator dashboards and audit views remain permission-gated.
  • The Social Media Tools start page now includes its own SocialGPT/OpenAI access request flow. Users who are not approved yet can submit the request directly from the extension/token setup page, and token-generation/API error wording now points them back to that page.
  • User profile pages and the Social Media Tools start page now show the account's SocialGPT/OpenAI limits directly, including OpenAI access state, daily budget/remaining usage, API request limiter, default model/output budget, and web-search availability.
  • Laravel timeout errors from tools.tornevall.com are no longer forwarded into the Laravel error notification stream when they are the noisy Maximum execution time ... exceeded class of warning; other hosts and other error types remain reportable.

2026-05-11

  • SocialGPT's Facebook participant-request helper is now gated more strictly to top-level Facebook participant-request pages, so unrelated pages such as Threads activity views no longer initialize the participant scanner or emit participant-scanner ReferenceError messages.
  • SocialGPT verify/fact-check requests that require independent verification now force the OpenAI Responses API web-search tool more reliably instead of only hoping the default tool toggle will add it. Nested web-search tool calls are now detected more robustly too, which reduces false independent_verification_missing outcomes caused by adapter-side payload/detection gaps.
  • The SocialGPT Facebook participant-request helper is now lighter on heavy /groups/*/participant_requests moderation pages: visible-card scans are more conservative, card scoring is cached more aggressively, the floating scanner now shows scan timing, and the inline actions now prefer a smaller placement near the card's existing action / ... area when Facebook exposes a stable anchor there.
  • ToolGPT/X bot now has a second repetition safeguard aimed at ordinary human conversations, not only AI-to-AI loops: when the latest 5–7 visible posts clearly keep circling around the same point, Tools can now either push the next reply to move the discussion forward with a new angle or clarifying question, or pause the thread entirely when the recent exchange has become a repetitive dead-end.
  • ToolGPT/X bot now runs an internal RSS/archive lookup pass continuously when RSS evidence is enabled: OpenAI first proposes the most relevant search terms, Tools then performs a local SQL lookup in the RSS archive, and any matching archive rows stay as optional supporting context rather than automatically taking over the first answer.
  • Tools now exposes a truly generic Microsoft auth surface in addition to the existing Microsoft To Do integration: the preferred browser callback/start routes are now /oauth/microsoft/*, there is a new generic status endpoint at GET /api/microsoft/auth/status, and the shared Microsoft / Graph app now reports both personal-account and Microsoft Entra work/school account support. Older /oauth/microsoft-todo/* and /api/microsoft-todo/oauth/start paths still work as compatibility aliases.
  • The X bot now runs one final “does this still look cut off?” failsafe immediately before posting a reply to X. If the stored candidate still looks like an unfinished near-limit fragment, Tools first tries a last clean trim and then blocks posting entirely instead of publishing a half-sentence.
  • ToolGPT/X bot now keeps a slightly softer local reply target below X's hard 260-character limit, and the final trim step now prefers complete sentences instead of squeezing the candidate right up against the maximum. Visible @author tagging is still counted inside the final posted length.
  • ToolGPT now treats broader “what are the most relevant/latest news from the feed today?” wording as an RSS/feed-archive lookup too, even when the post never literally says search RSS. When that kind of generic feed-summary prompt has no real topic keywords, Tools now falls back to recent feed items instead of missing the RSS lookup entirely.
  • Public RSS overview pages now cache the expensive edited-post counters for a short period instead of recalculating the same COUNT(DISTINCT content_hash) aggregates on every nearby page load. Manual RSS purge actions now also clear those cached counters immediately after cleanup.
  • RSS duplicate cleanup is now better at collapsing noisy repeated imports from embed/player script churn. Cautious-link auto-purge and the manual Purge noisy duplicates action now recompute signatures from the real article text and ignore script/style blocks that should never have counted as real content revisions.
  • On-demand Slack mirroring for X-bot audit events is now narrower: enabling audit_x_bot no longer forwards routine poll noise by itself, and Slack mirroring is now reserved for actual posted-reply outcomes rather than every recorded X-bot event.
  • The X-bot queued-reply scan and the RSS duplicate-import hot path were tightened for heavier traffic. Tools now uses a more targeted X-bot interaction index for queued/rate-limited auto-post checks, and the RSS importer now prefers hash-based duplicate checks instead of repeatedly running the older expensive exact-text existence scan against large article bodies.
  • The public ToolGPT/X-bot guide no longer lists raw bootstrap secret/env variable names for the X integration. The same guide now also explains keyword routing as a helpfulness/priority model — for example prioritizing "answer about the X bot itself first" or "answer as fact-check first" — instead of making keyword rules sound like rigid ideological overrides.
  • X-bot activity can now be mirrored to Slack on demand through the Slack log routing category audit_x_bot, even when the ordinary manual-review Slack toggle is off. This makes the recorded moderation/runtime event stream available as an audit feed instead of only as direct review notifications.
  • ToolGPT/X bot now strips accidental raw JSON reply envelopes before a reply is stored or posted, so a public X reply no longer risks leaking a literal { "message": ... } payload when one recovery/rewrite step answered in machine-readable form instead of plain text.
  • The scheduled X-bot poller now escalates provider-side budget failures automatically: when a run stalls because the active X/provider project hit a spend-cap or credits-exhausted block, Tools now sends an alert mail to the configured support recipient and can also send one SMS alert to the configured site-owner mobile number. The alert is deduplicated for the current incident and resets automatically after the next successful X-bot run.
  • Public /out/{contentId} feed redirects now cache resolved outbound article links and skip unnecessary feed-source lookups when the stored link is already absolute. This makes repeated article clicks much less dependent on the RSS database and reduces timeout risk during heavier crawler traffic.
  • The public ToolGPT / X-bot guide now includes a real FAQ and an explicit prompt-design section covering how replies are built, why the bot is supposed to stay politically neutral, why it pushes back on dehumanization or collective blame, and how the built-in neutral base prompt relates to the admin-visible fallback instruction and fallback mood fields.
  • The vBulletin invite-outreach composer now shows each saved invite key's current status directly in the recipient list, and keys that are already consumed, expired, or inactive stay visible only for review while recipient edits plus Messenger/email send actions are locked.

2026-05-10

  • SocialGPT's Facebook participant_requests helper now prefers stable left-column moderation rows such as comment/preview and unanswered-question lines before it falls back to the top-right ... area, and its DOM observer now stays scoped to the relevant request content root so heavier moderation pages trigger fewer unnecessary rescans.
  • The floating Facebook participant scanner popup can now be dragged around and remembers its last position instead of snapping back to the default lower-right corner.
  • The old unclear SG participant-request wording is gone: the main card action now explicitly says Analyze user, and the Toolbox import now structures visible group/friend clues, visible membership questions, unanswered-question state, and profile/background hints before the raw card text.
  • Participant-request rescans no longer depend only on the manual scan button: the helper now also rescans automatically after scroll/load-more settle plus a light timer fallback, and the popup no longer duplicates the Facebook Send queued / Release stuck controls that already exist on the ingest page.
  • The participant helper now keeps only Show card and Analyze user in the Facebook card UI, defaults to a docked top-right panel instead of dropping low on the screen, and verify-mode checks now degrade gracefully when OpenAI web search fails instead of hard-stopping the verification result.
  • The X bot admin page no longer crashes when Recent interactions applies its visibility filter to the account relation query, and when operators explicitly show non-publishable rows the truly hard-blocked diagnostics now stay in their own separate section instead of mixing back into the normal review queue.
  • The Microsoft To Do integration page now shows a proper direct browser auth-start link in addition to the callback URL, so operators can copy/open one real Tools-side OAuth start URL instead of being left only with the callback endpoint.
  • Manually created user accounts now default to email verified in the admin user-creation flow, and the same user editor now includes an explicit email-verification toggle so older manually created accounts can be corrected without waiting for a separate mail-click flow.
  • The X bot admin page is now easier to search and scan: raw serialized interaction payloads are loaded only when an operator explicitly opens them, and the large event log section now stays collapsed until requested instead of filling the page by default.
  • Clearly non-publishable X-bot rows are now hidden by default in Recent interactions, with an explicit show-toggle for diagnostics. The same obviously unengaged follow-up threads are now blocked before Tools spends money on AI reply generation, instead of first generating a candidate that X could never accept anyway.
  • The X bot event log now also has its own dedicated admin page, so the main X-bot console no longer needs to carry the whole runtime history inline. Spend-cap warnings on that page and in the main diagnostics area now also explain that the block can come from a separate project/account spend cap even when prepaid provider credits still remain.
  • The SocialGPT Facebook participant-request helper now has a stronger fallback when Facebook's layout is awkward: the floating SG participant scanner can list the visible request cards, scroll to the correct card, and launch Analyze in Toolbox / Verify facts directly from that list. Those actions now also anchor the Toolbox/fact result to the chosen request card instead of opening in a generic detached corner.
  • The Microsoft To Do OAuth callback now restores the matching Tools web session from the signed OAuth state before redirecting back to /settings/integrations/microsoft-todo, so a successful callback no longer drops the user onto a fresh login wall just because the earlier browser session was gone.
  • The vBulletin registration invite composer now treats the Messenger template as the primary source for the default email body too: when the stored email HTML was still just the derived default, changing the Messenger registration text now regenerates the HTML email template automatically until an operator explicitly writes a custom HTML version.
  • The X bot is now less likely to get stuck behind an opaque internal non-replyable/policy block: when the structured reply ladder ends in a generic non-replyable result with no useful explanation, Tools now keeps the real provider error text when available and also tries one last plain-text direct-reply recovery pass before the interaction is finally left blocked.
  • The Recent interactions area on the X bot admin page is now paginated over AJAX, so operators can move through stored interaction history page by page without reloading the whole admin page.
  • The X bot admin interaction cards now mark hard refusals much more aggressively: when a candidate is non-replyable or X would reject the post anyway (for example because the bot was not actually engaged in that thread), the card now shows a dedicated warning/error block with the concrete reason and keeps Approve/post disabled instead of letting operators hit the same refusal blindly again. The Structured reply max attempts field now also gives a direct 1–5 explanation instead of a vague invalid-data failure.
  • The X bot admin panel now includes a stronger opt-out management area: operators can review all stored opt-outs, opt one account back in directly, configure usernames/X user ids that should always bypass opt-out, or temporarily override all stored opt-outs globally. @Tornevall is now always treated as protected and can no longer be effectively blocked by X-bot opt-out handling.
  • ToolGPT is now less likely to get trapped by Grok or another known AI account when ordinary people are also active in the same thread: in mixed-participant conversations, Tools now deprioritizes the AI account as reply focus and keeps aiming at the non-AI participants instead. No keyword-rule match is now also stated more explicitly as a normal fallback case rather than something that should block a reply on its own.
  • The X bot admin panel now lets operators tune how many structured public-reply attempts Tools should try before giving up. The current supported range is 1–5, covering the existing GPT-5.4 reasoning ladder plus the final gpt-4o fallback.
  • ToolGPT now marks more X replies as Do not publish before operators hit the X API refusal: when a stored follow-up tweet was not actually a direct mention/reply to the bot, the interaction now stays visibly non-publishable instead of looking publishable until X rejects it during posting.
  • ToolGPT rule overrides are now less eager on ultra-thin tweets such as a bare mention plus a link. Fallback instruction/mood is used more often there instead of inheriting one older thread rule just because the surrounding conversation happened to contain matching keywords.
  • ToolGPT's repetitive-AI-loop safeguard now focuses on the latest exchange instead of stale older context, so an older Grok-style repetition is much less likely to block a thread after the conversation has already moved on.
  • ToolGPT/X bot opt-out detection is now stricter about intent: generic complaints that merely contain words like stop or Swedish sluta are no longer treated as opt-out commands unless the wording clearly asks the bot to stop replying/engaging. Bare stop / sluta wording on its own no longer counts as opt-out either. Older automatically stored false-positive opt-outs can now also auto-release when the same author later directly engages the bot again with a normal mention. The admin interaction cards still include an Opt in again action for manual recovery when needed.
  • ToolGPT/X bot now falls back to a normal context-based reply mode when a verification-style prompt wanted web search but the web-search tool was not actually used, instead of posting the repetitive independent verification missing boilerplate directly into the public thread.
  • The X bot now presents Required closing hashtags more explicitly as an optional advanced field instead of making fixed branded hashtags look like a normal default reply strategy.
  • Verification-aware AI flows are now stricter about what may count as verified: when a request requires real web search and OpenAI does not actually use that web-search tool, Tools now returns a clear independent verification missing outcome instead of pretending the answer was independently verified.
  • Microsoft OAuth now also has a more platform-neutral API entrypoint at GET /api/microsoft/oauth/start. The older GET /api/microsoft-todo/oauth/start path still works for backwards compatibility, but the generic path is now the recommended one when the same Microsoft app registration is meant to serve the wider Tools platform.
  • The SocialGPT browser extension now writes far less aggressively to Chrome extension storage during background logging/settings refreshes, which reduces MAX_WRITE_OPERATIONS_PER_MINUTE quota failures in the extension background worker.
  • ToolGPT now has a safeguard against repetitive bot-to-bot dead-end loops: when a known AI participant such as Grok keeps repeating the same or nearly the same reply pattern and the thread is no longer progressing usefully, Tools can now mark that thread as finished instead of continuing the same exchange for hours.
  • The X bot now detects Swedish/English verification wording more reliably, so mentions about checking facts, sources, proof, or RSS/feed lookup can enter a stronger fact-check path instead of staying in a generic ask flow. The prompt can now also include matching evidence snippets from the Tools RSS archive when that kind of request is made.
  • Verification-heavy OpenAI flows now treat reasoning effort and web search as separate controls: when the current environment supports the OpenAI Responses API web-search tool, Tools can now request real web search for current-information checks instead of only increasing reasoning effort.
  • Setting max_reply_length=0 no longer means “send overlong replies unchanged”: Tools now still enforces the normal X hard reply limit before posting, even when the extra local Tools cap is disabled.
  • The X bot is now less likely to get stuck on the first weak keyword hit: Tools now scores active rules, picks the best-matching rule, and lets stuck interactions be reprocessed with the latest settings plus the new rule analysis.
  • The public docs now also include a dedicated ToolGPT changelog page under /docs/en/toolsgpt-changelog, intended as the public reference when people ask how the X bot has evolved over time without exposing internal runtime details.
  • The Facebook participant-request helper is now much more aggressive about the current Facebook participant_requests layout: the per-card Analyze in Toolbox / Verify facts helper now climbs a wider DOM range, skips the bulk-action toolbar more reliably, and restores the saved Tools-side checkbox state on the Facebook Admin Tools page more consistently. That same admin page no longer renders the same green success confirmation twice.
  • Microsoft To Do now exposes a dedicated authenticated API OAuth-start helper at GET /api/microsoft-todo/oauth/start, and the callback URL can now finish the connection even when the browser has lost its active Tools web session, as long as the signed OAuth state still matches the original user/transaction. The integration page now also explains the common unauthorized_client mismatch between tenant choice and Azure app account-type support more clearly.
  • The Facebook Admin Tools page now saves its two main Tools-side master switches immediately when you click them, and the extension now follows those Tools-side values more strictly on load/route changes: the admin activity helper no longer keeps running just because of stale local cache, and the participant-request helper on /groups/*/participant_requests now wakes up from the Tools-side participant scanner switch more reliably.
  • The threaded Tools-side Mail Support Assistant reply box is now easier to steer per draft: operators can set one temporary mood/tone, model, and reasoning effort before generating a direct reply draft, and Tools now also normalizes greeting plus closing footer/signature automatically so generated or manually edited replies do not accidentally go out without a proper hello/sign-off.
  • The X bot now keeps watching already engaged conversation threads more intelligently: when the bot was already pulled into a thread, later follow-up replies in that same conversation no longer need to repeat @ToolGPT just to be ingested for review/reply handling, and explicit mentions are no longer misclassified as keyword-only just because a watch keyword matched too.
  • The SocialGPT browser extension can now help on Facebook group participant_requests pages when the feature is enabled from Tools: visible participant-request cards get compact Analyze in Toolbox and Verify facts actions, and the participant-scanner switch is managed from the Tools-side Facebook settings instead of a separate local popup toggle.
  • The X bot now supports a dedicated Required closing hashtags setting in the admin AI panel. When one or more hashtags are configured there, Tools always moves those same hashtags to the very end of the final reply, in the same order, even after local shortening or repost/post preparation.
  • The vBulletin registration key workflow is now easier to hand over: /admin/security/vbulletin is the clear preconfiguration page for groups, templates, destination, and other defaults, while the admin/delegated key pages now stay focused on choosing one onboarding flow, one recipient, and only optional advanced overrides. The older bulk copy-list blocks for active keys/direct URLs have been removed from those key pages.
  • The vBulletin invite-outreach composer now labels the test-send action as a real dry run to the currently signed-in Tools account, so operators can verify the email template safely before sending the live invite mail to the saved recipient.
  • The X bot admin page now keeps a persistent warning banner visible when a provider-side billing/spend cap blocks the bot, and that warning clears itself automatically after the next successful X-bot run.
  • The detailed online admin page can now do more than hide trusted IPs/CIDR ranges: admins can also add custom user-agent contains rules (for example dnsbl-engine-tools-client) so internal tools or automation clients are counted as bots/crawlers everywhere the live online counters are shown, and bot rows now surface clearer bot names plus categories in /online and the admin session view.
  • The vBulletin registration flow now uses clearer registration-oriented wording on the public pages, services/start-page shortcuts, and the main public guide instead of leaning so heavily on the word onboarding.
  • Tools now also has a short vBulletin registration changelog page under /docs/en/vbulletin-registration-changelog, intended as a compact operator summary that can be copied into external notes or a separate Google Drive document.
  • The built-in Messenger/email invite templates for the vBulletin registration flow are now more pedagogical by default: they explain that the recipient should finish the forum registration, keep the Tools status page afterwards, and contact support@tornevall.net plus the inviter if the code or link does not work.
  • The vBulletin admin audit trail now logs the public registration page view and personal registration-status view more explicitly, while the invite-code usage log is also clearer about when a real registration request was created.
  • Tools now also has a dedicated Playwright how-to page under /docs/en/playwright, focused specifically on when to use stored browser-automation scripts, browserbot.sh, record/open/profile-clone flows, and the Playwright E2E runner.
  • Mail Support Assistant replies sent directly from the threaded Tools case view now also generate and persist a stable reply issue id when an older handed-over follow-up case still had none, so outgoing Tools replies again include the same [Ärende MSA-…] style subject tag instead of sending a plain Re: subject.

2026-05-09

  • Mail Support Assistant follow-up threads can now be answered directly from Tools: the threaded admin case page includes an operator instruction field, AI draft generation, an editable reply body, and a Send reply via Tools action, and once a skipped/no-match/reply-not-sent thread has been handed over successfully to that centralized Tools follow-up flow the standalone runner can now mark the mailbox copy as seen so the same unread mail is not retried endlessly outside Tools. That successful handoff now also avoids repeating the same IMAP mark-seen step a second time.
  • Mail Support Assistant case sync is now tolerant of empty selected-rule placeholders too: when no matched rule exists yet, standalone/runtime clients can omit selected_rule* fields as before, and older payloads that still send selected_rule_id=0 are now normalized instead of being rejected with a generic 422, so unmatched mailbox messages can still appear in the centralized Tools case view.
  • browserbot.sh now supports explicit profile seeding/cloning for multi-account Playwright setups: --profile is available as a shorter alias for --profile-directory, --copy-profile-from can seed one persistent profile from another before open/record/run, and --clone-profile can copy a prepared base profile into a new target profile without starting the browser yet.
  • The standalone Mail Support Assistant now sanitizes and size-limits its Tools case-sync payloads before sending them, so odd mailbox encodings or overlong body/header fields are much less likely to disappear behind a generic 422 when unread mail is reported back into the centralized Tools case view.
  • RSS Watch now includes a dedicated posting queue for operators with the separate rss.posting.handle permission. The new /feed-admin/posting-queue and /rss/posting-queue UI lists the latest entries per feed/category with AJAX checkboxes for Handle group, Handle page, Handled group, and Handled page, and matching web-session API endpoints now live under /api/rss/posting-queue/items* so browser automation or another internal UI can mark entries handled as soon as they were posted.
  • Browser Automation now includes a browserbot.sh --open-socialgpt shortcut that opens real Chrome with the local projects/socialgpt-chrome extension and the shared persistent default profile, making it easier to keep SocialGPT available from the default reusable browser-automation profile.
  • Browser Automation now explains the safer extension/login preparation flow more clearly: Chrome Web Store and Google-account sign-in should be prepared in browserbot.sh --open --browser chrome with the same persistent profile, while unpacked local extensions can be loaded directly from a local folder instead of going through the Chrome Web Store.
  • Browser Automation now defaults to saved persistent profiles instead of ephemeral runs. When a stored script has no explicit profile_directory, Tools automatically falls back to that script's key as the saved profile name, and a clean one-off run now requires an explicit --fresh-profile request.
  • The browser-automation install helpers are now more robust on remote Linux hosts: bin/fixnode.sh waits for active apt/dpkg locks before repairing Node.js, verifies that node, npm, and npx really exist after installation, and the Playwright installer now points more clearly at the Node repair helper when npm tooling is still missing.
  • Browser Automation now also includes a repo-root browserbot.sh wrapper so admins can launch one stored browser script from shell by key or exact name instead of rebuilding the Playwright command by hand. The browser automation admin pages now show copyable shell examples, and persistent login/cookie reuse is now documented more explicitly around the stored profile_directory field.
  • Facebook Admin Tools now also has a merged moderation chart mode that combines manual and automatic approvals/rejects into the four main moderation groups (Approvals · join requests, Approvals · pending posts, Rejects · join requests, Rejects · pending posts) for a faster overview, while the older fully separated automatic/manual outcome view still remains available.
  • The admin user editor can now send a normal password reset email for one account directly from the control center, and the web login flow now also includes a public forgot-password/reset-password path so users can recover their account without manual password changes.
  • Live online counters can now hide trusted internal traffic by IP or CIDR. Admins can manage that whitelist from the detailed online page, and matching IPs are excluded from the small shared online widget, the Site Statistics online breakdown, and both /online views.
  • Mail Support Assistant now reports every relevant unread discovered mailbox message into Tools immediately, before reply/no-match handling finishes, so admins can still review the mail in the threaded Tools case view later even when no automatic reply was sent.

2026-05-08

  • X bot reply candidates are now normalized from Markdown into plain text before they are stored or posted, so raw URLs stay visible as ordinary URLs on X instead of being left inside Markdown link syntax.
  • X bot structured reply generation now keeps pushing through the full five-attempt ladder when no usable answer exists yet (four GPT-5 passes with stronger reasoning, then one final GPT-4o fallback pass), and already-posted replies now surface as Already posted in the admin interaction badges instead of looking like a fresh Do not publish block.
  • The shared frontend online summary now splits active traffic into bots, guests, and logged-in users instead of only guests vs users, and the admin Site Statistics page now uses a clearer card-based runtime overview with the same richer online-session breakdown.
  • The Site Statistics admin surface is now easier to reach and easier to read: older /admin/site-statistics links now redirect to the current /admin/security/site-statistics route, the admin/welcome shortcuts now point at the correct route, and the page itself has a stronger control-room style with quick links for adjacent admin tools.
  • Tools now also has an admin-only Playwright E2E runner under /admin/browser-automation/playwright for the project-level tests/e2e suite. Admins can inspect test files, run the whole suite or one allowed spec, choose base URL / skip-webserver / persistent profile settings, and review stored run history with stdout, stderr, exit code, HTML report links, screenshots, videos, and traces when those artifacts exist.
  • The Laravel Playwright helper setup now includes a persistent-session-aware config under storage/playwright/*, a reusable persistent-session test helper for specs that should keep login state between runs, a dedicated bin/playwright-record-persistent.sh codegen helper, and a matching test:e2e:codegen npm alias without replacing the existing Mix/Vite project scripts.
  • The SocialGPT / Facebook admin workflow is now easier to find from Tools: dashboard/profile/start-page shortcuts now point directly at Facebook Admin Tools, the Social Media Tools landing page now calls it out explicitly, and the Tools-side settings page now has a real stored on/off switch for the Facebook admin activity workflow that the extension can mirror on sync.
  • The X bot now paginates deeper through earlier posts in the same X conversation before building its reply context, keeps more of that earlier thread history in the local interaction archive, ignores later replies that happened after the current mention, and shows basic thread-capture diagnostics in Recent interactions so admins can see how much same-thread context was actually collected.
  • The public docs now expose the X bot more clearly: the docs index links directly to /docs/en/x-bot, the public services page links to that guide, and the guide now explicitly explains that ToolGPT on X is the public-facing account identity for the Tools-backed X Mention Bot.
  • X bot replies now run through a structured JSON decision contract (message, can_reply, status) instead of only raw prose, so recent interactions and API consumers can see when a candidate is actually replyable, why a refusal happened, which GPT-5/GPT-4 model path was chosen, and which alternative candidates were tried. GPT-5 reply generation now escalates reasoning effort step-by-step before falling back to gpt-4o, the recent-interaction history now uses badge-heavy expandable cards for that decision/rerun data instead of one cramped table row, and Facebook admin activity queue sends in the SocialGPT browser extension now retry directly to Tools when the normal extension-runtime handoff stalls.
  • Public /feed and /feed/cards/{categorySlug} now merge category analytics by the category slug before picking the visible edition, so the newest current-bucket analysis still wins even if older cached rows for the same category were saved under slightly different name/case variants such as Right Wing vs Right wing.
  • Feed Admin now sorts category/site analytics by the actual period bucket before generation time, so older buckets no longer jump ahead just because they were regenerated later, and the category cards page now shows the real bucket label/date more clearly instead of only the generation timestamp.
  • vBulletin onboarding invite workflows now also track recipient email per key, include a selectable invite-outreach composer with configurable messenger-ready and HTML mail templates, and can send either a real HTML invite mail to the saved recipient or a test copy to the current Tools account email.
  • My Profile can now verify a vBulletin username/email + password against the forum before linking that forum account to the current Tools account, making the Tools↔forum connection self-service instead of admin-only guesswork.
  • Recent X-bot interaction rows now show how many shortening reruns each candidate reply needed before it fit the configured character limit, together with a short per-run history for the latest length-fix attempts.
  • Public /feed and feed-card analysis selection now prefers the newest generated current-bucket edition before older default-marked variants, so fresh daily analyses no longer get hidden behind stale cached defaults.
  • Feed Admin and Scheduled Jobs now show clearer RSS analytics scheduler results per period, including the last scheduled slot, anchor date, success/error counts, languages, and a short output excerpt from the last cron-backed run.
  • Feed widget script bundles under /api/managed-scripts/feed* now accept additive feed_ids / feeds, categories / groups / category_slugs, and limit query parameters, and expose the normalized values back to the bundle/API response as query_context.filters.
  • vBulletin feed auto-detection now also keeps query-scoped external feed parameters (for example nodeid / forumids) when trying sibling rss2/rss/atom candidates, and feed probing now follows redirects more consistently for those external feed URLs.
  • Mail Support Assistant now keeps unanswered or unmatched synced mail threads visible in Tools as follow-up cases, highlights those needs_attention threads more clearly in admin, and lets admins delete one synced thread directly or bulk-delete older synced threads by age/status from /admin/mail-support-assistant.
  • The vBulletin onboarding key pages now let admins and delegated key creators tag each invite key with its own recipient/assignee name, and that lightweight field saves over AJAX directly on the key card so it is easier to see which person already received which invite link.
  • The RSS editor's 🤖 Auto feed detection now recognizes vBulletin-style external RSS links such as external?type=rss2... and external.php?type=RSS2..., including relative feed links discovered from forum pages, so those sources are now stored as ordinary rss feeds instead of falling back unnecessarily.
  • The Snöbollseffekten screenshot guide has now been simplified so that forum registration and the invite code are presented as the first real steps, while the Tools page only comes in after the forum account is already ready.
  • The Snöbollseffekten screenshot walkthrough now describes the finished status page even more explicitly, including that the applicant confirms access is ready there and then continues manually via the visible destination link/button.
  • The screenshot walkthrough for Snöbollseffekten now matches the updated finished-state image again, so the last step explains the completed onboarding/status page more accurately.
  • Public vBulletin onboarding status pages no longer auto-redirect when forum access becomes ready. The page still polls in the background, but once the applicant is approved it now keeps the user on the status page and shows a clear manual continue link/button to the configured forum destination instead.
  • The public /online page now separates likely bots and crawlers into their own section, shows a dedicated bot counter, and keeps automated traffic out of the ordinary guest list so the human online overview is easier to scan.
  • vBulletin onboarding key management now labels the reusable invite path more clearly than the single-use key path, both the admin and delegated key pages can delete invite keys over AJAX without a full reload, each invite key can now optionally override the success redirect URL/button shown on the public status page after approval, and the main vBulletin admin page can now generate paste-ready HTML blocks for two hosted forum widgets: latest member by selected usergroup(s) and current member count by selected usergroup(s).
  • vBulletin onboarding flows can now show their own configurable public welcome message on the invite page, stay hidden from the public onboarding hub unless they are explicitly marked for listing there, and both the admin plus delegated key-manager pages can now delete older referral/one-time keys and copy active invite keys or direct invite URLs as ready-made bulk-invite lists. The public docs now also include a separate screenshot walkthrough for Snöbollseffekten, with an explicit note that the text shown in the screenshots may not match the current UI word for word.
  • Public vBulletin onboarding status pages now render one primary status banner instead of two duplicate green success boxes, can keep polling in the background while Tools waits for forum access, and switch to a clear completed/access-ready state with a forum button plus automatic redirect when the allowed forum groups are active. Each onboarding config can now also store its own forum destination URL/button label for that final step, with https://forum.tornevall.net as the fallback when nothing more specific is configured.
  • Public vBulletin onboarding no longer hard-crashes when the automatic forum-side approval step fails (for example because the remote forum API returns 403). Tools now keeps the onboarding request, links the matched forum account when possible, and moves the request to Pending review so an admin can complete the approval later instead of the applicant seeing a raw exception page.
  • vBulletin onboarding/group-admin writes now refresh forum cache state with a separate-host-safe strategy after the scoped group save, preferring a dedicated remote refresh endpoint or direct cleanup of the forum cache tables through the configured forum database connection instead of assuming the forum core is mounted on the same server as Tools.
  • vBulletin onboarding/group-admin writes now also call the forum's own core/api.php save path after the scoped secondary-group change, so the affected forum account is rebuilt immediately and the old manual “open user in vBulletin and click Save” workaround should no longer be needed.
  • The vBulletin onboarding profile-field dropdown now follows the forum's profilefield records again instead of being limited by raw userfield column discovery, so configured custom fields such as field66 can reappear in the onboarding admin/key-generator selectors even when older leftover userfield columns would otherwise make the list misleading.
  • vBulletin onboarding now falls back to forum group 155 when an onboarding config has no explicit allowed-group list, and both the admin key generator plus the delegated key-creator page can now create invite links in bulk with either no expiry, one exact expiry timestamp, or an extend-by-days lifetime. Batch-created labels are auto-numbered, while account-bound profile-field invite writes are still intentionally limited to one key at a time so one forum profile is not overwritten repeatedly.
  • The vBulletin admin page now loads live forum groups from the usergroup table when configuring bounced-email handling, so admins can choose the target/exempt groups by real name instead of guessing numeric ids. The same page now also documents the exact membergroupids write format: one plain id when the field was empty, otherwise a comma-separated list without spaces.
  • Existing-account vBulletin onboarding copy now avoids pretending that one exact invite profile field is already known for every flow: when a dedicated field is actually configured it is still shown explicitly on the page, but the generic instructions now fall back to the user's profile wording instead of claiming a configured field that may still be unset.
  • The shared top navbar no longer shows the Invite Keys shortcut. Invite-key creation and management still exist, but they now stay discoverable from the more specific user/admin surfaces instead of taking space in the main navigation.
  • The Users admin list now opens a fuller per-user control center when you click a user, and that page shows much more of the user's real access footprint at a glance: linked child accounts, personal API keys, DNS-zone access, OAuth connections, Social Media Tools settings, service counters, and the existing edit/ban/delete controls in one place. Admin shortcuts that jump from other service pages now also point back to that same control center instead of the older quick-edit screen.
  • The migration 2026_05_02_150000_create_vbulletin_onboarding_key_profile_assignments_table is now extra defensive when the table already exists, so repeated or partially completed deploys do not stop on a duplicate-table error just because one environment created the table before the migration record was stored. The duplicate-table guard now also survives wrapped exception chains instead of relying only on one direct exception type.
  • Existing-account vBulletin onboarding no longer asks applicants to re-enter name and email on the Tools page: those invite pages now lead with the profile-field instructions first, show the exact invite code clearly, and then expose a minimal Start onboarding action once the invite code has been saved in the forum profile. Profile-field-backed invite links now use that same existing-account-first public flow even when the key already points to one known forum user.
  • Delegated vBulletin onboarding key creators can now see all invite keys for the slugs they are allowed to manage, and Tools now keeps a small profile-field replacement history that links older overwritten invite values back to the newer Tools key plus the affected vBulletin username.
  • vBulletin onboarding flows can now be configured for already registered forum accounts: the public invite page can start with a welcome/instruction step plus an AJAX-loaded Onboard here button, invite-key matching can now look at the configured vBulletin profile field, and Tools now clears the stored invite key from that profile field after approval and forum-group assignment.
  • vBulletin onboarding configs can now optionally map invite codes into a specific vBulletin custom profile field (for example field66), and newly created invite keys can target one known forum user so the code is written into that user's profile field and the onboarding request can link to that forum account immediately before applying the configured secondary group.
  • vBulletin onboarding links can now be created with a preferred public language (English or Svenska), and the public slug page, onboarding form, and bookmarkable status page now open in Swedish when a Swedish onboarding key/link is shared.
  • The Services catalog now explains more clearly what the onboarding-related boxes mean, the public onboarding hub now shows more of each slug's configured intro/rules text with preserved line breaks, and admins now have a dedicated onboarding key-generator page at /admin/security/vbulletin/onboarding/keys that focuses on referral/one-time keys plus welcome-message presentation.
  • vBulletin onboarding now supports both /vbulletin/onboarding/{slug} (enter key manually) and /vbulletin/onboarding/{slug}/{inviteKey} (key prefilled), old token-only links can redirect to the canonical slug URL, and expired/wrong keys now fall back to a friendlier onboarding-key prompt instead of throwing a raw exception page.
  • vBulletin onboarding configs can now delegate invite-key creation to ordinary Tools users on a per-slug basis, and those users get their own authenticated key-manager page at /vbulletin/onboarding/keys.
  • The onboarding invite-key editor is now surfaced more clearly from Users, the dashboard, the vBulletin admin page, the services catalog, and the main navigation when the current account is allowed to create those keys.
  • The admin user editor now shows a clearer account overview (mobile number, parent link, linked-child count, Google-link state, and active personal API keys), and the create-user form can now save mobile numbers plus an optional parent account from the start.
  • Menstrual Tracking now supports optional reminder SMS sent through the existing SMS API, with a per-user lead time from 1 to 7 days before the estimated next cycle.
  • Menstrual cycle estimates now handle large reporting gaps more intelligently by splitting likely missed reporting windows into smaller plausible intervals instead of inflating the average cycle length.
  • Accounts can now be linked in a parent/child relationship so a linked parent can also receive the child's due menstrual reminder SMS when that reminder is enabled.
  • My Profile is now a broader settings hub with mobile-number management, linked-child controls, and direct shortcuts to the services the current account can already use.
  • Added a dedicated token-authenticated Whisper transcription API under /api/whisper/transcribe/*, including required callback_url, terminal callbacks, and queue-origin metadata (queue_channel).
  • Whisper queue views and authenticated job payloads now show whether a job came from the ordinary web queue or the new API queue, and completed API jobs can include a direct public transcript share link.
  • Added user-facing guidance for the new whisper.api permission and Whisper API token flow, and tightened the public Whisper documentation so it focuses on the external contract instead of internal runtime details.
  • The Whisper queue form now separates Whisper's own transcription-language hint from a new queued analysis_language, supports multi-select translation_target_languages[] for automatic post-transcript OpenAI translations, treats diarization as enabled by default per job unless the operator explicitly disables it for that job, and shows more visible heartbeat-driven progress updates while Whisper is still transcribing between raw output lines.
  • Whisper now accepts PATH-based WHISPER_YTDLP_JS_RUNTIMES=node / deno specs in addition to runtime:/full/path, php artisan whisper:doctor now reports the exact missing/non-executable runtime path instead of only a generic JS-runtime failure, and bin/install-whisper-venv.sh now prints browser/proxy env keys plus validates the current .env Node/Deno/browser runtime paths against the binaries it installed.
  • php artisan whisper:doctor now reports Laravel-config, process-env, and effective WHISPER_YTDLP_PROXY values separately, while Whisper runtime attempt lines show proxy=(not configured) when the artisan worker did not actually receive a proxy; this makes stale config-cache / wrong-runtime-env cases obvious when manual yt-dlp --proxy ... works but queued Whisper jobs still hit YouTube 429.
  • Whisper's yt-dlp runner now passes WHISPER_YTDLP_BROWSER_PATH through to yt-dlp as --browser-location when browser-cookie mode is enabled, keeps ordinary temporary/file-based cookie fallbacks behind that browser phase instead of making browser mode all-or-nothing, exports WHISPER_YTDLP_PROXY into the child-process proxy env as well as the CLI flag, and summarizes repeated YouTube 429 / bot-check / missing-runtime failures much more cleanly in stored operator-facing error text.
  • Whisper's yt-dlp runner can now read an optional proxy from WHISPER_YTDLP_PROXY, can prefer --cookies-from-browser through env (WHISPER_YTDLP_COOKIES_FROM_BROWSER plus optional WHISPER_YTDLP_BROWSER_PATH) instead of relying on hardcoded browser-path checks, falls back cleanly to Netscape cookie files when that browser probe is unavailable, and the URL-title prefetch path now uses Laravel-8-compatible HTTP client options instead of the unsupported PendingRequest::connectTimeout() call.
  • Whisper now prefers real fetched page-title metadata when suggesting source_label for pasted media URLs, can ask OpenAI to shorten noisy/overlong page titles from fetched head/body content before falling back to a generic URL label, lets transcript owners queue diarization-only reruns from the detail page over AJAX, and now tries transcript analysis through gpt-5.4 with reasoning before falling back to the normal 4o-style model path while keeping Swedish transcripts much more aggressively in Swedish.
  • Whisper job detail pages now let the transcript owner edit the human-facing title/description metadata (source_label / source_note) fully over AJAX, and when a completed transcript exists the same owner can also ask OpenAI to suggest better text directly into those fields before saving.
  • The signed-in Whisper queue form now tries to prefetch a cleaner source_label from pasted remote URLs (YouTube/oEmbed first, otherwise page metadata such as og:title / twitter:title / <title>), falls back to the existing URL-derived label style when no clean title is available, respects manually edited labels instead of overwriting them, and now uses a clearer responsive form layout for URL/upload/model/language input.
  • Whisper now attempts best-effort speaker diarization automatically after each successful transcript save when WHISPER_DIARIZATION_ENABLED=true, stores additive diarization metadata plus speaker_aware_transcript / transcript_segments[].speaker_label, keeps transcripts completed even when diarization fails, and extends php artisan whisper:doctor with masked Hugging Face token presence plus pyannote.audio / torch import checks.
  • Whisper transcript reading order is now transcript-first across the new public share page and the signed-in detail pages, with transcript analysis following afterwards and source/share metadata pushed lower on the public page for cleaner sharing.
  • Whisper transcript analysis now explicitly follows the job's stored transcript language when available, with English as the fallback output language instead of relying only on transcript-language guessing.
  • Whisper completed jobs can now create a revokable public share page under /shared/whisper/transcript/{token}, so operators can hand out a clean transcript view showing only title/source info, transcript analysis, and full transcript text without exposing the normal job-progress/runtime UI.
  • Whisper job detail/API payloads now also expose cpu_threads_configured, processing_elapsed_seconds / processing_elapsed_human, and observed_throughput_multiplier / observed_throughput_summary, while the live runner detail now logs the active model/language/thread count/clip duration before transcription and reports elapsed time plus realtime throughput when Whisper finishes.
  • Whisper jobs now also record media duration when the host can probe it after upload/download, and the queue/detail/API surfaces expose that as source_duration_seconds plus a human-friendly source_duration_human label so operators can see clip length before opening the full transcript.
  • Whisper CPU-bound transcription now supports explicit thread tuning through WHISPER_CPU_THREADS, which appends --threads <n> to the current Python openai-whisper CLI and mirrors the same cap into common CPU math-library env vars so a 4-vCPU host can be pinned to 4 transcription threads more predictably.
  • Whisper queue/detail/mail surfaces now hide exact internal storage/cache paths much more aggressively, so stage details, runtime snippets, and notification mail no longer leak the precise filesystem location of Whisper job workspaces or Laravel cache files.
  • Whisper transcription requests now also accept optional source_label and source_note metadata, so URL-based jobs can be given a human-readable title plus free-text context when the fetch itself is vague or unreliable.
  • The system cron example now runs Laravel/Whisper Artisan work as the real web runtime user instead of leaving root-owned cache locks behind, and the new helper bin/repair-laravel-runtime-permissions.sh can repair storage/* plus bootstrap/cache ownership/permissions automatically from cron.
  • Whisper now attempts transcript analysis automatically as soon as a transcription completes, so completed job metadata can already contain analysis before any manual analyze action is triggered.
  • Whisper owner notification mails are now styled HTML messages with a plain-text fallback, and completed-job mails include both the generated analysis and the transcript text instead of only a short status summary.
  • php artisan whisper:process now takes a host-local non-blocking flock in addition to the existing Laravel cache lock, so cron/terminal runs on the same host cannot overlap even before the queue lock is reached.
  • Whisper transcription progress is now more visibly updated while the actual transcribe step is running, so jobs no longer look as frozen between the initial download and final transcript write.
  • When a Whisper job is reclaimed as stale after heartbeat timeout, the runner now also tries to kill any recorded lingering child PID/process group before requeueing/failing the row, helping free stuck yt-dlp/Whisper processes on the host.
  • Whisper execution is now CLI/cron-only: web and API requests no longer run Whisper or yt-dlp directly, restart now only re-queues the job for the next worker pass, and the admin/API run-now helpers are now queue/reset-only so terminal/cron remains the only execution environment.
  • Whisper CLI now supports direct queue-lock recovery through php artisan whisper:process --unlock-only, plus one-shot unlock-and-run via php artisan whisper:process --unlock --limit=1 when a stale whisper:queue-runner lock blocks processing.
  • Whisper now sends a terminal-state email to the request owner when a transcription job finishes as completed or reaches final failed, including job id/status/source and the latest failure text when applicable.
  • Whisper URL routing is now yt-dlp-first for all URL jobs (not host-gated): every URL now attempts yt-dlp first, while direct HTTP is only a last-resort panic fallback for likely direct media URLs and now enforces strict media verification so HTML/login/captcha/JSON responses are never stored as input media.
  • Whisper queue/runtime now uses explicit process environment settings (WHISPER_PROCESS_PATH, WHISPER_PROCESS_HOME) for yt-dlp and Whisper subprocesses so web/queue execution can match terminal behavior more reliably.
  • Whisper yt-dlp runtime resolution is now Node-first with verified executables and Deno as last fallback (WHISPER_YTDLP_DENO_BIN), with clearer diagnostics when configured JS runtimes are invalid or missing.
  • Whisper now includes php artisan whisper:doctor for runtime diagnostics (binary paths, version checks, effective PATH/HOME, JS runtime availability, cookie file readiness, and whisper storage write access).
  • Whisper's yt-dlp YouTube path now starts with one intentionally plain/basic anonymous_temp run before the newer JS-runtime, extractor, and format fallbacks kick in, matching the safer operator expectation that a simple public-video attempt should happen first.
  • Whisper's yt-dlp YouTube path now also supports explicit JavaScript runtime configuration through WHISPER_YTDLP_JS_RUNTIMES and WHISPER_YTDLP_NODE_BIN, and will try to auto-detect node / nodejs from PATH for YouTube-style downloads when no explicit runtime override is configured.
  • Whisper's yt-dlp cookie policy now always starts with anonymous_temp (an empty temporary cookie jar), only falls back to db_temp and then raw_file_temp on explicit access-required failures, and always hands yt-dlp a fresh temporary cookie file rather than the original stored DB cookies or raw storage cookie file.
  • Whisper's yt-dlp failure logging is now reduced to sanitized stdout/stderr plus cookie-source/fallback metadata, so operators can diagnose YouTube failures without leaking cookie contents or raw temp-file paths.
  • Whisper's yt-dlp YouTube retry path no longer builds duplicate -f arguments, now keeps the normal first attempt free from the accidental forced bestvideo+bestaudio selector, and can explicitly try database-managed cookies first and WHISPER_YTDLP_COOKIES_FILE as a later fallback phase on the same job before finally failing.
  • Whisper's stored YouTube n challenge hints now explain more clearly that this is YouTube's obfuscated player check and that yt-dlp may need a working JavaScript runtime such as Node.js to evaluate it, depending on the current yt-dlp extractor path.
  • Whisper transcript analysis now retries once without reasoning_effort when the active OpenAI chat model rejects that argument, so /api/whisper/jobs/{jobId}/analyze and the web detail view no longer fail immediately on models/providers that do not expose reasoning controls.
  • Whisper now does one built-in automatic yt-dlp fallback retry for known YouTube extractor failures such as n challenge solving failed, Only images are available for download, and Requested format is not available, using a safer player-client combination plus an audio-first format selector before the job is finally marked failed.
  • Whisper jobs now expose additive liveness heartbeat metadata (state, summary, heartbeat_at, last_output_at, runner_id, current_step, stale_after_seconds, is_stale) in the web/API queue payloads, so operators can tell whether a downloading / transcribing / finalizing row is still alive, only quiet, or likely crashed.
  • The Whisper runner now also reclaims stale active rows automatically on later passes: if an old processing job has gone too long without a runner heartbeat, it is requeued or failed with an explicit stale-job error instead of silently blocking the queue forever.
  • Whisper jobs that get stuck in downloading / transcribing / finalizing can now be cancelled safely from the web/API surfaces before deletion. The worker observes a stored cancel request, stops the active yt-dlp/Whisper process cooperatively, marks the row as stopped instead of retrying it, and only then allows ordinary delete/restart handling.
  • Whisper transcript analysis is now hardened against array-shaped OpenAI content, so detail views and stored job metadata no longer blow up with Array to string conversion when OpenAI returns structured content blocks instead of one plain string. The admin cookie box can now also append/merge newly pasted Netscape cookie rows into the stored yt-dlp jar instead of only replacing it.
  • Whisper now detects YouTube-style yt-dlp n challenge solving failed / missing-JavaScript-runtime warnings more explicitly, stores a clearer operator hint in the live queue progress, and supports an optional WHISPER_YTDLP_EXTRA_ARGS host override for stubborn YouTube player-client extractor issues after yt-dlp itself has been updated.
  • The Whisper web UIs now render grouped live job cards instead of the earlier wide table, delete/restart actions now work over AJAX, and every job now has its own detail page where operators can follow live progress, read the full transcript, inspect a longer runtime log, and rerun an OpenAI transcript summary.
  • Whisper admin now has a dedicated yt-dlp cookie box: operators can paste a Netscape-format cookie export on /admin/whisper, the queue stores it securely, yt-dlp uses a temporary cookie file during supported YouTube/login-bound downloads, and GET /api/whisper/status now exposes additive config.ytdlp_cookies metadata.
  • php artisan whisper:process is now self-locking even outside Laravel scheduler, so a minute-wise cron run no longer steals work while another Whisper run is still active. The command now logs to storage/logs/whisper-queue.log by default instead of depending on terminal output, and busy lock collisions are recorded as warnings instead of starting a second runner.
  • Whisper now prefers the highest-quality default model again: WHISPER_DEFAULT_MODEL falls back to large, the web UI defaults to that best-quality choice, and the API now also exposes POST /api/whisper/jobs/{jobId}/analyze plus additive analysis metadata on job payloads for on-demand transcript summaries.
  • Password Manager now has a first public authenticated API contract under /api/password-manager/*, covering entry list/read/create/update/delete for the logged-in owner's own encrypted vault rows.
  • That first Password Manager API currently accepts either the normal logged-in web session or JWT bearer auth from /api/account/login, returns masked list rows by default, only exposes decrypted secret_payload on explicit detail/create/update responses, and marks responses Cache-Control: no-store, private.
  • The X bot now does an extra direct-answer repair pass when a generated draft still opens with detached paraphrase such as The message expresses... or The post suggests..., so ordinary ask/explain replies are pushed back toward a concrete answer instead of narrating what the post said.
  • When the X bot posts a threaded reply while Prefix replies with @author is still off, Tools now also asks X to exclude the author's visible reply mention from the live post itself, so threaded replies can stay in-thread without automatically showing an unnecessary leading @username mention.
  • API audit Slack forwarding now treats ordinary 4xx auth/client failures as WARNING instead of ERROR, adds clearer suffixes such as 401 (authentication rejected), and reports authorization_header_present / bearer_token_present explicitly instead of the earlier ambiguous blank Has bearer token field.
  • The X bot now reliably honors the Prefix replies with @author toggle again: saving the admin/API config finally persists reply_prefix_with_author correctly, so visible @username tagging stays off unless an operator explicitly enables it.
  • The Whisper queue now persists operator-facing runtime progress details such as stage_label, stage_detail, and a short runtime_log tail, so the user/admin queue views and CLI runner output can finally show what yt-dlp or Whisper was doing instead of only ending on a generic failure sentence.
  • Tools bootstrap/runtime now also understands PHP_DEPRECATED_NOTICES=0 as a direct way to mute PHP deprecation noise on newer runtimes, while still honoring the older PHP_SUPPRESS_DEPRECATIONS compatibility flag.
  • A first logged-in-only Password Manager slice now exists under /password-manager, with encrypted per-user vault entries for logins, secure notes, and a cautious first payment-card mode. No extra permission is required, sharing is not implemented yet, and CVV/CVC storage is intentionally blocked.
  • Old Whisper jobs can now be deleted from the user/admin queue views and through DELETE /api/whisper/jobs/{jobId}. This is especially meant for failed jobs that no longer matter, and the backend now removes the job row together with its stored Whisper project directory plus safe local upload/transcript files while refusing deletes for actively processing jobs.
  • The Whisper queue now uses yt-dlp for YouTube and other explicitly supported page/video URLs instead of trying to transcribe downloaded HTML, direct-media HTTP downloads now reject obvious non-media Content-Type values before saving input.bin, the Python -m whisper path is tried before the plain whisper binary, successful-but-empty Whisper runs now report the expected TXT path plus stdout/stderr/files-found diagnostics, and GET /api/whisper/status can now expose additive config.ytdlp_configured metadata.
  • The Whisper queue can now reset and restart failed/exhausted jobs more cleanly: php artisan whisper:process now supports --restart-failed and --job-id=<id>, the API now exposes POST /api/whisper/jobs/{jobId}/restart, admin/API run-now calls can now also take reset_failed=true, failed-job state is reset properly before retry, and stored execution errors now include a clearer installation hint when the host is missing a working Whisper binary/module. The built-in default-model fallback is now turbo instead of small for new environments, although an explicit WHISPER_DEFAULT_MODEL setting still wins.
  • The X bot now preserves the richer live X API tweet row when the same post also exists in the local conversation archive, so attached images/media are no longer lost behind a duplicate local copy that only carried the internal .../photo/1 attachment URL. Internal X/Twitter attachment URLs like /status/.../photo/1 and /video/1 are also no longer treated as ordinary linked-page context when real tweet media is already attached.
  • The X bot now treats direct answers as an even harder top-priority rule: outside explicit summarize mode it should not open with detached third-person recap lines such as the commenter believes, the post says, or the tweet criticizes, and post-processing now strips those meta-summary lead-ins when they still slip through.
  • The X bot now recognizes X's Reply to this conversation is not allowed because you have not been mentioned or otherwise engaged... failure as a thread-engagement restriction: keyword-only public-post replies stay manual-only by default, reprocess can still run, and the stored error text now explains that the bot account was never explicitly pulled into that conversation.
  • The X bot admin interaction list now falls back to the configured local reply cap (260 by default) when older rows are missing stored length metadata, instead of misleadingly showing 280 for those legacy rows.
  • The X bot's image/public-figure guidance is now less over-cautious: when the thread already names or clearly discusses a public figure, the prompt tells AI to discuss that public figure contextually instead of defaulting to generic I cannot identify individuals refusals.
  • The X bot keyword-rule panel now saves Add rule / Save rule / Delete rule over AJAX like the rest of the admin page instead of forcing full-page reloads for every reply-priority profile change.
  • The X bot now treats @author prefixing as optional compatibility behavior instead of a reply-threading requirement, because live posting already threads through X's in_reply_to_tweet_id payload. New/default bot settings now leave that prefix off unless an operator explicitly enables it.
  • The X bot now tries a short AI rewrite loop when a generated candidate reply is still too long for the effective reply cap (local Tools cap vs X hard limit), so it can shrink the wording before falling back to a last-resort hard trim with .
  • The X bot admin interaction list now shows the stored candidate reply character count together with the effective reply cap, and it keeps the latest stored posting error visible in the row so operators can see whether a failed Approve/post attempt was really a length issue or something else.
  • When X returns the exact error You are not permitted to perform this action., Tools now explains that this is normally an X-side write/reply permission problem for the app or bot token pair, not a character-length problem.
  • The X bot now treats “go straight to the answer” as a top-priority override across every mode: replies should start with the concrete answer or verdict immediately, skip preambles/scene-setting, and avoid bullet-point or numbered mini-summaries so the output fits tighter X/Twitter reply space.
  • The X bot now treats a bare bot-tag mention as an implicit “read the prior thread and comment on it” request, instead of acting as if the tag itself were the whole message when earlier thread context is available.
  • The X bot now accepts max_context_posts up to 50, and max_reply_length=0 now means “no local Tools character cap” instead of failing validation or forcing the older trim range.
  • The X bot now treats explicit short-answer requests such as Kortfattat!, briefly, short answer, and one sentence as a stronger concise-intent signal: the prompt tightens brevity rules, the AI token budget is lowered, and the generated reply is sentence-trimmed before final posting.
  • The X bot now records explicit webhook_crc_failed / webhook_receive_failed runtime events when CRC verification or signed delivery handling crashes, stores the latest webhook error in runtime settings, and can forward those webhook failures to Slack through audit_x_bot even outside the normal manual-review flow.
  • The X bot prompt now treats earlier supplied thread rows as implicitly relevant by default, and it is explicitly instructed not to answer as if earlier discussion is unavailable when prior context is already present in the prompt.
  • The X bot now treats directed-account target usernames as a denylist instead of an allowlist: operators can keep a short “must not approach” list for directed-account mode, while ordinary public replies still remain allowed unless another safety rule blocks them. The same config now also supports trusted admin X usernames that stay authorized for directed-account commands even outside the normal delegate list.
  • The X bot admin page now sends its inline config saves and interaction moderation AJAX calls through the authenticated /admin/social-media-tools/x-bot* web routes instead of depending on the /api/social-media-tools/x-bot/* session path, which fixes the lingering 401 Unauthorized failures on blur/change saves for logged-in operators while still keeping the public API endpoints intact.
  • The X bot admin/diagnostics, polling, webhook ingest, and linked-page/media fetch paths now catch transport-level X/HTTP/SSL failures and return structured errors instead of letting those network exceptions bubble into raw 500s.
  • The X bot now also preserves normalized external link metadata from visible thread posts, fetches a few linked pages server-side when link handling is enabled, and feeds compact title/excerpt summaries into the AI context so replies can react to “read the article” style follow-ups against actual fetched page text.
  • The same X bot now prefers embedding forwarded images as inline data URLs in the OpenAI multimodal request when the server can fetch them safely, while still falling back to ordinary remote image URLs when inline fetch is unavailable or too large.
  • The X bot admin page now forces its inline AJAX saves and moderation actions through same-origin relative URLs, which fixes the earlier .com/.net style host drift that could trigger 419 CSRF token mismatch failures and surface a raw [object Object] error instead of useful feedback.
  • The same X bot now also enforces max_reply_length hard not only when a fresh AI candidate reply is first generated, but again when a candidate reply is persisted/finalized and immediately before it is posted to X, so older stored candidates cannot slip past a newly lowered length limit.
  • The X bot can now send its thread context to OpenAI either as legacy flat text or as a nested structured JSON payload, and the new default is the JSON form so incoming post data, mode metadata, reply constraints, and context rows stay clearer for the model.
  • When X exposes usable media URLs, the same X bot can now also forward those image URLs to OpenAI as multimodal image_url inputs instead of relying only on media summaries / alt text, while still letting operators disable that forwarding explicitly from config.
  • The same X bot now also preserves the opening thread anchor together with the latest turns when a conversation is longer than the configured context window, and the prompt explicitly tells the model to resolve short follow-ups like “Ping”, “Try again”, or “What did we talk about?” against earlier visible thread context instead of treating them as isolated one-liners.
  • The X bot now also keeps a local per-conversation history archive inside interaction metadata, so older discussion content and previously generated bot replies can still be reused even if parts of the remote X thread are later deleted or become unavailable.
  • Operators can now configure historical_context_max_posts separately from the much smaller prompt window, including 0 for effectively unlimited local archive depth while max_context_posts still controls how much is sent to OpenAI in one request.
  • A new public /docs/terms-of-service page now exists for the full Tools platform, covering website use, APIs, AI-assisted workflows, automation features, credentials, third-party providers, and acceptable use.
  • Tools now also exposes a public X callback placeholder at /oauth/x/callback plus /oauth/x/callback-url, so there is a concrete callback URL available for X app registration even though the current X bot still uses static server-side X credentials instead of a browser-based OAuth completion flow.
  • The X configuration now also has reserved space for future OAuth 2.0 client credentials so operators can store those values cleanly without confusing them with the current posting credentials.
  • The X bot diagnostics/admin panel now also mirrors OAuth2 capability metadata more explicitly, including configured scopes and whether OAuth2 client/access/refresh token values are present, so the UI can reflect what the X dashboard currently offers without pretending that the posting path has switched away from OAuth1.
  • The X bot now survives older environments where the new x_bot_rules table has not been migrated yet: the page no longer crashes, the rule panel explains that php artisan migrate is still needed, and fallback instruction/mood keep working until the table exists.
  • Manual shell/admin polling can now bypass the stored local interval gate through php artisan x-bot:poll-mentions --now (and the existing run-x-bot* wrappers), while the admin Poll mentions now action now also ignores that interval and immediately runs the due auto-approve cycle.
  • The X bot's local reply caps are now clearly labeled as Tools-side safety limits, and admin-owned bots now bypass those local caps entirely so only real remote X rate limits remain.
  • The X bot settings page now explains fields such as Max context posts, poll interval override behavior, list-clearing for AI account/target textareas, and the difference between Manual approval required = No and the separate randomized auto-approve delay toggle.
  • The X bot now also exposes a first public webhook ingest endpoint at /api/x-bot/webhook, with CRC verification on GET, signed delivery handling on POST, queue-based processing, and admin-visible webhook diagnostics such as last received/processed/error state.
  • The same X bot now stores last-seen remote X read/write rate-limit headers and surfaces them in the admin panel plus config/status payloads, so polling and reply posting can pause automatically until the reset window passes instead of blindly retrying too early.
  • The X bot admin page now also saves core config fields inline over AJAX, runs reprocess/approve/skip moderation actions without full page reloads, and lets operators truly clear textarea-based account/allowlist lists such as known AI accounts and directed targets.
  • The same X bot can now also poll public X posts that match configured watch keywords such as Tornevall when Only explicit @mentions is turned off, and those keyword-triggered posts go through the same helpfulness-/priority-based routing as direct mentions.
  • The X bot now also walks backwards through reply chains, carries media metadata/alt text into the AI context when X exposes it, normalizes IF-keyword punctuation, and can identify itself as a configurable ToolsGPT-vX.Y.Z style version label when users explicitly ask.

2026-04-27

Whisper - yt-dlp-backed page/video downloads now work in the transcription queue

  • URL-based Whisper jobs now distinguish between direct media files and supported page/video URLs such as YouTube instead of trying to fetch everything through a plain HTTP GET.
  • YouTube-style jobs are now downloaded/extracted through yt-dlp into the job directory before Whisper starts, so media_path points at the real extracted audio file instead of a saved HTML page.
  • Uploaded files are unchanged and still bypass yt-dlp completely; they continue to use the stored upload path directly.
  • Direct HTTP downloads are now also hardened so obvious web-page responses (text/html, JSON, and other non-media content types) fail with a clear error before input.bin is written.
  • When Whisper exits successfully but still does not leave a .txt transcript file, the stored error text now includes the expected transcript path, the output directory, the files found there, and the captured stdout/stderr from the successful run.
  • Queue/status config payloads can now also expose additive ytdlp_configured=true|false, making it easier for clients and operators to tell whether the host is expected to handle YouTube-like URLs.

vBulletin admin - first onboarding/review/group-management slice added on top of the existing security console

  • The vBulletin onboarding flow is now also exposed through its own separate, styled public page under /vbulletin/onboarding, so people no longer need to discover it indirectly through the embedded admin vBulletin area.
  • That new public onboarding hub can now be linked from the front page and Services page, explains the onboarding process in normal user-facing language, and lets users paste either a full onboarding URL or only the token before continuing to the actual form.
  • /admin/security/vbulletin now includes a first real onboarding/admin layer in addition to the earlier failed-login cleaner and frontend script-box area.
  • Tools can now store onboarding configurations with intro text, rules, allowed vBulletin group IDs, referral requirements, manual-review flags, and optional auto-approve behavior.
  • Admins/operators with vbulletin.manage can now create referral or one-time onboarding keys, and each created key gets a public onboarding URL under /vbulletin/onboarding/{token}.
  • The public onboarding flow now stores one request/status page per applicant, and the per-request status page is available under /vbulletin/onboarding/status/{statusToken}.
  • The same admin page now includes a review queue, manual forum-user linking by forum_user_id, and limited approve/reject/revoke flows that add or remove only the configured secondary vBulletin groups.
  • There is now also an AJAX-searchable vBulletin user lookup in the admin page together with scoped grant/revoke actions for the selected onboarding configuration.
  • Tools now runs php artisan vbulletin:onboarding-sync every five minutes so exact email/username matches can link forum accounts automatically and optionally auto-approve when the configuration allows it.
  • Welcome/info banner messages for vBulletin can now be managed in Tools and rendered through /vbulletin/onboarding/messages.js, intended for direct vBulletin template/snippet use or for embedding through the existing vBulletin managed-script surface.

Whisper - transcription jobs can now accept uploaded files in addition to URLs

  • The Whisper queue now accepts uploaded audio/video files as a second input mode instead of requiring every job to point at a public media URL.
  • POST /api/whisper/jobs now supports both the older source_url JSON/form flow and multipart/form-data uploads through media_file, while still rejecting requests that try to send both at the same time.
  • The /whisper user page now includes an upload field next to the URL field, so local recordings and other non-public files can be queued directly from the browser.
  • Whisper job payloads and polling responses now expose additive source metadata such as source_type, source_label, source_mime, and source_size_bytes, making it clear whether a job came from a URL or an uploaded file.
  • Admin queue views under /admin/whisper now render uploaded filenames and MIME information instead of assuming the source column always contains a URL.

ACME / Let's Encrypt DNS flow - safer console consolidation plus DNS-side helper endpoints

  • Tools now exposes dedicated DNS-side ACME helper endpoints under /api/dns/acme/* so console tooling can present, clean up, and explicitly stale-clean _acme-challenge TXT rows without pretending that a separate legacy ACME subsystem still exists.
  • The new endpoints are POST /api/dns/acme/present, POST /api/dns/acme/cleanup, and POST /api/dns/acme/cleanup-stale, and they reuse the same /api/dns/* auth and zone-permission model as the normal DNS record writes.
  • cleanup-stale now supports a keep-list plus dry-run/cache-refresh flags, which gives operators a controlled way to remove older _acme-challenge TXT rows while preserving the currently active challenge values.
  • Tools also now includes an Artisan helper php artisan dns:acme:cleanup-stale <domain> for the same one-owner stale-cleanup flow, so shell/cron-driven maintenance does not need to guess at raw nsupdate commands.
  • The public DNS example script and DNS docs were cleaned up at the same time so they no longer point at the older non-existent /api/acme/challenge/* routes.

Frontend script boxes - reusable vBulletin and feed widget bundles with AJAX autosave + public API delivery

  • Tools now includes shared Frontend Script Boxes for both /admin/security/vbulletin and /feed-admin, so operators can keep small frontend JavaScript customizations in one place instead of scattering them across ad-hoc snippets.
  • Each script box now supports a title, optional external script src, AI instructions, inline JavaScript body, enabled/disabled state, and sort order.
  • The vBulletin admin page now has a dedicated vBulletin frontend script boxes section for forum/htmlsnippet customizations such as frontend tweaks and reusable helper scripts.
  • /feed-admin now also has a Feed script snippets / embeddable widgets section for /feed-related widgets and external site news boxes.
  • Both admin sections now work over AJAX: Add script creates a new box immediately, field edits autosave on blur/change, Save now forces a save, AI → script can generate the inline script body from the saved AI instruction, and Delete removes the box without a full page reload.
  • New public API endpoints now expose the active script boxes per surface as either separated rows or one merged bundle: GET /api/managed-scripts/{surface} and GET /api/managed-scripts/{surface}/bundle.js for vbulletin and feed.
  • The merged bundle now loads external script src rows, executes inline snippets in configured order, and guards against duplicate inline/external initialization on the same page load.

2026-04-25

X mention bot - first dry-run/admin/API foundation added for explicit mention handling

  • Tools now includes a first X mention-bot foundation under Social Media Tools, with a dedicated admin page for bot status, polling, dry-run candidate replies, opt-outs, and diagnostics.
  • The same X bot panel now also supports fallback custom instruction + mood settings plus keyword-based reply-priority profiles, so incoming mentions can be routed differently depending on which answer direction is most helpful for the words or phrases in the mention text.
  • The X bot can now also watch configurable public trigger words such as Tornevall outside direct mentions when Only explicit @mentions is set to No, and those posts are stored/reviewed through the normal interaction queue.
  • X bot polling now walks multiple X result pages per cycle instead of only the first page, which reduces the chance that bursty mention traffic is only partly ingested and then silently skipped.
  • Operators can now enable delayed auto-approval for queued X replies with randomized min/max second windows, so cron-driven answers no longer need to fire on the exact same second for every run.
  • While manual approval is still required, the X bot can now also notify operators by mail and/or Slack for real runtime events such as newly ingested mentions, queued review candidates, posting attempts, and auto-approve cycles.
  • The Services page, welcome shortcuts, and authenticated dashboard now also expose a dedicated X Mention Bot entry so operators do not have to discover it only through the broader Social Media Tools hub.
  • The X bot admin page itself is now styled as a real operator dashboard instead of a near-unformatted Bootstrap-like form dump, with clearer status cards, grouped configuration panels, readable diagnostics, and denser moderation/event tables.
  • The same X bot admin page now also explains the read-vs-write credential split more explicitly, so operators can immediately see when mentions can be ingested but replies still cannot be posted because the required X-side user-context write credentials are missing.
  • Core X bot config fields on that page now save inline over AJAX on blur/change, recent interaction actions like Reprocess / Approve/post / Skip now also run over AJAX, and list textareas can now be emptied cleanly through explicit clear actions.
  • The X bot docs and diagnostics wording now also state more explicitly that those missing write credentials belong to the X-side bot account itself, not to Tools API keys or internal Tools tokens.
  • The same diagnostics/event wording now also explains the next common posting blocker more clearly: if X returns an oauth1 app-permissions error even after tokens are configured, the X developer app itself likely still needs Read and write permission and the user token pair usually needs to be regenerated after that permission change.
  • The event log on that page is now more human-readable as well, with summarized diagnostic/posting/polling outcomes plus expandable raw payloads instead of only large raw JSON blobs.
  • X bot AI defaults now target gpt-5.4 with reasoning enabled by default and an automatic fallback to gpt-4o-mini when the primary model fails, rejects the reasoning flag, or returns an empty reply.
  • The X bot admin page now also keeps a persistent event log so operators can review what happened during diagnostics, polling, processing, opt-out actions, and posting attempts instead of relying only on the latest screen state.
  • The backend can now store one primary bot account plus idempotent mention/interactions/opt-out rows, poll new mentions from X, sanitize visible thread context, and generate candidate AI replies without forcing immediate posting.
  • Thread-context handling is now stronger as well: the bot now walks backwards through reply chains, sorts the visible prompt context oldest-first, and lets keyword-based reply-priority profiles match against that pulled-in thread history instead of only the newest post text.
  • X tweet lookups now also include media metadata and alt text when the platform exposes them, so replies can react more honestly to attached images/video while still explicitly saying when raw image details are unavailable.
  • The X bot can now answer explicit “who/version are you?” questions with labels such as ToolsGPT-v0.1.0, where the name comes from the synced X account identity and only the version suffix remains operator-configurable.
  • New ToolsAPI endpoints now expose X bot status/config, diagnostics, recent interactions, one interaction, reprocess/approve/skip actions, and opt-out management for authorized Tools operators.
  • The underlying X-side credentials are still kept server-side while the runtime reply/polling behavior remains controlled from Tools settings.
  • Operators can now also run the bot directly from console through php artisan x-bot:poll-mentions or the new repository-root helper scripts run-x-bot, run-x-bot.sh, and run-x-bot.bat, and the X bot docs now describe the manual --no-dispatch dry ingest mode plus the scheduler path.

Visit statistics - web visitors and API traffic are now split more clearly, with excluded automation still left out

  • The /visit-stats page now presents ordinary web visits and tracked API requests as two clearly separated traffic groups instead of treating them like one mixed usage bucket.
  • API paths that should not be part of visit statistics at all now stay excluded both during collection and during reporting, so older and newer /api/rss/* scraper traffic does not leak back into the visible API totals/trends.
  • The visit statistics UI now explicitly lists the permanently excluded API prefixes so operators can see which automated endpoint families are intentionally not counted.

Firewall - async host-list refresh progress, literal-IP CIDR expansion, and safer scoped full on/off cleanup

  • /firewall/live host-list refreshes now start asynchronously and expose tracked progress/status polling, so the admin page can show visible per-list progress without blocking the whole browser request while DNS and RDAP/network lookups run.
  • Literal IP entries in firewall host block lists now pass through the same RDAP/network-range expansion path as hostname-resolved addresses, which means one pasted service IP can now still become a broader stored CIDR range when registry data exposes it.
  • Scoped child full-off/full-on handling is now aligned per child IP: selected-IP full-off removes matching rule data only for those targeted child IPs before writing the new strong rule, and selected-IP full-on removes only the matching strong/selective rule targets instead of wiping unrelated child-IP rules.
  • fwmaker and the Laravel-side live-rule renderer now both apply explicit strong-rule precedence per targeted child IP, so full_block wins over internet_block, which wins over allow_only_local, regardless of row creation order, while CIDR destinations remain intact in generated output.

Mail Support Assistant - Tools cases now keep remote-readable headers and sync handled/ignored mail more reliably

  • POST /api/mail-support-assistant/cases/sync now also accepts additive headers_raw, headers_map, and body_text_raw, and Tools stores those fields centrally on case messages so remote operators can inspect headers plus raw/plain/HTML body variants instead of only excerpts.
  • The Tools admin case page at /admin/mail-support-assistant/cases/{id} now exposes stored raw headers, parsed header maps, raw plain-text bodies, normalized plain-text bodies, and HTML bodies under the case-message details view.
  • The standalone runner now still attempts Tools case sync for handled/ignored/manual outcomes even when the message lacks a stable local message-state key, which fixes cases where mail looked processed locally but never appeared in the remote Tools thread view.

2026-04-24

Firewall - live child-control tables, Tools admin editor, API, and fwmaker output

  • Tools now has a dedicated live child-control layer under /firewall/live, separate from the older generic firewall table editor, where operators can manage children, their IP addresses, host block lists, resolved block targets, and live rules from one place.
  • The child screen in that editor now also includes a child-centric multi-select host-list box, so selective internet blocking can be saved directly as “which host lists should this child be blocked from?” instead of relying only on generic enable/disable handling on rule rows.
  • Tools now exposes a first /api/firewall/* surface for this feature, including child listing/update, child IP management, host block list management, live rule management, quick child block/unblock actions, and a sync endpoint for firewall regeneration.
  • GET /api/firewall/children can now also include additive selected_host_block_list_ids, and Tools now accepts PUT /api/firewall/children/{child}/host-block-lists so clients can save that child-centric selective block selection directly.
  • Host block list refreshes now also do a best-effort RDAP/network lookup for hostname-derived IPs, so Tools can store CIDR-style service ranges when the registry response exposes them instead of only one resolved host address.
  • Quick child block actions and selective host-list sync can now target either all child IPs or only selected child IPs, and the firewall API now accepts additive target_scope plus child_ip_address_ids[] for those flows.
  • GET /api/firewall/children and GET /api/firewall/live-child-rules can now return additive target metadata such as selected_host_block_list_target_scope, selected_host_block_list_child_ip_address_ids, target_child_ip_address_ids, target_ip_addresses, and target_scope_summary so clients can render the exact per-IP scope safely.
  • The same firewall editor now also supports database-backed recurring full off / full on rows with cron expressions, stable child / child-IP schedule_key mappings, and a global scheduled writes enabled/disabled switch so operators can pause schedule writes without deleting the stored rows.
  • A seed set of recurring schedule rows matching the current Max / Emily timing examples is now inserted into the firewall schedule table, ready to resolve once the corresponding child and child-IP schedule keys are assigned in /firewall/live.
  • The new live-control state is now stored in dedicated firewall-database tables (child, child_ip_addresses, host_block_lists, host_block_list_addresses, live_child_rules) instead of trying to overload the legacy firewall rule tables.
  • The live-control schema now also includes live_child_rule_ip_targets, child_schedules, child_schedule_ip_targets, and child_schedule_settings, which let one live rule affect all child IPs or only a chosen subset and let recurring full off / full on rows live in the database instead of only in handwritten system crontab files.
  • fwmaker now reads that live-control state and generates a dedicated child_live chain in the FORWARD path, so strong child blocks and selective host-list blocks can become actual iptables output instead of just database records.
  • The firewall generator was also modernized internally: fwmaker now uses the extracted helper firewall_pdo.php instead of the older TorneDB dependency, its legacy template placeholders were normalized for newer PHP runtimes, the active bitcom generation path was removed, runfwmaker now falls back across installed PHP binaries instead of hard-failing only on missing php7.2, and the wrapper now suppresses stale CLI mapi.so startup warnings by using an empty PHP scan dir when pdo_mysql is still available.

DNSBL whitelist visibility - new public statistics page and local-network purge workflow

  • Tools now exposes a new public page at /dnsbl/statistics, where visitors/operators can see the DNSBL API counters together with the active whitelist rows loaded from DNSBL_V5.ipwhitelist.
  • The whitelist section now shows row descriptions when present, and rows flagged with is_local_network are called out as blacklist-exempt local-network cleanup entries rather than ordinary test-only whitelist rows.
  • /admin/dnsbl/engine-settings now also shows the same whitelist/local-network overview and includes a dedicated Purge local networks now action that removes currently listed blacklist owners whose decoded IP matches those active local-network IP/CIDR rows.
  • The older RFC1918-only hygiene purge remains available separately, so operators can still clean up both hard-coded private ranges and table-driven local-network exceptions.

DNSBL API - new stats endpoint for query/add/remove counters

  • Tools now exposes GET /api/dnsbl/stats, which returns API-readable counters for /api/dnsbl/* traffic plus database-backed logical DNSBL add/delete/update outcomes.
  • The DNSBL admin engine-settings page now shows the same counters directly in the DNSBL section, so operators can quickly see how many additions, removals, dry-runs, and already-not-listed delete no-ops have been recorded.
  • Existing DNSBL operator/audit logs were already useful for manual troubleshooting, but they were not a stable API-readable counter source for POST add/delete activity; the new stats persistence is the new canonical counter surface going forward.

DNS and DNSBL docs - safer public rendering and clearer API examples

  • The public DNS API guide (/docs/dns-api) was rewritten to remove broken markdown/example blocks, align the examples with the live /api/dns/* endpoints, and explain the current auth, cache, search, and record-write flows more clearly.
  • The public DNSBL / FraudBL guide (/docs/dnsbl-api) now includes a clearer quick-start section plus practical token, inspection, add/update, delete, and dry-run guidance for current ToolsAPI integrations.
  • The docs page renderer now wraps long inline code, URLs, tables, and code blocks more safely so large API examples are less likely to break the page layout when viewed in the browser.

2026-04-23

DNSBL engine - delist runs now unpack RESEND wrapper mail without auto-processing restored mail

  • dnsbl-engine delist/reply runs now also scan a dedicated RESEND spool before normal reply handling and unpack attached/original .eml mail such as ForwardedMessage.eml, so the outer SpamAssassin/Thunderbird wrapper text no longer becomes the thing operators keep seeing/retrying.
  • Restored mail files now get stable message-id-based filenames when possible, which makes repeated operator resend/retry flows easier to follow in the spool.
  • RESEND is now unpack-only: those restored/original mails are left in the RESEND spool and are not fed into the same delist/reply run automatically.
  • When RESEND is a Maildir path and the runner scans cur/, restored mail is now written back into sibling new/ so mail clients can show the unpacked original as a separate message.
  • Non-mail attachments from those wrapper mails are still preserved under RESEND/__attachments/ for operator inspection, while the successfully unpacked wrapper itself is cleaned away afterward.
  • Delist mode now also logs more explicitly that the separate FORUM bounce segment still runs together with delist/reply handling, so forum-recipient bounce processing remains part of the same operator pass.

2026-04-21

Mail Support Assistant - centralized case history now keeps full mail bodies, and standalone can bypass broken local TLS verification

  • POST /api/mail-support-assistant/cases/sync now accepts additive full-body fields (body_text, body_text_reply_aware, body_html, reply_body_text, reply_body_html), and the Tools admin case page now shows that centrally stored content instead of relying only on short excerpts.
  • Synced case entries now also carry source-instance/source-host metadata, which makes it clearer in Tools admin which standalone runner/server processed or replied to a message.
  • The standalone runner now refreshes one stable local message copy for every scanned mail, so the dashboard/manual handling flow is much less likely to look like the message body disappeared between runs.
  • Standalone now also has explicit TLS override env flags for both Tools API calls and direct SMTP (MAIL_ASSISTANT_TOOLS_SSL_VERIFY, MAIL_ASSISTANT_TOOLS_CA_BUNDLE, MAIL_ASSISTANT_SMTP_SSL_VERIFY, MAIL_ASSISTANT_SMTP_CA_FILE), which helps unblock WSL/self-signed certificate setups when local CA trust is broken.

RSS Watch - public /feed now links directly to support for questions, complaints, and corrections

  • The public RSS page now shows a dedicated support/contact block next to the existing reader/disclaimer area.
  • Visitors are now pointed both to the normal contact page and directly to support@tornevall.net, so feed-related questions, complaints, and correction requests can go straight into the support flow.

Mail Support Assistant - Tools now stores threaded support cases and standalone can report unanswered mail

  • The standalone Mail Support Assistant can now sync processed inbox outcomes back into Tools as threaded support cases, which makes conversations visible from /admin/mail-support-assistant instead of only in one local latest-run summary file.
  • Tools now exposes GET /api/mail-support-assistant/cases plus POST /api/mail-support-assistant/cases/sync so the standalone client can fetch recent stored cases and upsert one thread snapshot back into Tools.
  • Standalone dashboard activity can now merge a live unread IMAP preview, so unread support mail can appear in the assistant web GUI even before another saved run exists.
  • Outgoing assistant replies can now append a public Tools case-tracking link for the recipient when that case could be prepared before delivery.
  • Standalone can now also send one operator report mail summarizing which messages were not answered during the run when MAIL_ASSISTANT_UNANSWERED_REPORT_ENABLED is enabled.

DNSBL / forum admin - bounced forum recipients can now be grouped automatically from a dedicated FORUM spool/API flow

  • dnsbl-engine now has a dedicated FORUM spool segment for bounce / mailer-daemon mail that is separate from ordinary DNSBL add/delist handling, similar to the earlier dedicated DMARC segment.
  • Tools now exposes POST /api/dnsbl/forum/bounce, where rejected recipient addresses from those bounce mails can be matched against vBulletin users and moved into a configurable bounced-email forum group as a secondary group.
  • The current bounced-email group ID and exempt forum group IDs are now configurable from the Tools vBulletin admin page, so admin/operator groups can stay excluded from that automatic grouping flow.
  • Tools also now exposes GET /api/dnsbl/engine-settings, which returns the normalized runtime settings used by dnsbl-engine, including the forum-bounce enable flag and group configuration.

Mail Support Assistant - standalone dashboard now supports manual handling, quota alerts, and terminal no-match stop conditions

  • The standalone Mail Support Assistant dashboard now behaves more like a lightweight operator mail client: latest-run message cards can assign a local rule context, send a manual reply, or mark a message handled/read without waiting for another cron run.
  • Manual operator replies now reuse the same plain-text + styled HTML reply pipeline as automatic replies, so manually sent support answers keep the same visible formatting and appended request-summary behavior.
  • AI quota / billing failures are now promoted into explicit runtime alerts in the standalone dashboard, and standalone can optionally send cooldown-limited operator alert mail when MAIL_ASSISTANT_QUOTA_ALERT_EMAIL is configured.
  • When the strict unmatched AI/final-fallback path is actually evaluated but still ends in a terminal reject/error outcome, the standalone runner now marks that message read for manual follow-up so the unread poller does not keep retrying the same no-match AI path forever.

Mail Support Assistant - standalone runs are now overlap-safe and reuse one stable subject issue id

  • Standalone CLI/dry-run executions now acquire a local run lock before polling unread mail, so overlapping cron/dashboard invocations are skipped cleanly instead of double-processing the same assistant instance.
  • The overlap path now reports a structured runner_already_active conflict together with lock-holder metadata, which makes operator troubleshooting easier when a previous run is still active.
  • cron-run.sh now also keeps its own PID-aware shell lock with stale-lock cleanup, so overlapping cron invocations can be rejected before the PHP runner even starts.
  • Outgoing standalone replies can now stamp one stable issue/case tag into the subject (default style like [Ärende MSA-ABC12345]), and later replies in the same conversation reuse that same tag instead of appending a fresh issue id every time.

Mail Support Assistant - styled HTML replies now render markdown instead of exposing raw markdown markers

  • The standalone assistant's styled HTML reply body now converts ordinary markdown from AI/operator reply text into real HTML structure such as headings, lists, links, emphasis, and inline code.
  • The plain-text reply part is still preserved for compatibility, but HTML-capable mail clients now see formatted content instead of raw markdown syntax like **bold** or - list items.

OpenAI / SocialGPT auditing - Slack audit entries now include actor, IP, and readable error reasons

  • Operator-facing Slack audit entries for SocialGPT/OpenAI requests now include the resolved user identity (name/email when available), request IP, and a readable error_reason when an upstream/provider request fails.
  • Social media audit summaries now also show the actor and IP directly in the audit headline text so it is easier to see who triggered the request that produced a reply or error.

SocialGPT reply endpoint - structured provider errors no longer break fallback handling

  • POST /api/ai/socialgpt/respond now safely normalizes structured upstream OpenAI/provider error payloads into plain error text before retry/fallback handling runs.
  • This prevents repeated Array to string conversion failures when the provider returns nested JSON error objects instead of a flat string, while keeping the existing fallback retry flow intact.

Mail Support Assistant - strict Tools-configured last fallback for unmatched mail

  • /admin/mail-support-assistant now exposes a stricter mailbox-level last unmatched fallback with an explicit enable checkbox plus dedicated allow-condition and final-instructions text fields.
  • That fallback now only activates when the mailbox checkbox is explicitly enabled in Tools config; environment-only toggles no longer turn it on.
  • Advanced unmatched IF rows are still supported, but they are now evaluated before the mailbox's own last fallback.
  • When that strict unmatched fallback actually sends a reply, the standalone runner now marks the message as read immediately so it is not handled again on the next unread poll.
  • The mailbox form now also submits an explicit false value when that checkbox is unchecked, and the standalone runner refuses to evaluate any unmatched AI row at all when Tools says the checkbox is off, even if older unmatched text fields still contain values.
  • Unmatched AI requests now also include source metadata (advanced_row_rule vs mailbox_final_fallback) in their request context so upstream SocialGPT/OpenAI audit logs are easier to map back to the real unmatched fallback path.

Mail Support Assistant - HTML/non-UTF8 mail bodies now contribute to matching and AI context more reliably

  • The standalone mail client now decodes HTML-only inbound mail into readable plain text before rule matching, unmatched AI triage, appended request summaries, and saved local message copies are built.
  • MIME body decoding is now charset-aware, which improves body readability for non-UTF8 or otherwise malformed incoming support mail instead of falling back to what looks like subject-only handling.
  • The strict unmatched-mail JSON parser now also tolerates a few common sloppy formatting mistakes from the provider/model (for example smart quotes or trailing commas) before it concludes that the AI reply was malformed JSON.

DNS editor - inline update rows now close properly after successful saves

  • The inline Update Record editor in /dns/editor/{zone} now closes immediately again after a successful record update instead of sometimes leaving the expanded edit row visible.
  • The DNS editor now builds that inline edit section as a proper table row, which also makes repeated edit/cancel/save flows more stable.

DNS editor - cached pages now stay visible while AXFR refresh runs in the background

  • When the DNS editor already has cached rows for a zone page, it now renders that cached table immediately and starts AXFR in the background instead of blocking the whole page until AXFR finishes.
  • When only an expired/stale cached page exists, the cache endpoint now still returns that stale page immediately (source="from_database_stale") so the editor can show something useful right away instead of showing only the AXFR spinner.
  • The editor now replaces the visible table only when the completed AXFR result changes the currently displayed page/pagination state.
  • If AXFR completes with the exact same visible result as the cached page, the editor leaves the table untouched to avoid unnecessary redraw flicker.

2026-04-20

DNS editor - cached/AXFR zone loading now strips null bytes before IP normalization

  • The DNS editor and zone-cache load path now sanitize embedded null/control bytes before reverse-owner decoding and inet_pton()-based IP normalization runs.
  • This prevents zone pages such as /dns/editor/tornevall.net from failing with inet_pton(): Argument #1 ($ip) must not contain any null bytes when malformed legacy/cache data is encountered.

SpamAssassin editor - bare whitelist domains now expand back into the legacy wildcard pair

  • In the SpamAssassin whitelist editor, entering only a domain such as openai.com now restores the earlier behavior and adds both *@openai.com and *@*.openai.com.
  • The whitelist remove flow also treats those generated wildcard rows as one legacy domain pair again, so removing either generated openai.com wildcard row clears the companion row too.

OpenAI admin + account registration + API whitelist - admin views are now more stable and readable

  • /admin/openai now uses a safer script-stack rendering path for the model-catalog refresh logic, which prevents the admin page from falling over on the latest OpenAI dashboard updates and keeps the page renderable again.
  • The public registration form at /register now includes an optional Where did you hear about us? field.
  • That registration-source answer is now stored on the user row, included in the new-user notification mail, and shown to admins in /users and /users/{user}/edit alongside the registration IP.
  • /admin/security/api-ip-whitelist has been restyled to match the rest of the Tools admin UI and now includes inline edit forms instead of the previously broken edit button.

DNSBL - private RFC1918 blacklist owners can now be purged from admin and are blocked on future writes

  • /admin/dnsbl/engine-settings now includes a hygiene purge that removes any reverse-owner DNSBL / FraudBL entries that point to private IPv4 ranges in 10.0.0.0/8, 172.16.0.0/12, or 192.168.0.0/16.
  • The DNSBL/FraudBL write layer now also blocks new add/update publication attempts for those RFC1918 IPv4 ranges so private addresses cannot be reintroduced through the normal Tools write paths.
  • DNSBL add/update requests that hit this guard now return 422 with reason="private_ipv4_not_allowed_in_dnsbl".

Mail Support Assistant - standalone dashboard now explains config-only inboxes and shows readable Tools rule rows

  • The standalone Mail Support Assistant dashboard's Refresh dashboard action now uses the supported dashboard reload path again instead of surfacing Unknown ajax action..
  • The activity tab now still lists configured mailbox cards even before any saved dry-run/real run exists, while explicitly clarifying that this is a latest-run operator inbox rather than a full live IMAP mail client.
  • The Tools config tab now shows readable matched-rule rows, fallback-rule details, and unmatched AI/IF row settings (including footer, AI model, and reasoning effort) without forcing operators to rely only on raw JSON.

Browser Automation - Playwright-backed stored scripts can now run from Tools admin and cron

  • Tools now has a new admin-only Browser Automation surface where operators can store Playwright-based browser scripts directly in the database.
  • Scripts can target Chrome / Chromium / Microsoft Edge, keep optional persistent browser profiles for login-heavy sites such as Facebook, and save screenshots / JSON / text artifacts under storage/app/browser-automation/.
  • Operators can run a stored script immediately from the admin UI, invoke it directly with php artisan browser-automation:run <key>, or schedule it through the existing DB-backed scheduled-jobs system with App\Jobs\Handlers\BrowserAutomationScheduledJobHandler.
  • The Laravel scheduler now also executes jobs:run every minute, so cron-based scheduled jobs can run automatically from the normal schedule:run setup instead of depending only on opportunistic web traffic.
  • The create/edit page at /admin/browser-automation/create is now styled as a more guided admin form with clearer runtime sections, a built-in smoke-test example, and sidebar testing guidance for first-run verification.

DNSBL WordPress plugin - public delisting Turnstile can now fail open automatically during operational outages

  • The WordPress DNSBL plugin now has a second removal-page Turnstile checkbox that temporarily bypasses the public delisting/removal challenge when the Turnstile widget cannot initialize or Cloudflare verification itself has an operational failure.
  • This fail-open behavior is limited to the public delisting/removal flow only; comment and WordPress registration Turnstile protection still stay governed by their own settings.
  • A later healthy Turnstile verification closes the temporary bypass again automatically.

DNSBL WordPress plugin - public delisting page Turnstile is now controlled separately

  • The WordPress DNSBL plugin now has a dedicated admin toggle for Cloudflare Turnstile on the public delisting/removal page.
  • The removal page no longer inherits Turnstile automatically just because comment or registration Turnstile is configured.
  • This makes it possible to turn off only the removal-page challenge quickly when challenges.cloudflare.com is unstable, while keeping comment and account-registration protection unchanged.

Mail Support Assistant - reply-chain follow-ups now reuse earlier support path more intelligently

  • The standalone Mail Support Assistant can now reuse the earlier matched rule when a follow-up unread mail is clearly linked to a previously handled conversation through In-Reply-To / References.
  • Follow-ups that were previously handled through unmatched-mail AI can now also prioritize the earlier generic_no_match_rules[] row first instead of always restarting from row 1, which helps shorter repository/API follow-ups stay on the same answer path.
  • Local thread summaries sent into AI context now also include prior selected-rule / matched-unmatched-row metadata, making reply-chain continuity more visible both to operators and to the AI classifier.
  • Standalone-sent replies now also keep a generated outgoing reply_message_id in local state, which improves continuity when later Gmail/Outlook follow-ups reference the assistant's sent mail instead of the original inbound message.
  • If a follow-up arrives in an older or intermediary-rewritten format without usable In-Reply-To / References, the standalone runner can now still try a normalized subject + same participants fallback before treating the mail as a fresh no-match conversation.
  • If an unmatched thread was already approved earlier and a later follow-up is explicitly linked through reply headers, the standalone runner can now continue that same unmatched row directly instead of asking the first allow-condition classifier to approve the same thread again.
  • Per-message standalone run diagnostics now also expose thread_key, in_reply_to, and references[], which makes reply-chain debugging much clearer when mail unexpectedly falls through to no-match.

Mail Support Assistant - standalone dashboard is now more human-readable

  • The standalone Mail Support Assistant dashboard now renders a more mail-client-style operator view instead of mostly raw JSON blocks.
  • Latest-run mailbox activity is now shown as expandable message cards with subject/from/to/date, preview text, selected-rule/no-match diagnostics, and thread metadata.
  • When a local cached message copy exists, the dashboard can also expose saved headers/body preview through expandable diagnostics instead of forcing the operator to inspect raw files manually.

Mail Support Assistant - clear-text contact-form summaries now keep the real issue text

  • The standalone Mail Support Assistant no longer truncates clear-text contact-form mails to only the first From:-style body line when that structure lives inside the message body itself.
  • Request summaries, appended Summary of your request excerpts, and AI context now preserve the actual complaint/problem paragraph plus helper fields such as sender IP when the inbound mail uses that kind of structured clear-text format.

DNSBL / DMARC - DMARC intake now requires an active add-capable DNSBL token

  • POST /api/dnsbl/dmarc/report no longer accepts anonymous/internal uploads, admin-session fallback, or ordinary Tools API-key passthrough.
  • DMARC intake now succeeds only when the caller presents an active DNSBL token whose effective add permission is enabled (can_add=true).
  • Active DNSBL provider keys (provider=tornevall_dnsbl) and active admin-owned Tools API keys are now again accepted for DMARC upload through the same X-Dnsbl-Token / dnsbl_token transport, matching the rest of the DNSBL write auth model.
  • Missing/invalid/inactive DNSBL tokens now fail with 401, while active DNSBL tokens without add scope now fail with 403 reason="insufficient_dnsbl_scope".
  • projects/dnsbl-engine now keeps DMARC spool files in place when upload is denied, and DMARC-only runs now validate that the configured DNSBL token really has add permission before continuing.
  • The DMARC parser now also extracts XML correctly from forwarded/wrapped message/rfc822 mails that carry the real DMARC report as a nested ZIP attachment, which fixes false invalid_dmarc_report failures for those inbound mail wrappers.
  • Duplicate DMARC uploads are now idempotent even when two identical payloads race each other into the same unique payload_hash; the endpoint now returns the already stored report with duplicate: true instead of surfacing a raw SQL duplicate-entry failure.
  • The DMARC parser now also accepts notice-style / forensic DMARC mails that summarize Sender Domain, Sender IP Address, SPF Alignment, DKIM Alignment, and DMARC Results plus copied headers, so those reports can land in the same admin review queue even when aggregate XML is absent.
  • The DMARC admin review queue now also resolves reverse-DNS hostnames for reported source IPs and shows the report kind (Aggregate XML vs Forensic notice) to make triage easier for operators.

Mail Support Assistant - unmatched IF rows can now be deleted in-place

  • /admin/mail-support-assistant now removes unmatched fallback IF rows through AJAX without forcing a full page reload.
  • The unmatched-row empty-state is now restored immediately when the last row is deleted.
  • The same unmatched IF rows now also save in place through AJAX with row-local busy/success feedback under the save button instead of relying only on page-level status.

Mail Support Assistant - requirements and support path are now documented more explicitly

  • The Mail Support Assistant docs now spell out the practical prerequisites for a working installation, including a real Tools account, access to /admin/mail-support-assistant, a valid personal Tools token, mailbox configuration in Tools, reachable Tools base URL, and a configured outbound transport.
  • The docs now also point operators to the standalone client's GitHub tickets page for bugs, setup questions, and feature requests.

2026-04-19

DNSBL - delist/removal audits now include caller source/site context and configurable mail delivery

  • POST /api/dnsbl/records/delete and delete-items inside POST /api/dnsbl/records/bulk now enrich the dedicated DNSBL removal audit trail with caller/source metadata such as source_type, source_name, site URL/host, page URL, request Origin, Referer, and user-agent when available.
  • DNSBL removal audit messages now also include a clearer caller/source summary so operators can see not only which IP/zones were delisted, but also where the request came from.
  • Dedicated DNSBL removal audits now continue to forward to the separate Slack category DNSBL removal audit, and can now also send plain-text audit mail to the configured DNSBL_REMOVAL_AUDIT_EMAIL recipient list.
  • The WordPress DNSBL plugin now stamps its Tools write/check calls with its own site identity metadata, so delist audits can show which site submitted a removal request even during server-to-server calls.
  • DNSBL delete requests for IPs that are already not listed anywhere now return an accepted no-op response (reason="already_not_listed", forced_success=true) instead of a hard invalid_dnsbl_publication failure, which makes delist cleanup idempotent for callers such as dnsbl-engine.

DNSBL / DMARC - DMARC intake and review queue added

  • POST /api/dnsbl/dmarc/report now accepts DMARC XML / gzip / ZIP payloads for review when the caller presents an active DNSBL token, an admin session, or an active admin-owned Tools API key through the DNSBL-token transport.
  • Tools now stores normalized DMARC report metadata plus per-source-IP rows so admins can review reported IPs in /admin/dnsbl/dmarc instead of treating those reports like ordinary blacklist mail.
  • The new DMARC admin queue lets the site owner publish one reported source IP to DNSBL as either spam (16) or spam + fraud (84) and also mark rows ignored when no publication should happen.
  • projects/dnsbl-engine/run now also scans a dedicated DMARC spool folder, uploads those payloads to the new Tools endpoint, and sends a support reminder mail stamped with X-Tornevall-Mail-Assistant: sent whenever DMARC files are present.
  • projects/dnsbl-engine/run now also supports dmarc / --dmarc-only, while projects/dnsbl-engine/start.sh dmarc and projects/dnsbl-engine/start-dmarc.sh provide a separate DMARC-only runtime path with its own PID lock.

dnsbl-engine - blacklist runs now discard obvious delivery-status / mailer-daemon mail

  • Normal Fraud / Spam mail-engine passes now clean up obvious bounce / DSN / mailer-daemon messages instead of blacklisting the relay hops or sender IPs found inside those reports.
  • The skip targets classic delivery subsystem patterns such as Delivery Status Notification, Mail Delivery Subsystem, mailer-daemon, Reporting-MTA, Final-Recipient, Action: failed, and Diagnostic-Code.
  • The same cleanup-only detector now also recognizes Auto-Submitted: auto-generated, Exchange / Office 365 NDRs, Gmail bounces, and qmail / exim / postfix daemon-style reports.

dnsbl-engine - runspam spam now defaults to a capped per-pass batch size

  • projects/dnsbl-engine/runspam spam now forwards a default limit of 1000 messages per run into the normal blacklist/add pass instead of leaving the batch size unlimited.
  • The helper also accepts an optional second numeric argument such as runspam spam 500 when operators want a different cap.

Mail Support Assistant - outgoing replies now carry an anti-loop marker header

  • Standalone outgoing replies and Tools relay replies are now stamped with X-Tornevall-Mail-Assistant: sent.
  • Standalone polling now skips unread messages that already carry that marker before rule matching/reply and marks them seen, which prevents assistant self-reply loops.

Standalone Mail Support Assistant - unmatched fallback rows now keep falling through after row-local failures too

  • The standalone unmatched-mail runner no longer aborts the whole no-match row loop when one unmatched fallback row hits a row-local AI/API evaluation error.
  • Later active unmatched rows are still tried in sort_order, which matches the earlier fallthrough behavior for ordinary strict-AI rejections.
  • Standalone no-match diagnostics now also expose generic_ai_decision.evaluated_no_match_rules[], so operators can see exactly which unmatched rows were evaluated before the message was answered or left unread.

Mail Support Assistant - mailbox spam-score reply threshold now suppresses replies and keeps mail unread

  • /admin/mail-support-assistant mailbox defaults now include spam_score_reply_threshold for reply suppression.
  • GET /api/mail-support-assistant/config now returns additive mailbox default defaults.spam_score_reply_threshold.
  • Standalone runner now reads X-Spam-Score as an extra score source when X-Spam-Status score metadata is missing.
  • If a message score is above the configured mailbox threshold, the standalone client now suppresses reply handling for that message and explicitly keeps it unread.

Standalone Mail Support Assistant - duplicate closing/signoff blocks are now deduplicated before footer append

  • When static footer mode appends a mailbox/rule footer, the standalone runner now strips trailing AI-generated signoff blocks repeatedly first.
  • This avoids duplicate closings such as Best regards plus Regards in the same outgoing support reply.

Mail Support Assistant - invalid empty unmatched rows are now cleaned and excluded

  • A follow-up cleanup now removes invalid unmatched fallback rows where if or instruction is empty, because those rows can never pass strict no-match triage.
  • GET /api/mail-support-assistant/config now filters defaults.generic_no_match_rules[] to only include valid rows (non-empty if + instruction).
  • If a mailbox had only invalid rows, cleanup backfills one valid row from legacy mailbox fields when those legacy fields contain both values.

Standalone Mail Support Assistant - skipped mail is forced back to unread and local state can now help preserve thread continuity

  • When the standalone runner skips an unread message without sending a reply, it now explicitly clears \\Seen again when the IMAP server supports that operation, instead of only assuming the mail stayed unread.
  • Local message-state.json is no longer part of unread skip/dedupe logic, but it can now still retain lightweight thread excerpts so later AI replies get more realistic conversation continuity.
  • History-heavy diagnostics remain opt-in on the standalone CLI through php run --include-history.

Mail Support Assistant - unmatched fallback now uses add-row IF + instruction rules

  • /admin/mail-support-assistant now supports mailbox-level add-row unmatched fallback rules instead of forcing one single generic IF/instruction pair.
  • Admins can now create, edit, sort, activate/deactivate, and delete multiple unmatched fallback rows per mailbox.
  • GET /api/mail-support-assistant/config now returns additive defaults.generic_no_match_rules[] with ordered row objects (id, sort_order, is_active, if, instruction, footer, ai_model, ai_reasoning_effort).
  • Backward compatibility remains: legacy defaults.generic_no_match_if, defaults.generic_no_match_instruction, and defaults.generic_no_match_footer are still returned and now map to the first active unmatched row when available.
  • Standalone unmatched handling now evaluates these rows in order and can fall through to later rows when earlier rows are rejected by strict AI triage.

2026-04-18

Mail Support Assistant - unmatched mail can now use a strict AI IF/instructions triage instead of a loose generic reply

  • /admin/mail-support-assistant now lets admins configure two separate mailbox-level no-match AI fields: an If... condition describing which otherwise unmatched mail may be answered at all, and separate Instructions... describing how to answer when that condition is met.
  • GET /api/mail-support-assistant/config now also returns additive defaults.generic_no_match_if for each mailbox.
  • The standalone Mail Support Assistant no longer sends a generic unmatched-mail AI reply just because the model produced some plausible text. It now requires a strict JSON decision from AI and only replies when the model explicitly says the mail can be answered with high certainty.
  • Rejected or not-certain unmatched-mail AI decisions now also discard any leftover reply payload internally, so only truly approved high-certainty decisions continue through the reply path.
  • SpamAssassin wrapper-style prose is still ignored as outer noise during that unmatched-mail AI decision, but SpamAssassin score/tests remain available to the AI as risk signals so suspicious sales/fraud mail is more likely to be rejected safely.
  • The Tools admin mailbox/rule forms now use lightweight AJAX saves for routine create/update/delete actions, which makes iterative Mail Support Assistant tuning faster.
  • Focused standalone regression coverage now also guards this strict unmatched-mail path, including the rule that rejected no-match AI decisions must leave the email unread.

Standalone Mail Support Assistant - live CLI diagnostics, empty-response fallback retries, and clearer unread/reply state

  • The standalone public/tornevall-tools-mail-assistant runner now mirrors live log lines to stdout during CLI/manual runs, so bash cron-run.sh shows progress while the mailbox pass is still executing instead of only printing the final JSON block.
  • AI reply generation now also retries through the configured fallback model when the primary model returns an empty reply body, not only when the request throws a transport/API error.
  • Run summaries now include per-message message_results[] diagnostics, making it easier to see whether each unread message was handled, skipped, state-skipped, warned, or failed.
  • If the same unread IMAP message already has a previous local record proving that a reply was sent, the runner now skips automatic resend as previous_reply_recorded_unread to avoid duplicate replies.
  • If a reply is sent successfully but the IMAP finalize step (markSeen, move, or delete) fails afterwards, the runner now records an explicit warning reason instead of silently looking fully handled.
  • Outgoing HTML reply cards now use stronger explicit text colors and light-only color-scheme hints so manual replies/quoted history are less likely to render as white text on a white background in mail clients.
  • Reply delivery now also normalizes to / cc / bcc recipients more defensively before SMTP or Tools relay transport is used, which improves BCC forwarding when addresses arrive with display-name formatting.
  • If neither a matched rule nor mailbox defaults provide a BCC recipient, the standalone runtime can now also fall back to MAIL_ASSISTANT_DEFAULT_BCC from its local .env.

DNSBL engine - cron runs now show live progress and accepted delists no longer get described as failed in reply mail

  • The standalone projects/dnsbl-engine/run worker now forces immediate CLI flushing and prints timestamped progress markers for scan, analyze, lookup/write, cleanup, final whitelist sweep, and operational-report delivery, which makes cron/background runs easier to debug while they are still executing.
  • Long live DNSBL lookup batches now also emit progress checkpoints during the check-ip phase instead of staying quiet until the entire lookup set finishes.
  • Reply-delist AI is now more tightly anchored to the machine-confirmed delete result and will be rejected if it claims that an already accepted delist "did not go through".
  • Successful single-IP bulk delists are now summarized with clearer wording that the delist submission was accepted and the required delete operations were sent, instead of only echoing a generic bulk-success sentence.

Mail Support Assistant - same-language replies, cleaner original-mail summaries, and visible AI budget metadata

  • Standalone Mail Support Assistant AI replies now explicitly default to the same language as the incoming email instead of relying only on general prompt inference.
  • The standalone fallback model now defaults to o4, and that fallback retry intentionally omits reasoning_effort metadata so it behaves as a plain non-reasoning fallback path.
  • Wrapper-style inbound mails are now cleaned more aggressively before rule matching, AI context generation, and outgoing reply quoting, which means SpamAssassin wrappers, forwarded .eml header dumps, and malformed embedded header runs are less likely to drown out the real original request.
  • Outgoing replies now also append a compact excerpt of the original incoming request, so operators and recipients can still see what the sender actually wrote inside the sent answer thread.
  • Mailbox error summaries for failed AI replies now include the attempted model trail, and GET /api/mail-support-assistant/config now also returns additive user.ai_daily_budget metadata so operators can inspect the effective AI token cap/remaining budget used by Mail Support Assistant's SocialGPT-backed reply path.

Mail Support Assistant - overlapping rules now evaluate all matches before one rule is applied

  • The standalone Mail Support Assistant no longer stops at the first matching rule when several rules overlap.
  • It now evaluates all matching rules, selects the most specific match first (most active match fields, then longer combined match text, then sort_order), and records both the selected rule and all other matching candidates in diagnostics.
  • This makes overlaps such as broad Gmail rules vs more specific copyright-notice rules visible instead of silently applying whichever rule happened to be checked first.

AI / Mail Support Assistant API - throttling is now bearer-aware with a very high ceiling and effective admin bypass

  • POST /api/ai/socialgpt/respond, /api/social-media-tools/extension/*, and /api/mail-support-assistant/* now use user-aware throttling instead of the older low fixed per-minute caps.
  • Normal users/tokens now get a much higher limit, while admin-owned requests are effectively unthrottled at the API layer so maintenance/support flows do not break on bursty runs.

Menstrual Tracking - admins can now manage another user's entries directly

  • Admins can now open /admin/menstrual-tracking/{user} to edit another user's menstrual profile and cycle rows without logging in as that user.
  • The user editor now includes a direct Menstrual tracking shortcut so admins can jump straight from /users/{id}/edit into that user's tracking view.

Mail Support Assistant - AI replies now retry throttling and stop sending the misleading generic fallback

  • Temporary AI rate-limit failures such as 429 / Too Many Attempts are now retried automatically by the standalone Mail Support Assistant before it gives up on the AI reply path.
  • AI-enabled rules without an explicit static fallback template no longer send the old generic sentence Thank you for your message. We have reviewed it. when AI fails; the reply is now aborted and logged instead of pretending the issue was handled.
  • One failed message reply no longer aborts the rest of the same mailbox run; later messages in the mailbox can continue to be processed.

Mail Support Assistant - outgoing replies now include styled HTML

  • Mail Support Assistant replies are now emitted as multipart/alternative, keeping the original plain-text body while also adding a styled HTML version for richer-looking support mail.
  • POST /api/mail-support-assistant/send-reply now also accepts additive body_html, so Tools relay delivery can preserve the formatted reply body when the standalone runtime falls back to relay mode.

Mail Support Assistant - matched rule AI now really uses the rule's AI overrides

  • AI-enabled Mail Support Assistant rules now forward their configured responder_name, persona_profile, custom_instruction, selected ai_model, and additive ai_reasoning_effort to Tools as explicit one-request overrides.
  • The mailbox-level generic unmatched-mail AI settings remain mailbox defaults for the no-match path only; they no longer look like the source of matched rule replies.
  • /admin/mail-support-assistant now uses dropdowns for Mail Support Assistant AI model selection and per-rule/per-mailbox reasoning effort instead of relying on free-text model input for those AI controls.

DNSBL API - fraud writes now mirror into the ordinary DNSBL zones again

  • Fraud-style DNSBL add/update publication now mirrors into dnsbl.tornevall.org + opm.tornevall.org as well as bl.fraudbl.org, restoring the older combined DNSBL+FraudBL behavior for phishing/fraud flags.
  • Commerce publication is unchanged and still stays in bl.fraudbl.org + ecom.fraudbl.org without being mirrored into the ordinary DNSBL zones.

DNSBL API - bulk DNS writes now harden interrupted UDP receives

  • Large DNSBL bulk requests now extend their runtime budget before executing the server-side DNS updates.
  • The low-level DNS socket transport now also retries retryable interrupted receive calls such as socket_recvfrom(): Interrupted system call before the operation is treated as failed.

Mail Support Assistant - no-match/configuration skips now stay unread

  • The standalone projects/tornevall-tools-mail-assistant no longer marks mail as read when it was skipped only because no rule matched or the generic unmatched-mail fallback was disabled, unanswerable, or failed.
  • mark_seen_on_skip is now effectively limited to deliberate heuristic skips such as high-score SpamAssassin junk, which makes mailbox misconfiguration easier to notice and retry.

Mail Support Assistant - mailbox-level generic reply settings for unmatched mail

  • /admin/mail-support-assistant now includes mailbox-level settings for a generic AI fallback reply when an incoming mail does not match any explicit rule.
  • The additive mailbox config returned by GET /api/mail-support-assistant/config now includes defaults.generic_no_match_ai_enabled, defaults.generic_no_match_ai_model, defaults.generic_no_match_instruction, and defaults.generic_no_match_footer.
  • This lets the standalone mail assistant answer unmatched but still support-relevant mail without having to create a dedicated explicit match rule for every case.

Mail Support Assistant - local MTA fallback and Tools relay endpoint

  • The standalone projects/tornevall-tools-mail-assistant now supports selectable outgoing transports: local PHP mail(), custom MTA command, or direct Tools relay mode.
  • Standalone transport now also supports direct SMTP and defaults to smtp, reducing postdrop/local sendmail dependency issues in cron environments.
  • Standalone runners can now automatically fall back from failing local mail transport to a new Tools endpoint: POST /api/mail-support-assistant/send-reply.
  • Tools now exposes that relay endpoint behind a dedicated personal token provider (provider_mail_support_assistant_mailer) plus a new permission gate (mail-support-assistant.relay).
  • /admin/mail-support-assistant now also has relay-token rotation UI and optional one-click permission grant for the selected token owner.
  • Standalone API error parsing was hardened to avoid PHP Array to string conversion warnings when Tools returns structured error arrays.

DNSBL API - per-user delete guardrails in admin and runtime enforcement

  • DNSBL token admin now also supports a delegated CIDR floor through delete_min_cidr_prefix, so non-admin token owners can be limited to smaller delete ranges such as /25../32 instead of receiving blanket CIDR access.
  • DNSBL auth/token metadata can now expose can_cidr_delete, and CIDR delete attempts now fail explicitly with delete_cidr_not_allowed or delete_cidr_prefix_too_broad when the requested range is outside the delegated floor.
  • DNSBL token admin now supports user-level delete guardrails: delete_limit_per_day, delete_cidr_limit, delete_throttle_limit, and delete_throttle_window_seconds.
  • The DNSBL delete path now enforces those limits for token-backed users on both single delete and bulk-delete requests.
  • Guardrail denials now return explicit reasons: delete_daily_limit_exceeded (429), delete_throttle_exceeded (429), and delete_cidr_limit_exceeded (422).
  • DNSBL token info and auth-summary payloads now include additive delete_guardrails metadata so clients can show effective limits before delist attempts.
  • Runtime guardrail resolution now also degrades safely on older installations where the new dnsbl_api_tokens columns have not been migrated yet, returning default/no-guardrail metadata instead of crashing the delete endpoint.

DNSBL engine - empty runs no longer send operational report mail

  • projects/dnsbl-engine/run now suppresses the summary report mail entirely when a run did not touch any actual address activity, which avoids "nothing happened" report spam from no-candidate/no-op spool passes.

Standalone Mail Support Assistant - read-at-ingest mail is now tracked separately

  • Runner summaries now include messages_read_skipped / mailbox read_skipped counters so already-read IMAP mail does not look like no_matching_rule noise.
  • The IMAP mailbox payload now includes is_seen, and messages already marked read at ingest are skipped without being recorded as new ignored no-match state rows.
  • Local message-state summary now exposes excluded_read_records and raw_count metadata to make state diagnostics clearer when operators compare visible rows against raw storage.

Standalone Mail Support Assistant - unread mail can now be re-evaluated even if it exists in local history

  • The standalone runner no longer uses storage/state/message-state.json as a hard dedupe gate for unread mail.
  • Messages that are still unread in IMAP can now be reprocessed on later runs, which means newly added rules can start matching without first clearing the local state file.
  • Already-read mail is still skipped immediately, and the local state file remains available only as diagnostic history.
  • Runner summaries now also expose messages_previously_recorded_unread when an unread thread was found in local history and deliberately re-evaluated.

DNSBL token requests - approval emails now include account-level delete defaults

  • When an admin approves a pending DNSBL token request, Tools now emails the requester that the token is ready and includes the granted add/delete scope.
  • The same mail now also summarizes the effective delete guardrails/default limits currently applied to that account, so delegated users can see the current caution limits before trying delist operations.

2026-04-17

DNSBL engine - skipped already-covered IP mail now cleans up, reports are mailed, and reply-delist can add AI clarification

  • The standalone projects/dnsbl-engine worker now also cleans up spool files when an IP is skipped because the live DNSBL already has the same or a higher bitmask, instead of leaving those files pending forever.
  • Add/Fraud runs now also clean up mails that contain no actionable IP/CIDR candidates at all, instead of leaving those no-op mails stuck in the folder forever.
  • The whitelist loader now honors env-driven DNSBL/firewall/spamassassin DB connection names, so .env values such as DB_DNSBL_DATABASE, DB_DNSBL_FIREWALL_DATABASE, DB_DNSBL_CONNECTION, and DB_SA_CONNECTION drive the live whitelist source setup.
  • When SPAM_REPORTS_FROM and SPAM_REPORTS_TO are configured, add/update runs and delist/reply runs now send an operational summary mail with stats, whitelist-match details, and final whitelist-sweep output.
  • SpamAssassin sender-whitelist holds are now less sticky too: if the mail still looks clearly fraudulent from its content, sender_matches_whitelist_from is auto-overridden and the mail continues through normal Fraud/Spam handling.
  • projects/dnsbl-engine now also has a narrow --force-sender-whitelist-holds operator flag that bypasses only sender_matches_whitelist_from manual holds, without turning off the other low-score / safe-test protections.
  • Reply-delist still uses its default HTML confirmation template, but unclear requests can now get one extra AI-generated clarification paragraph from Tools/OpenAI; if that AI step fails, the standard template is still sent unchanged.
  • Reply-delist now also treats header-only inferred targets as unclear, so the reply can explain that the worker only found removable IPs in message headers/transport metadata and can ask the sender for explicit IP/CIDR targets when needed.
  • Support-heavy reply-delist mails can now let AI write the primary DNSBL-focused answer too, but that answer is now anchored to the actual delist outcome from the run so it acknowledges already-submitted delists instead of asking the sender to start the process again.
  • AI-backed reply-delist answers can now also add a short apology when the original mail is more than two days old, and they are now explicitly steered to stay within DNSBL, DNSBL API, delisting, and the public DNSBL documentation/removal workflow.
  • Reply-delist AI responses now request gpt-5.4 with medium reasoning effort first, and if that pass comes back empty the worker immediately reruns the answer through the fallback model (gpt-4o-mini) instead of sending a blank AI section.
  • Auto-reply logging now states whether the local mail transport accepted or rejected the outgoing reply, which makes silent reply-delivery failures easier to diagnose.
  • Delist-heavy paths now also reuse chunked multi-request deletes where possible instead of always deleting strictly one target at a time.
  • Malformed non-JSON bulk write responses are now recovered more aggressively too: the worker first salvages wrapped JSON when possible, then retries the failed chunk once as a normal bulk request, and finally falls back to per-operation requests before leaving the chunk failed.

DNSBL reply reports + AI fallback reasoning - cleaner summaries and clearer failure causes

  • Reply-delist request summaries now strip common MIME/HTML wrapper noise more aggressively, so the "Summary of your request" section focuses on the actual question instead of multipart boundaries or copied transport fragments.
  • Repeated identical request lines are now deduplicated there as well, so the same sender question is not echoed twice in the summary box.
  • Proton/quoted-printable copies of the same mail are now decoded and cleaned before summary extraction and AI reply context building, which makes technical delisting/help questions much less likely to be skipped behind MIME noise.
  • DNSBL reply AI now also receives explicit date-check context (message age plus current reply time), so it can decide when a delayed-reply apology is appropriate.
  • Long DNSBL AI reply paragraphs are now trimmed at safer sentence/whitespace boundaries instead of a hard mid-word cut, preventing visibly broken endings in outgoing replies.
  • DNSBL reply-mode mail handling no longer treats a message as our own auto-response only because support@tornevall.net appears in the thread, which allows real customer follow-up replies to earlier support mails to be answered normally.

Standalone Mail Support Assistant - reply-chain handling and clearer skipped-mail diagnostics

  • The standalone projects/tornevall-tools-mail-assistant runner now handles reply chains better: Re:/Fwd:/Sv: subject prefixes are stripped before rule matching, quoted historical mail blocks are removed before body matching and AI summaries, and outgoing replies now preserve In-Reply-To / References headers.
  • IMAP parsing there now stores real Message-Id values plus a stable fallback local message key when the header is missing, so skipped/handled mail appears correctly in local message-state summaries.
  • No-match skips are now logged more explicitly with mailbox/from/to/subject context, making scanned but handled=0 runs much easier to diagnose.
  • CIDR-heavy delist output is now compact in reply/report sections: instead of listing every expanded IP row, the mail now shows handled/failed totals plus sample failed targets/reasons.
  • Bulk chunk failures now include explicit diagnostics in target-level delete outcomes (for example HTTP/decode/fallback details) instead of only surfacing a generic "One or more bulk chunks failed." message.
  • IPv6 extraction from incoming mail text is now stricter, so labels such as IPv6: no longer leak a bogus leading 6: into the processed address list.
  • DNSBL reply-delist now also supports its own responder/persona/custom-instruction overrides (DNSBL_AI_RESPONDER_NAME, DNSBL_AI_PERSONA_PROFILE, DNSBL_AI_CUSTOM_INSTRUCTION) for operators who want reply-specific AI tone independent of saved SocialGPT settings.
  • When AI writes the primary DNSBL support reply, the outgoing mail no longer repeats the same accepted-target summary in a second generic deterministic paragraph.
  • SocialGPT reasoning-effort support now also includes gpt-4o* model prefixes by default, so gpt-4o/gpt-4o-mini fallback runs can keep the same reasoning behavior when configured.
  • The standalone projects/tornevall-tools-mail-assistant AI path now mirrors the same behavior profile: sanitized message summaries, configurable reasoning effort, and one primary-to-fallback retry (gpt-5.4 -> gpt-4o-mini).

Feed Q&A - SQL-matched rows now stay visible through final answer generation

  • Feed Q&A now carries a dedicated matched_entries evidence layer from SQL retrieval into the final AI context instead of using the SQL match only as a hidden filter step.
  • That means question-specific matches can now keep their concrete title, description, content excerpts, publishby, feed_title, and link data all the way into the answer stage.
  • For questions about who is writing about a person or topic, the model is now explicitly instructed to prioritize those matched rows before falling back to aggregate counts like feed totals.

IRCWatch - fixed missing local action inference, added ? channel replies, and escalates question abuse

  • IRCWatch now actually includes the local invite / op / kick inference helper that its channel-mention flow calls, fixing the runtime fatal about ircwatchInferStructuredChannelActionFromMessage() being undefined.
  • Channel messages that end with ? can now trigger a short bot answer even without a direct bot mention.
  • Repeated question spam or abusive ?-bait can now produce warnings, while the ? reply path itself no longer auto-kicks anyone.
  • Ordinary non-abusive questions no longer trigger kicks just because the AI classifier or question counter was too eager.
  • Kick permission checks for manual/inferred kicks now also recognize ordinary channel-op / eggdrop-o access, not only owner/master-style flags.

Mail Support Assistant - Tools admin cards cleaned up and examples made generic

  • The /admin/mail-support-assistant configuration surface now uses clearer card groupings and better-formatted mailbox/rule sections so the page is easier to scan and edit.
  • Mailbox/rule placeholders and helper copy now use neutral support-oriented examples such as order status, customer care, reference numbers, and processed folders instead of a narrow copyright-specific scenario.
  • The Swedish Mail Support Assistant docs/changelog wording was also normalized so recent entries once again preserve proper åäö characters.

Mail Support Assistant - renamed standalone project path plus AJAX/operator and SpamAssassin handling refinements

  • The standalone client now lives under projects/tornevall-tools-mail-assistant/ instead of the earlier temporary projects/mail-support-assistant/ path.
  • Its mini web UI is now more operator-friendly, with AJAX refresh, self-test, and safe dry-run actions that reuse the same plain-PHP runner classes as CLI execution.
  • The standalone client remains framework-free and databaseless locally: mailbox credentials stay managed in Tools admin, while the client only stores local env/session/log/summary data.
  • The runner now also inspects SpamAssassin headers so obviously high-score junk can be skipped more safely, while wrapper-style SpamAssassin rewrites can still be copied locally and stripped before rule matching / AI reply generation.

DNSBL engine - queued blacklist mail is no longer cleaned before write acceptance

  • The standalone projects/dnsbl-engine worker now keeps queued spool files until the Tools DNSBL bulk write has actually accepted the corresponding add/update operations.
  • Duplicate files for the same queued IP are now retained with that IP until acceptance instead of being discarded early as already-handled.
  • Header-driven message whitelist mode is now suppressed during add-blacklist runs so stored whitelist / SPF logic can still protect trusted senders without letting SpamAssassin wrapper headers short-circuit blacklist adds.

IRCWatch - structured channel actions now cover op/invite alongside topic-like actions

  • IRCWatch now routes op, invite, and kick through the same structured channel-action layer that already handled topic-style actions, which keeps PM/manual execution more consistent.
  • The AI action contract now also allows structured Op and Invite actions in addition to Topic, Mode, and Kick.

Mail Support Assistant - standalone IMAP support-mail client scaffold plus Tools admin/API config

  • A new admin-only Mail Support Assistant console is now available at /admin/mail-support-assistant.
  • Admins can define IMAP mailbox settings, ordered mail-handling rules, reply sender/BCC/footer defaults, and per-rule AI/static reply behavior there.
  • Tools now also exposes GET /api/mail-support-assistant/config, which lets the standalone PHP client fetch its mailbox/rule configuration by bearer token instead of keeping a separate local database.
  • The first standalone project scaffold now lives under projects/tornevall-tools-mail-assistant/ with its own README.md, CHANGELOG.md, AGENTS.md, env-driven mini web UI, and CLI/cron runner skeleton.

API keys - AI receiver tokens now keep is_ai consistent while provider_openai stays excluded

  • The is_ai flag is now normalized more consistently whenever API keys are created or updated from the Tools key UI.
  • Legacy/generated tools_ai_bearer tokens still force is_ai=1.
  • The upstream provider_openai secret is now explicitly excluded from is_ai, because it is the provider secret used towards OpenAI, not a client token used towards Tools.
  • OpenAI-access rejection and user-ban cleanup now remove all personal AI receiver tokens for that user instead of deleting only the legacy tools_ai_bearer row.

Tools AI auth - personal AI-capable tokens can now authenticate per-system clients such as IRCWatch

  • Tools AI endpoints no longer need to rely only on the legacy tools_ai_bearer provider key.
  • Personal API keys can now be marked as AI-capable, which lets a client keep its own provider identity (for example provider_ircwatch) while still authenticating against Tools-hosted AI endpoints.
  • OpenAI execution still requires the token owner to have approved provider_openai access or admin bypass; the AI-capable token only proves client identity and user ownership.
  • The SocialGPT/extension validation + smoke-test endpoints now report the resolved token provider, and they accept those personal AI-capable tokens as well.

IRCWatch - PM commands can now grant +o and perform manual or AI-assisted kicks

  • IRCWatch now supports private-message operator requests such as op #channel and ge mig op på kanal #channel, which send MODE #channel +o <nick> when the requester is authorized for that channel.
  • IRCWatch owners/masters can now also send kick <nick> <#channel> [reason] for a direct kick with their own reason.
  • A new PM command aikick <nick> <#channel> <motivation> lets IRCWatch ask Tools/OpenAI for a short kick reason before sending the kick.
  • IRCWatch now also supports local per-bot responder/persona/instruction overrides, so its prompt identity no longer has to piggyback on the SocialGPT extension defaults.

SocialGPT - Chrome-first source manifest now builds browser packages for Edge, Opera, and Firefox too

  • The SocialGPT extension source tree still keeps its root manifest.json as the Chrome-first development/test manifest.
  • Release packaging now goes through projects/socialgpt.sh, which stages browser-specific archives under projects/socialgpt-chrome/dist/ instead of zipping the raw source folder directly.
  • Chrome, Edge, and Opera builds currently reuse the same manifest, while the Firefox package gets a build-time browser_specific_settings.gecko patch so the repository manifest can stay Chrome-oriented.
  • SocialGPT request telemetry now also reports a browser-aware client_platform value (chrome_extension, edge_extension, opera_extension, or firefox_extension) instead of always claiming Chrome.

2026-04-16

Public feed analytics - /feed shows one edition per period again, and feed-admin can now purge extra cached editions

  • The public /feed overview now shows only the single selected public edition for each category/site period (daily, weekly, monthly, yearly) instead of rendering multiple cached editions inline.
  • The heavier “review every saved edition” behavior is now limited to the category cards page when you intentionally open /feed/cards/{categorySlug}?show_all=1.
  • Feed-admin category/site cards now also include a Keep only selected edition cleanup action that deletes all other cached analyses for the chosen period while preserving the currently selected edition.

Public feed reader - inline article "Read more" links now reuse /out/{contentId} redirects

  • The public feed reader now rewrites inline article links inside rendered description/content blocks when they point at the same article as the entry itself, so those "Read more"-style links go through Tools' /out/{contentId} redirect instead of exposing the external URL directly.
  • The same redirect reuse now applies consistently on the main feed view, category feed view, and category cards view.

WordPress DNSBL checker - primary listed/not-listed text now says when the answer comes from DNS resolvers

  • The public checker now states more clearly that the first "listed" / "not listed" answer is the plugin's local DNS-resolver result, while the slower Tools API follow-up is only an extra confirmation step for delist detail.
  • Reset/input changes also invalidate older checker/CIDR async callbacks more aggressively, which reduces stale-result repainting when a user starts a new search immediately.

WordPress DNSBL plugin - local CIDR scans now show live progress and a hit list without leaving the 3.1.0 release line

  • The advanced CIDR check in the WordPress delist flow now stays inside WordPress instead of relying on Tools for the block scan itself.
  • The plugin walks /24-/32 ranges in small local batches, shows live progress while scanning, and keeps a visible hit list of listed IPs found in the block.
  • Batch pacing is intentionally conservative so the resolver side is not flooded, while the final CIDR delete still goes through the DNSBL write endpoint after the local scan has found at least one listed address.
  • The Advanced CIDR field is now the authoritative delete scope after that handoff, so users no longer need a second single-IP anchor once the range has been moved there and approved by the local scan.
  • Advanced handoff now waits until the user actually clicks Check if listed with a CIDR still in the first field, so the form no longer interrupts someone who is still typing the range.
  • Checker and delist submits now also show a dedicated busy spinner/status row in the public WordPress form so the user can see that the live request is still running.
  • CIDR delete now targets only the IPs that the local CIDR scan actually found listed, and those deletes are now submitted sequentially one IP at a time instead of in chunked bulk batches.
  • Plugin-facing release docs were also normalized so these maintenance refinements remain documented under the current 3.1.0 line instead of inventing a separate version bump.

DNSBL removals - every delist attempt now gets its own dedicated audit event

  • POST /api/dnsbl/records/delete and delete items inside POST /api/dnsbl/records/bulk now emit dedicated DNSBL removal audit entries in the backend, separate from the broader generic DNSBL API request log.
  • The removal audit includes authoritative delete metadata when available, such as the submitted IP, resolved owner names, affected zones, target values, dry-run state, and final outcome (success, denied, failed, or validation-related rejection).
  • A new Slack log category DNSBL removal audit can now be enabled when operators want targeted delist notifications without turning on the full API /dnsbl requests Slack stream.

2026-04-15

Feed Q&A - fixed answer-model lookup, context build crash, and markdown rendering consistency

  • Feed Q&A no longer calls an outdated model-catalog method during answer-model selection, which removes the warning about ModelCatalogService::getAvailableModels() and makes the configured answer model follow the current catalog contract again.
  • The question-processing context builder now defines its scoped feed-ID list before progress reporting uses it, fixing the Undefined variable $feedIds failure that could abort some questions before the answer stage.
  • Latest/recent Feed Q&A answers now render with the same safe markdown formatting more consistently across /feed, the site-specific question panel, and live AJAX-updated answer boxes, so markdown links/paragraphs are no longer shown as plain raw text on those newer surfaces.

DNSBL API - delist requests now resolve affected blacklist subzones on the server

  • POST /api/dnsbl/records/delete and delete items inside POST /api/dnsbl/records/bulk now derive the actual listed owner names from a live DNS inspection of the submitted IP before any low-level DNS delete is attempted.
  • Client-provided publication_type / bitmask is no longer trusted as the source of truth for delete scope; the DNSBL endpoint now decides which of dnsbl.tornevall.org, opm.tornevall.org, bl.fraudbl.org, and ecom.fraudbl.org must be deleted.
  • This keeps delist orchestration in Tools instead of in the WordPress checker/plugin and makes delete operations reflect the current live DNS state for the IP.
  • The WordPress checker client now sends only one delete request per IP (instead of expanding one checked IP into multiple delete calls), and failed checker-mode delist attempts reset the Turnstile verification so the user can retry immediately with a fresh token.
  • The WordPress-side timeout for DNSBL write requests was also raised so the newer server-authoritative delete flow gets more time to complete before the plugin aborts the call.
  • In checker mode, clicking Delist now shows the in-flight “Submitting request…” state on the Delist button itself while both checker buttons stay disabled until the request completes, which helps prevent accidental double submits.
  • Dynamic DNS transport for blacklist child zones now updates the real authoritative parent zone (fraudbl.org / tornevall.org) while still deleting the concrete child-zone owner names, fixing NOTAUTH delist failures where BIND rejected bl.fraudbl.org or opm.tornevall.org as the update zone.

Microsoft To Do - platform app configuration is now visible directly on the integration page

  • /settings/integrations/microsoft-todo now includes a platform-app diagnostics panel showing whether the shared Microsoft Entra / Graph app is available, which required fields are still missing, and the effective/recommended callback URL for the current host.
  • Acknowledged admins can now save/update the database-backed Microsoft To Do platform app directly from that page through AJAX when environment values are not already managing it.
  • GET /api/microsoft-todo/status now returns additive non-secret platform_app diagnostics so authenticated clients can distinguish “not connected” from “host app not configured yet.”

Public feed entry history - /feed/entry can now switch to side-by-side diff view

  • The permalink history block on /feed/entry/{contentId} now has a small diff-view toggle so operators can switch between the existing highlighted inline diff and a side-by-side before/after comparison for each adjacent revision pair.
  • The default remains the current inline diff view, while the raw chronological Versions list still stays below the diff block for one-revision-at-a-time inspection.

DNS editor - zone loading no longer crashes when cache settings have never been saved

  • Loading a zone in the DNS editor now falls back cleanly to default cache-policy metadata when that zone has no dns_zone_settings row yet.
  • This fixes the Attempt to read property "last_invalidated_at" on null error on first load for zones that have never had per-zone cache settings saved.

Public feed category cards - “Show all” analytics links now actually show every variant

  • The public /feed/cards/{category} page now respects the show_all=1 query flag from the overview page, so the linked page displays every stored analysis variant for each period instead of silently collapsing back to one selected variant.
  • The cards-page toolbar now keeps that show_all mode when changing pagination or history settings, and the period header clearly states how many variants are being shown.

DNSBL delist flow - checker-mode delist no longer waits for the background follow-up, and blacklist subzones reuse root TSIG keys

  • The WordPress DNSBL checker now lets users click Delist immediately once the first DNS lookup has already confirmed that the IP is listed, even if the slower Tools API follow-up is still running in the background.
  • The checker keeps showing that the background follow-up is in progress, but it no longer blocks the delist action when the listing itself is already confirmed.
  • DNS update key lookup now resolves blacklist subzones via their root key domain, so bl.fraudbl.org and ecom.fraudbl.org use the fraudbl.org key configuration, while dnsbl.tornevall.org and opm.tornevall.org use the tornevall.org key configuration.
  • That root-key canonicalization now also applies in the direct socket-based DNS update path, so deletes/adds no longer fall through to missing key-file lookups such as bl.fraudbl.org.conf on systems that only store fraudbl.org.conf.

SocialGPT Chrome (v1.2.16) - popup and context-menu routing now target the right frame

  • The extension popup's Open Toolbox in active tab action no longer depends on whichever injected frame answers first.
  • The background worker now inspects all injected frames and routes the request to the most relevant one (existing Toolbox frame, selected-text frame, focused editable frame, otherwise the top frame).
  • Right-click Open Toolbox and Verify fact with Toolbox now use that same frame-aware routing, which makes the feature much more reliable on iframe-heavy or app-like pages.

2026-04-14

RSS analytics - easier old-bucket cleanup and richer generation audit context

  • Feed-admin category/site cards now include a dedicated Purge old buckets action for the selected period, making it easier to remove stale cached analytics left behind from older bucket semantics.
  • Analytics variant selectors on both feed-admin and /feed now include the stored bucket label, so older cached variants are easier to identify before deleting them.
  • OpenAI engine audit forwarding for RSS analytics now includes richer execution context such as analysis stage, category/site target, period type, bucket, language, variant title, and entry/feed counts.

DNSBL WordPress checker - calmer follow-up wording for wrong token type

  • The WordPress DNSBL checker now keeps the first DNS-based “this IP is listed” result as the primary message even when the background Tools API follow-up discovers that the configured token belongs to another Tools API provider/token type.
  • In that case, the follow-up now reports the token problem as a delist-rights confirmation issue instead of sounding like the listing result itself suddenly changed.

RSS analytics - calendar-period overwrite and clearer period labels

  • Daily/weekly/monthly/yearly analytics now use calendar-aligned periods for snapshot ranges:
    • daily: current day,
    • weekly: current ISO week (Monday start),
    • monthly: current calendar month,
    • yearly: current calendar year.
  • Current-period selection now prefers entries from the active bucket (period_bucket) when rendering feed-admin cards, so stale default variants from older buckets no longer mask the latest period output.
  • Feed-admin analytics cards now show explicit period labels from bucket identity (for example Week 16 (2026), Month 2026-04, Year 2026) together with generation timestamp.
  • Prompt wording used by category/site analytics generation now matches calendar semantics (current ISO week, current month to date) instead of rolling last 7/30 days wording.

DNSBL API - CSRF exclusion for all /api/dnsbl/* routes

  • All POST /api/dnsbl/* endpoints (including check-ip, records/add, records/delete, records/update, records/bulk) are now excluded from CSRF verification in VerifyCsrfToken.
  • This resolves HTTP 419 errors that occurred when server-side clients (WordPress plugin, Android app, or any token-backed API caller without a browser session/CSRF cookie) sent requests to these endpoints.
  • Session-based auth for admin users through the web UI is unaffected — the web middleware stack (and therefore sessions) is still active on the DNSBL routes.

DNSBL API - clearer token-type diagnostics on write/check routes

  • /api/dnsbl/check-ip and /api/dnsbl/records/* auth failures now distinguish between:
    • unknown/revoked DNSBL token,
    • matched non-DNSBL Tools token/provider (reason="wrong_token_type"), and
    • inactive admin API-key token (reason="inactive_admin_api_key").
  • This aligns write/check auth diagnostics with GET /api/dnsbl/token/info, so clients can see whether a supplied token was recognized but belongs to the wrong auth model.

VirtualBox Manager - new web GUI for vboxwebsrv machine parks

  • Added a new permission-gated admin web GUI at /admin/virtualbox for managing VirtualBox hosts through vboxwebsrv.
  • Users with virtualbox.manage can register their own VirtualBox server endpoints (host, port, username, password) and keep multiple server segments in one panel.
  • Each server card now loads an expandable machine list with a minimalist default overview and direct VM operations.
  • Supported VM actions in this release: start, stop, restart, delete, plus core settings update (memory, cpus, vram, boot1).
  • Added encrypted server credential storage (virtualbox_servers table) and a dedicated permission migration for virtualbox.manage.
  • Added service discoverability card on /services and new EN/SV docs page (/docs/en/virtualbox-manager, /docs/sv/virtualbox-manager).

VirtualBox Manager - AJAX-first operations + VM setup defaults

  • VirtualBox Manager now reads the actual WSDL function list first and prefers VirtualBox-specific SOAP operations such as IWebsessionManager_logon before falling back to generic guesses like logon.

  • VirtualBox Manager now retries login through multiple SOAP client candidates when the WSDL client reaches the server but fails with a namespace/method fault such as Method 'ns1:logon' not implemented: method name or namespace not recognized, including both known VirtualBox SOAP URIs.

  • Failed VirtualBox login diagnostics now keep the effective endpoint, WSDL URL, selected SOAP mode, selected SOAP URI, available WSDL functions, and the attempted login sequence visible in the raw debug payload.

  • Most VirtualBox admin operations on /admin/virtualbox now run through AJAX, so routine actions no longer require full page postback/redirect cycles.

  • Added direct VM provisioning from each server segment (Create new VM) to cover practical setup even when unattended install is not used.

  • New VM defaults are now aligned for faster setup: bridged network mode, 1024 MB memory, and 25 GB disk.

  • The create form can now check server-registered installable DVD/ISO media and offer those paths as quick suggestions.

  • If ISO inventory lookup is unavailable on a server, VM creation still works with manual ISO path (or no ISO) so setup can continue.

VirtualBox Manager - fixed invalid VBoxManage remote flag usage

  • Removed unsupported VBoxManage command flags (-H, -p, -u, -w) that produced Oracle CLI usage dumps such as Invalid command '-H'.
  • Runtime mode is now explicit: the current implementation runs local VBoxManage commands only.
  • Non-local host entries now fail fast with a clear operator message instead of misleading credential-related output.

VirtualBox Manager - remote hosts now run via SSH

  • Remote VirtualBox hosts are now executed over SSH instead of being hard-failed.
  • host, port, username, and encrypted password from each server card are now used to run remote VBoxManage --nologo ... commands.
  • Remote SSH now respects the configured server port (default should be 22 for SSH, not legacy 18083).
  • Error diagnostics are now explicit for common SSH cases:
    • auth failure (sshpass exit code 5) reports credential guidance,
    • connection failure (255) reports host/port/firewall guidance.
  • /admin/virtualbox UI/help text now matches the runtime: local execution for localhost targets, SSH execution for remote targets.

2026-04-13

SocialGPT - client version telemetry and stronger secret-disclosure guardrails

  • POST /api/ai/socialgpt/respond now accepts additive client metadata fields: client_name, client_version, and client_platform.
  • Successful and failed SocialGPT responses can now echo that accepted client metadata back in an additive client object, which makes it easier to identify which extension/app build made the request.
  • Tools-side SocialGPT guardrails now explicitly allow the AI to state the currently used model identifier and client version when a user explicitly asks about version/model information.
  • The same guardrails now explicitly block attempts to extract Tools internals such as hidden prompts, source code, .env values, passwords, tokens, API keys, and similar secrets.
  • When a SocialGPT interaction matches those secret-exfiltration patterns, Tools can now email an incident report to the configured support recipient.

DNS editor - cached-zone search no longer crashes on null-byte-padded IP queries

  • DNS cached-zone search now strips null bytes and other control characters from incoming search/IP strings before inet_pton() runs.
  • This fixes the DNS editor error inet_pton(): Argument #1 ($ip) must not contain any null bytes when malformed/pasted search values reached /api/dns/zones/{zone}/search.

SMS - support email notifications for inbound and outbound activity

  • Tools can now email support@tornevall.net (or the configured SMS activity recipient) whenever an inbound SMS is stored or an outbound SMS is queued.
  • Automatic prefix-trigger replies routed through the GSM queue are included in the same activity-mail flow.

2026-04-10

DNSBL delist flow - new token-backed IP inspection and explicit audit routing

  • Added POST /api/dnsbl/check-ip, a live DNS inspection endpoint that returns the currently listed DNSBL/FraudBL publication families for one IP plus the delist candidates that the authenticated token/session can act on.
  • The WordPress DNSBL plugin now keeps its first checker answer DNS-first and immediate, then runs that new Tools endpoint in the background when a DNSBL / Tools API token exists before enabling the delete action.
  • This means checker-mode delisting can now follow the correct publication family (dnsbl, fraudbl, commerce) instead of relying only on the first local resolver impression.
  • DNSBL write requests and the new check endpoint now also emit explicit backend logs and attempt to forward DNSBL API activity to the configured audit/notification out-channels when those routes/channels are enabled.

2026-04-09

RSS Watch Feed Q&A - body-only keyword hits now stay visible to Ask-a-question answers

  • Feed Q&A now always merges deterministic fallback search terms with the OpenAI keyword plan, so literal database-searchable terms such as sabah survive even when the AI proposes broader phrasing.
  • When a person-targeted byline filter had to fall back to broader keyword retrieval, the later context-loading step no longer reapplies the old byline-only filter and accidentally empties the evidence set.
  • Recent entry evidence sent to the model now includes short keyword-aware description/content excerpts, which helps Ask-a-question answers recognize matches that were found in article body text rather than only in titles.

RSS analytics - overwrite current now stays inside the active weekly/monthly bucket

  • Category and site analytics now derive their period_bucket from the active period reference date instead of the rolling window start date.
  • This means overwrite current replaces only the current weekly/monthly bucket and naturally moves on to a new row when the calendar period changes, instead of looking like it still belongs to the previous bucket.

WordPress DNSBL plugin - built-in main removal page now requires live delete permission

  • The WordPress DNSBL plugin can now render its own built-in main removal-page template when the selected delisting page does not already contain a removal shortcode.
  • Saving that primary delisting-page setting now performs a live permission lookup against GET /api/dnsbl/token/info, and the page is only accepted when the configured token currently has delete / delist access.
  • Site owners can still build their own removal pages with [dnsbl_removal_form] or [tornevall_dnsbl_removal_form], and those shortcode forms now only expose DNSBL operations that the configured token is allowed to perform.

RSS Watch Feed Q&A - extra search terms now stay queryable instead of turning into helper-text phrases

  • Feed Q&A now strips generic helper/context suffixes from keyword-planner output before the SQL-backed retrieval pass reuses those terms.
  • Multi-word names and entities are therefore kept closer to literal query phrases such as Tara Sabah or Tara Saleh, instead of odd variants like tara sabah artiklar, tara sabah analys, or tara saleh källor.
  • The readonly extra search terms box on /feed now reflects those cleaned phrases too, so the visible hints better match terms that can actually occur in article titles, descriptions, content, or bylines.

RSS Watch Feed Q&A - readers can now see the literal search keywords used

  • /feed now shows a separate readonly search keywords used box under the latest answer, alongside the existing extra-terms helper.
  • The site-specific feed question panel now shows the same keyword box for the latest answer on that feed.
  • /feed/user-questions now surfaces those literal retrieval keywords inside the How this answer was analyzed box, so users can compare the exact searched phrases with any broader extra terms.

RSS inbound XPath importer - null-safe DOM handling for malformed legacy rule sets

  • The legacy XPath importer used by /api/rss/update now guards its DOM helper calls against null values so partially matching or malformed elements rule sets no longer crash the whole inbound conversion batch with method_exists(..., null) type errors.
  • Legacy XPath extraction failures are now caught as full PHP throwables in the RSS conversion pipeline, which means one bad inbound row is logged and skipped cleanly instead of aborting the surrounding update run.

2026-04-08

OpenAI access-request admin pages - missing-table environments now fail soft instead of crashing

  • /users, /users/{id}/edit, /keys/mine, and /admin/openai now handle a missing openai_access_requests table gracefully instead of throwing a SQL exception.
  • The OpenAiAccessRequest model is now explicitly bound to the real openai_access_requests table name, which also fixes environments where Laravel would otherwise infer the wrong open_ai_access_requests name.
  • When that migration has not been run yet, the affected pages now stay usable and show an admin-facing warning that the request queue is temporarily unavailable.

2026-04-07

RSS Watch Feed Q&A - keyword extraction is now stricter and more name-aware

  • Feed Q&A now asks OpenAI for a stricter JSON keyword list ordered from the most specific phrase to broader supporting search terms before database narrowing runs.
  • Clear multi-word entities such as full personal names are now prioritized as phrases, which reduces cases where a broad first-name-only match pulls in the wrong person.
  • Generic question filler such as har, du, något, spännande, with, about, and similar words is now filtered out before SQL-backed retrieval, even if those words appear in the original question text.
  • The non-AI fallback extractor now preserves stronger phrase candidates (such as quoted phrases and proper-name sequences) and only broadens with more specific trailing tokens like surnames.

2026-04-06

DNSBL token info - live inspection now accepts any non-empty token string

  • GET /api/dnsbl/token/info no longer rejects non-empty token strings purely because they fail a local length/format check.
  • The endpoint still returns 401 when no token is supplied, but otherwise now performs a real lookup before answering.
  • If the supplied value is not a DNSBL write token but does match another Tools token/provider (for example a normal Tools bearer token), the API now returns an explicit diagnostic response instead of a generic format failure.
  • The WordPress DNSBL plugin's Check token permissions button now always asks the API directly for that answer, so operators can see what the Tools backend recognizes instead of being blocked by plugin-side guessing.
  • When that other matched token belongs to an active admin-owned Tools token, the API now reports the token as having automatic effective DNSBL access through the same X-Dnsbl-Token flow, with full effective permission fields in the success payload instead of the old "not a dedicated write token" wording.
  • The WordPress plugin settings page now mirrors that model with one visible DNSBL / Tools API token field instead of presenting a split token story in the UI.

SocialGPT Facebook dashboard - returning-customer insights are richer

  • The Facebook admin-activities dashboard now exposes a top-10 list of returning customers directly under the returning-customers chart.
  • The ordinary activity by outcome chart now has an extra checkbox that can overlay a Returning users series in the same graph.

My Profile + Social Media Tools - direct profile shortcut and stricter repeat-rejection counting

  • Users with access to /admin/social-media-tools can now open that dashboard directly from My Profile instead of having to discover it only through My API Keys.
  • The Facebook moderation dashboard now treats the former “returning customers” metric as people rejected more than once.
  • The repeat-user chart, summary cards, and optional activity overlay are now all aligned to that stricter rejection-only definition.

User security / OpenAI access - online users, feed probing, bans, and approval-first AI access

  • /admin/security/online-users now infers authenticated web users from the session payload and recent tracked page visits when the database sessions.user_id field is empty, so logged-in users no longer look like guests just because the default auth guard is api.
  • The same admin page now includes an Expand all visitors toggle so every tracked session group can be opened at once.
  • RSS feed auto-analysis in /rss now uses one consistent explicit probe User-Agent instead of mixing a self-identifying Tools string with generic Mozilla/5.0 requests.
  • The admin user editor now separates Ban user from Delete user. Banning can add email/IP bans, drop active sessions, revoke OpenAI access, and remove the user's Tools AI bearer token without forcing immediate account deletion.
  • OpenAI access is now approval-first for regular users: /keys/mine includes a request form, /admin/openai includes the review queue, and non-admin AI usage now requires real approved access instead of only inheriting the existence of a daily budget cap.

2026-04-05

RSS Watch analytics views - localhost links now follow the active public host again

  • Cached analysis markdown on /feed, /feed/c/{categorySlug}, /feed/cards/{categorySlug}, /rss, and /feed-admin now rewrites accidentally stored localhost/loopback links to the current public host before rendering on screen.
  • Category/site analytics generation and retranslation now also normalize those host references before saving, so future cached analyses are less likely to keep stale localhost URLs.
  • The main /feed category overview now refreshes long-analysis overflow detection when a collapsed category is opened, restoring the Read more button there.
  • Public RSS article links now also absolutize stored relative source paths against the owning feed's real_url/url, so /out/{contentId} no longer redirects into path-only links when a source only stored /foo/bar.

SocialGPT Chrome extension - 1.2.15 fixes reply-language regression after runtime localization

  • The extension now keeps popup/config UI translations separate from the actual AI responder defaults and language selectors.
  • This fixes the regression where some users with Swedish browser UI could end up getting Swedish-biased replies even after choosing another answer language.
  • Localized default responder-profile/test-question text is no longer written into the editable fields as real stored AI settings.
  • Affected users now get an automatic repair path when the extension reloads their stored responder profile.
  • The popup/options page now includes a separate Extension language selector, and the on-page SocialGPT UI now follows that same setting for Toolbox chrome, floating buttons, fact-check actions, and context-menu labels.

Per-feed publishby name mappings for author IDs

  • Added a new RSS-side mapping store for unfinished publishby values (for example numeric or legacy IDs) keyed by urlid + publishby, so the same raw value can safely mean different people in different feeds.
  • The RSS editor now includes a per-feed Authors page where operators can manually map raw publishby values to a real display name.
  • Resolved author names now flow through public feed/article views, category cards, edited-post listings, Feed Q&A context/retrieval, and /api/rss/feed/{site} readers.
  • API/feed readers now receive the resolved display name while still preserving the original raw value as additive metadata for clients that need both.

Feed Q&A - fallback keyword extraction now skips generic filler terms

  • When OpenAI keyword extraction is unavailable and Feed Q&A falls back to plain token extraction, generic words like article, latest, feed, discussion, nyheter, and similar broad labels are now filtered out before database narrowing.
  • This keeps fallback retrieval more anchored to actual entities/names from the question instead of accidentally widening the search with noisy generic terms.

SocialGPT Chrome extension - config page now mirrors the popup settings

  • The extension's larger config/options page now exposes the same editable settings as the popup, using the same autosave/storage behavior.
  • The old scaffold copy on that page was removed, including the stale sc4a-insights companion-module note.
  • The config page is now intended as the clearer full-size settings surface, while the popup remains the compact quick editor.

SocialGPT Chrome extension - popup and config page now localize dynamically

  • The popup and config/options page now switch UI language dynamically at runtime instead of being English-only.
  • Swedish is now supported explicitly and is selected automatically when the browser UI prefers Swedish, with English fallback for missing keys.

SocialGPT Chrome extension - bearer token fields now validate inline

  • Popup and config/options page token fields now show a lightweight inline spinner and accepted/rejected confirmation while the bearer token is checked against Tools.
  • Added a lightweight extension API endpoint for this flow: GET /api/social-media-tools/extension/validate-token.

SocialGPT Chrome extension - advanced mark-mode context on the config page

  • The extension config/options page now includes a new Advanced mark-mode context section for Toolbox users who need more traceable marked blocks.
  • Default behavior is unchanged: marked context still uses compact [1], [2] numbering unless the user opts in.
  • Advanced users can now add generated mark ids (for example tn-mark-2), richer element descriptors, and optional broader extraction modes such as one parent up or one parent up + direct child scan.
  • When a richer mark-label mode is enabled, active marked elements also show a visible local badge on the page so the DOM block can be matched back to the context shown in Toolbox.

SocialGPT Chrome extension - popup Toolbox shortcut, draggable panel, and iframe-aware capture

  • The popup now includes Open Toolbox in active tab, which gives users a direct fallback on pages where the normal context-menu flow is unreliable.
  • If the current page already has a live text selection, that popup shortcut imports the selection directly into Toolbox instead of opening an empty panel.
  • The Toolbox panel header is now draggable so the panel can be moved away from crowded page UI.
  • The Toolbox close × control now works again after the draggable-header change.
  • Selection-overlay timing was hardened so Open Toolbox and Verify fact appear more reliably after short direct selections and double-click selection gestures.
  • Advanced mark-mode extraction now also supports whole current frame/document text, and the extension now runs its content scripts in nested frames (including matching about:blank child frames) where Chrome allows it.

RSS scraper idle wait hints + timestamped scraper logs

  • GET /api/rss/urls now returns additive scraper-idle metadata (idle) when no URLs are currently due for the requesting agent.
  • The idle payload includes a reason, wait_seconds, wait_minutes, next_poll_at, and the next expected URL identity so scraper workers can see when work is likely to resume.
  • The bundled projects/scraper/scrape.php runner now writes timestamped status lines and tries to persist them to SCRAPE_LOG_FILE or /var/log/tools-scraper/scrape.log when writable.
  • No-work runs now exit cleanly instead of being treated as failures, and the scraper logs the backend-provided wait hint when available.

RSS Watch docs front matter + crawler identity intro

  • Fixed a front matter delimiter typo in docs/en/rsswatch.md that caused metadata to render as visible text in docs output.
  • Added a new top intro segment that explains what RSS Watch is (public reader + ingestion pipeline) and documents active scraper/trigger User-Agent patterns.

Scraper User-Agent modernization (no reporting key change)

  • Replaced legacy UA format RssWatch-2.0/<hostname> in scraper runners with crawler-style RSSWatchBot strings including a docs URL.
  • Agent attribution/scheduler reporting remains unchanged because it still uses agent_id=<hostname> in API calls.

Public category cards - analyses first + articles/history restored

  • /feed/cards/{categorySlug} now renders AI analyses first and then the category article stream below (instead of analyses-only focus).
  • Analysis selection is now balanced per period: one selected variant each for daily, weekly, monthly, and yearly when available.
  • Article rows on cards view now include the same version-history and diff behavior as the classic category view.
  • The cards page presentation is now more editorial/news-magazine styled, with a masthead-like category intro and a lead-story-first article layout.

Feed Q&A - author-first publishby retrieval for person-targeted questions

  • Feed question retrieval now detects explicit person targets in the question (for example "articles Andreas Magnusson wrote").
  • When a person target is detected, SQL retrieval now runs a first-pass filter against rss.content.publishby to reduce cross-author mixing.
  • If no results are found in the author-filtered pass (for example legacy rows with ID-style bylines), the system automatically falls back to the broader keyword retrieval path.
  • Retrieval metadata now records whether author filtering was requested, applied, and whether fallback was used.

2026-04-04

Feed Q&A – article links in AI answers

  • The AI analysis context now includes article links (link field) for recent entries and version history articles.
  • When citing specific articles, the AI will format them as markdown hyperlinks [Title](url) if the article URL is available in the context.
  • The inline answer panel on /feed now renders markdown links as clickable hyperlinks.
  • The session-flash answer (shown after page reload) also renders using full markdown, consistent with the history page.
  • Hidden feeds remain protected: their article links are never passed to the AI context.

Feed Q&A – data accuracy warning

  • The Ask-AI panel on /feed now shows a small notice below the description reminding users that answers are based on available indexed content, that some sites may have limited history, and that answers are not always complete or fully accurate.
  • This warning is fully translated in all five supported interface languages (EN, SV, DA, NO, FI).

Feed Q&A – progress detail phrases now translatable

  • Progress detail messages shown during analysis (e.g. "Refining the evidence set…", "Preparing search terms…") are now picked up from the phrase translation system.
  • Swedish and the other supported languages now show their own translated text for these detail messages instead of the English fallback.

Feed phrases – missing translations filled in

  • Corrected missing phrase translations for all supported languages:
    • Swedish: ask_error_required, ask_error_captcha, ask_error_generic, site_analytics_title (were falling back to English).
    • Danish: ask_error_required, ask_error_captcha, ask_error_generic.
    • Norwegian: ask_asking, ask_error_required, ask_error_captcha, ask_error_generic.
    • Finnish: ask_error_required, ask_error_captcha, ask_error_generic, site_analytics_title.

DNSBL token info endpoint

  • New GET /api/dnsbl/token/info endpoint lets any client check the permissions of a configured DNSBL token without needing a logged-in session.
  • Pass the token via X-Dnsbl-Token header or ?dnsbl_token= query parameter.
  • Returns the token's name, status, add/delete scope flags, admin status, approved-at timestamp, and the list of zones the token may operate on.
  • Works for tokens in any state (active, pending, revoked), so clients can diagnose exactly why a token may not be working.
  • The WordPress DNSBL plugin admin panel now shows a "Check token permissions" button next to the configured token field. Clicking it performs a live check and renders the permission table inline without reloading the page.

WordPress DNSBL plugin - infinite-loop fix in token sanitize callback

  • Fixed a fatal stack overflow that occurred when saving settings with a write token configured. The sanitize_option callback was calling update_option which re-triggered the same filter, producing an infinite recursion. Added a static re-entrancy guard to prevent this.

DNSBL write API - dry-run acknowledgements for safer delisting/listing tests

  • POST /api/dnsbl/records/add, /delete, /update, and /bulk now accept optional dry_run.
  • Dry-run requests validate token scope, payload, and zone permissions, then return success acknowledgement without applying DNS writes.
  • Responses include dry_run and dry_run_accepted markers to confirm that the backend accepted simulation mode.

WordPress DNSBL plugin - shortcode-based delisting form

  • The DNSBL WordPress plugin now supports a page-embed shortcode ([dnsbl_removal_form]) with HTML/AJAX submission through WordPress backend proxy.
  • The form supports both local WordPress dry-run and Tools API dry-run acknowledgement mode for safer pre-flight testing.

Feed Q&A - richer per-question analysis metadata for broad scopes

  • Feed question runs now persist a richer analysis metadata envelope per question, including scope mode, broad-scope indicator, context size, block metadata, and retrieval strategy details.
  • This is especially useful when no category/site focus is selected or when many sites are included, where block-based analysis can accumulate larger evidence snapshots.

Feed Q&A history - "How this answer was analyzed" box

  • /feed/user-questions now shows a compact analysis box per completed row.
  • The box includes analysis method (single-pass vs block/chunked), scope/retrieval details, and extra search terms used before final answer generation.

Public /feed categories – compact cards page and per-period analysis cap

  • Added a new category cards route: /feed/cards/{categorySlug} for a compact, card-style AI analysis view.
  • The classic category route /feed/c/{categorySlug} remains unchanged and now includes a shortcut link to the cards page.
  • Category AI variants on /feed now show a max of 3 items per period (daily/weekly/monthly/yearly), with a direct "Show all" handoff to the cards page.

RSS analytics generation – deeper scheduled analysis via segmented blocks

  • Category and site analytics generation now supports segmented block passes before final synthesis when period datasets are large.
  • This applies to scheduler/CLI/API-generated daily, weekly, monthly, and yearly analytics, producing deeper and more stable outputs on larger periods.

Public feed search – byline/author matching now includes publishby

  • Public /feed free-text search now includes content.publishby in fallback LIKE matching and ranking, improving person-name lookups.

RSS import – creator/author metadata is now captured more reliably

  • RSS/Atom import now stores creator/author metadata in content.publishby when feeds expose it (including more feed variants where this field previously came through inconsistently).
  • Added an RSS DB migration that creates content.publishby when missing, so the byline can be persisted instead of being dropped.
  • Duplicate suppression now explicitly ignores byline-only changes: when link + title + description + content are unchanged, a new row is not created just because publishby appeared, disappeared, or changed.

RSS editor + Feed Admin – quick category switching via dropdown (AJAX)

  • The category field in /rss row editing now uses a dropdown with existing categories for faster reassignment.
  • A Custom... option is available when you need a new category value; custom values still save inline via AJAX.
  • Site cards in /feed-admin now include the same quick category dropdown/custom input, so category changes can be done directly from Feed Admin without opening /rss.

RSS editor – add-feed now blocks duplicate source rows

  • The /rss add-feed form now checks existing feed rows before insert.
  • If the submitted URL or Real URL already matches an existing feed (including URLReal URL cross-matches), the insert is blocked and the matching row details are shown in a warning.

RSS scraper – Centerpartiet synthetic <time> text now prefers ISO timestamps

  • The Centerpartiet special scraper now writes ISO timestamp text inside the synthetic <time class="tn-date"> node whenever an ISO value exists.
  • This makes downstream XPath imports less dependent on localized Swedish date text when the importer reads the time node value directly.

RSS scraper – Centerpartiet text extraction now normalizes non-UTF-8 input more safely

  • The Centerpartiet special scraper now normalizes fetched HTML and extracted text to UTF-8 before script parsing, text cleanup, and DOM fallback parsing.
  • This reduces the risk of Swedish characters being mangled when the source page or transport bytes are not clean UTF-8.

RSS import – XPath pass-through no longer degrades already-UTF-8 text

  • The RSS XPath import path no longer runs utf8_decode(...) on rendered row values before insert.
  • This fixes a corruption path where already-UTF-8 titles, descriptions, and other extracted fields could lose Swedish characters during import from synthetic HTML sources.

2026-04-03

Public /feed – category cards now default to collapsed and support saved accent colors

  • Category cards on /feed now start collapsed by default when no previous preference exists.
  • The page now remembers only the last open category card in a cookie and restores that card on the next visit instead of reopening multiple old sections.
  • Feed Admin category settings now support a per-category public card accent color. The public feed softens those colors automatically so dark text remains readable in light mode.

RSS editor – WordPress auto-detect now falls back cleanly when wp-json is protected

  • The add-feed auto-detect step now treats 401 and 403 responses from /wp-json/wp/v2/posts as unavailable WordPress REST endpoints.
  • When that happens the analyzer continues with normal feed discovery (<link rel="alternate">, /feed, /rss.xml, etc.) instead of assuming the WordPress REST endpoint can be used.

RSS scraper – Centerpartiet news listing support via AppRegistry synthetic HTML

  • The scraper project can now special-handle centerpartiet.se news pages before the normal RSS/XPath import step.
  • Instead of scraping only visible HTML, it now extracts the correct AppRegistry.registerInitialState(...) payload dynamically from the page's bootstrap scripts and converts the item list into stable synthetic HTML.
  • The synthetic HTML is designed for manual sitetype=xpath rules, which makes Centerpartiet's news listing easier to ingest with the existing importer without teaching /api/rss/update a new raw-script format.
  • If the script-state extraction fails, the scraper falls back to parsing rendered <article> nodes from the page.

RSS editor XPath Lab – legacy parser compatibility preview for href/attribute issues

  • /rss/xpath-lab now shows both a fast lab preview and a second Production / legacy scraper compatibility preview that runs the same XPath renderer used by live imports.
  • The compatibility panel highlights a common failure mode where non-value extractors such as href are silently skipped because the legacy renderer expects matching extractor keys in pipeline[3].
  • The lab now warns about that mismatch explicitly and recommends a safer pattern: extract attribute nodes directly (/@href, /@src, /@datetime) and map them with "value".
  • The OpenAI rule-generation prompt for XPath feeds was updated to prefer that safer attribute-node pattern so generated rules are more likely to work in the live scraper without manual fixes.

Feed Q&A – live progress feedback for long-running questions

  • /feed and the site-specific feed question panel now switch the submit button into a visible spinner state while a question is being analyzed.
  • The browser now polls dedicated web status endpoints so the UI can show the current backend phase, elapsed time, pre-analysis pass counts, and context-block progress for large questions.
  • This makes long-running multi-pass questions feel much less "stuck", especially when the backend is still working through sequential block analysis.

Feed Q&A – latest-answer box now shows broadened related search phrases

  • When /feed broadened question retrieval with extra related phrases, the latest-answer panel now shows those phrases in a readonly helper box below the answer.
  • The wording is intentionally user-facing and explains that the phrases were used to find more relevant posts before the final answer was written.

RSS Watch – permalink entry history hardened for pathological duplicate links

  • /feed/entry/{contentId} now scopes history lookups to the same feed (urlid + link) instead of scanning by link alone.
  • Entry history rendering is now capped to the newest 200 stored revisions per entry page, while still showing the total revision count.
  • A new RSS DB index was added for the permalink history query so duplicate-heavy links load much faster and are less likely to hit PHP max-execution time.

RSS Watch – AJAX free-text search on /feed

  • Added a new Search posts box at the top of /feed.
  • Search runs asynchronously and keeps the user on /feed while looking up matching feed entries.
  • Results are grouped by category and list matching posts with shortcuts to open the entry, the feed, or the original source.
  • While search results are open, the normal feed/category/site browser is temporarily hidden so the page stays focused on the result list.

Social Media Tools extension – Chrome Web Store compliance restructure (v1.2.12)

  • Permissions restructured: default host_permissions narrowed to the Tools API servers only (tools.tornevall.net, tools.tornevall.com). The <all_urls> host permission moved to optional_host_permissions.
  • Static content script injection narrowed: content scripts now run by default only on Facebook, SoundCloud, and X / Twitter. There is no passive injection on unrelated pages in baseline mode.
  • Global browser-wide AI mode added: users with a valid Tools token can enable a "Global browser-wide AI mode" toggle in the extension popup. Enabling it triggers Chrome's native permission dialog for <all_urls>, then registers content scripts on all sites. Disabling the toggle unregisters the scripts and removes the optional permission.
  • Context menu fallback injection: the "Open Toolbox" and "Verify fact" context menu items now work on any page — the background injects scripts temporarily via activeTab when a menu item is clicked on a page without a running content script.
  • CHROME_WEB_STORE_PRIVACY.md added to the extension source directory as the authoritative Chrome Web Store submission document.
  • docs/en/socialgpt-chrome.md added as end-user documentation for the extension permission model, features, and storage.
  • Runtime fix: resolved a panel-generation regression that could throw resetReplyTransientFieldsButKeepContext is not defined in extension runtime logs after AI responses.

Documentation - Public privacy policy pages for Chrome Web Store

  • Added a dedicated public privacy policy page in English: /docs/en/privacy-policy.
  • Added a matching Swedish privacy policy page: /docs/sv/privacy-policy.
  • Updated docs index pages (/docs/en and /docs/sv) with a visible legal section and direct privacy-policy links.
  • Policy text now explicitly covers extension authentication token usage, user-submitted content for AI/fact-check requests, remote-code statement, and no-sale/no-advertising data sharing position.
  • Policy text now also states that users can request purge/deletion of their stored data at any time.

Documentation - docs autodiscovery for public markdown pages

  • DocsController now auto-discovers public docs slugs from language folders (docs/en/*.md, docs/sv/*.md) instead of a manually maintained allowlist.
  • New markdown pages are now routable/listed without controller edits.
  • Internal docs are now hidden by front matter flags (visibility: admin or access: internal) instead of a hardcoded slug denylist.

Feed Q&A - keyword-first retrieval and context orchestration

  • /feed question answering now starts with a keyword extraction pass: OpenAI derives compact search terms from the user question before full context assembly.
  • Backend retrieval now narrows candidate rss.content rows by keyword matches in title/description/content and only then expands context blocks + version history.
  • Period-aware retrieval caps now prefer more generous narrowing for shorter windows (daily/weekly/monthly) and stricter caps for yearly to keep prompts bounded.
  • Feed Q&A now includes a new all_time period option for whole-database analysis, so questions can search across the full stored RSS history instead of stopping at the current year window.
  • Keyword expansion is now more scope-aware: planner-suggested extra terms must stay anchored to the original question and active category/site scope, and generic drift terms are filtered out before SQL retrieval.
  • Users can now choose keyword-search breadth per question (strict, balanced, expansive), while admins can define the default mode used on /feed and site-specific feed question panels.
  • Feed-question orchestration was refactored so answer settings/meta and context-block rendering are handled through clearer helper paths, making future multi-pass question flows easier to extend.
  • Keyword retrieval now uses staged recall (strict fulltext -> relaxed fulltext -> LIKE fallback) and merges ranked hits, which improves narrow-topic questions that previously returned too few matches.
  • Feed Q&A now supports a bounded, configurable pre-analysis loop before the final answer, so OpenAI can suggest what additional SQL-backed retrieval to run for up to 5 passes by default before the final report is generated anyway.

2026-04-02

RSS editor (/rss) – add form URL mapping + category selector workflow

  • The Add URL form now keeps the originally entered page URL as real_url and uses the auto-detected feed endpoint as url.
  • The 🤖 Auto action now fills URL with the detected feed URL while preserving the typed source page URL in Real URL.
  • Category input now supports a combined workflow:
    • choose an existing category from a dropdown, or
    • write a custom category in a free-text field.
  • If the custom category field is filled, it overrides the dropdown value. If custom is empty, the dropdown selection is used.

Feed Q&A – sequential block analysis for large contexts

  • Large feed-question contexts are now analyzed in small JSON blocks sent sequentially instead of one oversized prompt.
  • The backend now keeps a rolling summary between block requests so later OpenAI calls retain the important findings from earlier blocks without resending the entire dataset.
  • Version-history-heavy questions therefore remain broader and more evidence-preserving while avoiding huge single-request token spikes.
  • Final answers are now synthesized from the rolling multi-block evidence summary rather than only from one monolithic context payload.

Social Media Tools extension – selected-text Open Toolbox button next to Verify fact

  • When text is selected and no Toolbox panel is open, the extension now shows an extra floating Open Toolbox button next to Verify fact.
  • The new button imports the selected-text context directly into the Toolbox panel.
  • Both selection buttons can now be dragged away from the text and reset with double-click / Esc, matching the movable floating-button behavior used elsewhere in the extension.

2026-04-01

XPath Lab – pipeline rule testing and AI suggestions

  • The XPath Lab at /rss/xpath-lab now supports pasting and testing the full 5-element pipeline / elements JSON format used by sitetype=xpath feeds.
  • Results panel shows which row selector matched, how many rows were extracted, and the extracted field values per row.
  • When a pipeline is provided but finds 0 rows, the lab auto-triggers AI suggestions (OpenAI generates new rule candidates from the HTML snippet).
  • Ask AI checkbox can also trigger rule generation on demand even when results were found.
  • AI-generated rules are shown with row selectors and output field preview; a "Fill pipeline field" button copies them back into the pipeline textarea for immediate re-testing.
  • The pipeline textarea now includes a placeholder showing the complete 5-element format with an annotated example.

Feed Q&A – version history now shows more versions per article

  • Raised VERSION_SAMPLE_LIMIT_BROAD from 4 to 6 and VERSION_SAMPLE_LIMIT_NARROW from 6 to 12.
  • Effect: articles with up to 12 versions (in narrow/site-focused mode) now show all versions, not a sample. AI analysis can therefore see the full edit history without the "historiken är trunkerad" note.
  • For articles with more versions than the limit, representative samples are still selected across the full history span.

Documentation – RSS Watch API reference expanded

  • docs/en/rsswatch.md and docs/sv/rsswatch.md now include a full API Reference section covering all /api/rss/* endpoints with request/response schemas, auth, and parameter tables.
  • New section: elements / Pipeline JSON format with a complete annotated example of the Socialdemokraterna-style XPath rule set.
  • New section: Feed Q&A API with request/response details and focus selector behavior.
  • New section: Version history in context describing the version_history.articles JSON structure returned to OpenAI.
  • XPath Lab section updated to document the pipeline testing workflow.

RSS public feed – shareable category pages (/feed/c/{categorySlug})

  • Added a new public category reader page at /feed/c/{categorySlug}.
  • The page aggregates posts from all visible feeds in that category and keeps the same post-level history/diff behavior as regular feed pages.
  • Category analytics cards on /feed now include a Share button (next to admin actions) that opens the matching category page directly.
  • Category page metadata now also exposes a category API endpoint shortcut (/api/rss/feed/{categorySlug}).

2026-03-31

RSS editor – new visual XPath Lab

  • Added a new editor-side XPath tool at /rss/xpath-lab (permission: rss) for debugging and building XPath rules from pasted HTML snippets.
  • The page shows a visual DOM outline with per-node XPath, key attributes, and text previews to make selector discovery faster.
  • Added optional XPath execution on the same page (xpath_query) with immediate match previews (node path, tag, text, and snippet).
  • Added a SimpleXML-style XML preview block so users can inspect the normalized parser tree before writing elements rules.
  • Added quick navigation from /rss to the new lab and a helper link in the auto-detect HTML-context section.

Feed questions – richer version snapshots + navbar cleanup

  • Version snapshots now include text evidence: version_history.articles[*].versions[*] now includes description and conditional content excerpts (when content is present), not title-only rows.
  • Payload remains compact: excerpts are trimmed and still pass through context-budget trimming to avoid oversized AI requests.
  • Top navbar cleanup: removed My To Do, Whisper, and Whisper Admin shortcuts from the global navbar.
  • Service discovery unchanged: Microsoft To Do and Whisper remain available via /services (permission-aware cards).

Feed admin – configurable default page size for /feed

  • Added a new feed-admin global setting for Public /feed default feeds per page.
  • The public feed page now uses this saved value when no per_page query parameter is provided.
  • Explicit query override still works (/feed?per_page=...) and values remain clamped to safe bounds.

RSS entry admin – per-post import ignore + hard one-row purge

  • Added entry-level admin controls on /feed/entry/{contentId} for noisy posts:
    • Ignore this post at import (feed-scoped or global), implemented as a link block rule.
    • Allow import again to remove that ignore rule.
    • Purge all except one latest row for the link (hard reset).
  • Existing cautious-mode dedup purge is still available and now works alongside import-ignore for problematic links that keep cycling.

Feed user questions – JSON context, version history, period fixes

  • Timeout fix: Version history context for site-focused questions is now cached (5-minute TTL per site), eliminating the 30-second execution timeout on /feed/{urlid} question panels.
  • Monthly period corrected to 31-day rolling window: Monthly feed context now covers 31 days (today minus 30 days) instead of the previous 30-day window.
  • Structured JSON context for site-focused questions: When a question targets ≤10 specific sites, the AI context is now delivered as structured JSON with the following top-level keys:
    • context_type, analysis_note, period_metadata – scope metadata
    • feeds – list of matching feeds with id, title, category, url
    • recent_entries – period-filtered entries with version count annotation
    • version_historyperiod-independent list of all edited articles with every stored version (v1=oldest, vN=newest), title change notes between adjacent versions, and permalinks
    • top_terms, stats – frequency summary and scope counters
  • Version history is period-independent: version_history.articles covers ALL recorded time regardless of the selected period (daily/weekly/monthly/yearly), so the AI can answer questions about article edits spanning any date range.
  • Prompt updated for JSON context: When context is JSON, the AI is explicitly instructed to use version_history.articles to answer questions about edits, content changes, title evolution, and publishing history.
  • Entry limit reduced for narrow-focus queries: When version history is the primary context, the period-entry sample is capped at 25 items to reduce query load.
  • RSS query performance indexing: Added new RSS DB indexes for buildContextSnapshotFresh() and version-history lookups to reduce latency/timeouts under scoped questions:
    • content(urlid, pubdate, contentid) for period-window entry sampling
    • content(urlid, link, content_hash) for version-count grouping
    • content(urlid, link, pubdate, contentid) for version-history row retrieval
    • urls(deleted, is_hidden, category, title, urlid) (or urls(deleted, category, title, urlid) where is_hidden is absent)
  • Prompt size protection added: Feed-question JSON context is now compacted before sending to OpenAI to avoid TPM/input-limit failures.
  • Version history is now sampled, not fully expanded: Each edited article keeps total version_count, title_change_count, first_published, last_published, and a compact sampled versions[] list instead of every stored revision.
  • Context budget trimming: Large scoped questions now trim low-value sections first (debug_summary, domains, some sources/terms/feeds/recent entries) and then reduce version_history.articles until the JSON payload fits the configured character budget.

2026-03-30

Whisper – Permission-gated transcription queue (web + API + admin)

  • Added a new Whisper transcription tool for authorized users at /whisper (permission:whisper.use).
  • Added admin queue console at /admin/whisper (permission:whisper.manage) with all-user visibility and run-now trigger.
  • Added authenticated API endpoints under /api/whisper/*:
    • GET /status
    • GET /jobs
    • POST /jobs
    • GET /jobs/{jobId}
    • POST /run-now (manager/admin)
  • Added queue persistence table whisper_transcriptions with stage/progress fields, retry metadata, transcript storage, and owner linkage.
  • Added scheduled queue processing (whisper:process --limit=1, every minute) plus host config/env support (WHISPER_*) for binary path, timeouts, retries, and SSL behavior.

Facebook moderation charts – stronger bar colors

  • Increased chart palette contrast and fill intensity for moderation and returning-customer series.
  • Increased series border weight so bars/lines remain easier to distinguish in dark theme and screenshots.

Microsoft To Do – User web UI + authenticated API with bidirectional sync

  • Added a new personal Microsoft To Do integration for authenticated users at /settings/integrations/microsoft-todo.
  • Users can now connect a Microsoft account, browse synced lists, create/edit/delete lists, and create/edit/delete tasks directly inside Tools.
  • Added authenticated API endpoints under /api/microsoft-todo/* for status, sync, list retrieval, task retrieval, and list/task mutations.
  • Sync now runs in both directions: Tools changes are pushed to Microsoft To Do, and remote Microsoft To Do changes are pulled back into Tools.
  • Added a local mirror for lists/tasks plus a scheduled sync job (microsoft-todo:sync) that runs every 15 minutes.
  • Added documentation, OAuth route reference, services discoverability links, and Android API-contract notes for the new endpoint family.

Social Media Tools – SocialGPT-only dashboard history + complete history page

  • The AI prompt and response history panel on the Social Media Tools dashboard now shows only SocialGPT history rows.
  • SocialGPT scoping uses request-log metadata (source=social_media_extension, SocialGPT tool slug, and feature_slug=gpt) for both list and detail views.
  • Added a separate admin page for complete request history: /admin/social-media-tools/audit/complete.
  • The complete history page supports filtering by source, tool slug, feature slug, user, status, model, and request mode.
  • Existing log creation/storage is unchanged: all request logs are still persisted as before.

RSS – Restored inbound processing and feed generation helpers

  • Restored missing internal helper methods in RssController that had caused runtime failures during RSS inbound conversion.
  • Fixed /api/rss/update crashes caused by missing canQuery() and handleWordPressRestApi() methods.
  • Restored the related content deduplication, import-rule filtering, JSON/XML conversion, and feed-generation helper chain used by RSS processing.
  • This also restores the controller-side helper path used by /api/rss/feed/* and analytics feed selectors after the accidental method removal.

2026-03-29

Public Feed – Language Switcher Translation Fixes

  • Fixed missing translations in the Ask a question panel across all languages when switching the feed language.
  • JS submit handler now uses the active feed language for all inline status messages, button text (Asking…), the re-rendered answer box labels (Latest answer, Q:, A:), success text, and error messages — instead of always showing English.
  • Added five new translatable phrase keys available in all languages (en, sv, da, no, fi):
    • ask_error_required – shown when the question field is left empty.
    • ask_error_captcha – shown when the captcha has not been completed.
    • ask_success_answered – shown after a successful answer is received.
    • ask_error_generic – shown on network/server errors.
  • Fixed missing ask_asking phrase in the Norwegian (no) language entry.
  • Site-specific analytics period headings (Daily, Weekly, Monthly, Yearly) and Read more / Show less buttons now respond to the language switcher.
  • Added site_analytics_title phrase key (site-specific AI analysis summary label) to all languages.

nginx – TLS Hardening

  • Updated TLS configuration in nginx/tools.tornevall.net.conf to remove deprecated TLSv1 and TLSv1.1.

  • Now only TLSv1.2 and TLSv1.3 are accepted.

  • Updated cipher suite to modern ECDHE/CHACHA20/AES-GCM set and set ssl_prefer_server_ciphers off (lets clients negotiate the best mutual cipher).

  • Updated scraper URL selection so always=0 is now agent-aware instead of globally gated by urls.lastscrape.

  • Each scraper agent (agent_id) now tracks per-URL claim state: when it last saw a URL and when it is next allowed to see it.

  • Interval checks for always=0 now use the URL's configured readinterval together with that agent-specific seen state.

  • always=1 now returns all non-deleted/non-noscrape RSS URLs (no interval filtering).

  • Added backend persistence table rs_agent_url_seen (RSS DB) for per-agent URL scheduling state.

Feed Questions – Period Inference and Empty-Answer Failover

  • Feed question processing now infers context period from question wording (today/day, week, month, year) when no explicit period is selected.
  • Saved question metadata now includes period source (user_selection, question_text, or admin_default) for traceability.
  • Added empty-answer failover in the feed question service: if the primary model returns an empty answer, it retries once with gpt-4o-mini.
  • If the final answer is still empty, the request now returns an error instead of showing a false "Question answered" success state.

Facebook Statistics – Action Type Filter for Charts

  • Added an Action types shown multi-select filter to the Facebook moderation dashboard charts panel.
  • Select one or more outcome types (Approved, Rejected, Removed, Edited, Added, Blocked, Revoked, Observed) to narrow the charts to only those event categories.
  • Leaving the selection empty shows all types (previous default behavior preserved).
  • The filter applies across all three charts (moderation activity, outcome distribution, returning customers).
  • Active outcome filter is shown in the filter status label (e.g. "Types: Approved, Rejected").
  • The outcome type selection is preserved in chart share URLs.
  • Fixed dashboard behavior so selecting action types and pressing Apply filters / Show charts stays in the AJAX flow (no full page reload).
  • Fixed persistence of selected action types so the multi-select no longer clears itself after refreshes and chart rendering.

Public Feedreader – Language Switcher (/feed)

  • Added a browser-aware language switcher to the Tornevall Networks Feedreader header card on the public /feed page.
  • Supported languages: English (EN), Swedish (SV), Danish (DA), Norwegian (NO), Finnish (FI).
  • Language is detected automatically from the browser's navigator.languages preference on first visit, then persisted in localStorage.
  • Flag+code buttons in the header card let users switch at any time without a page reload.
  • All major UI phrases translate on the fly: page title, subtitle, the Ask-AI panel (labels, placeholders, buttons), column headers, category action buttons, analytics section, recent questions card, and read-more controls.
  • Default built-in phrases live in config/feed_phrases.php (editable as code).
  • Administrators can override individual phrases per language without deploying, using the new Phrase Editor section in Feed Admin (/rss-admin → 🌐 Public feed phrase editor).

Public Feedreader UI – Category Card Layout (/feed)

  • Analytics language dropdowns in each category card now follow the active feed language automatically — switching the flag in the header also updates all variant language selectors on the page.
  • On page load, analytics dropdowns initialise to the browser/stored language instead of always defaulting to English.
  • Category panel open/closed state is persisted in localStorage and restored on each visit, so the page opens where you left off.
  • Category headers are fully clickable for expand/collapse (keyboard accessible). The toggleCategory() function is now correctly wired.

Feed Questions – Default model and reasoning (/feed)

  • The default AI model for feed questions has been updated to gpt-5.4, which handles reasoning natively.
  • A new Output style / persona instructions field has been added to the feed question settings page (/feed/user-questions). Free-text, max 500 characters, injected directly into the prompt. Use it to enforce plain prose, suppress bullet lists, or shift the output personality without touching the underlying prompt profile.

RSS Inbound Queue Cleanup (/api/rss/update)

  • Updated inbound maintenance so rows marked handled=1 are now purged as soon as they are unlocked (processlock=0) during update runs.
  • Removed the previous one-day retention window for handled inbound rows to keep the inbound table lighter under sustained feed traffic.
  • Clarified lock-safe cleanup behavior so active rows (processlock=1) are not purged.
  • Added a configurable stale lock release window for inbound rows with processlock=1 via RSS admin (/feed-admin, alias /rss-admin) using setting key feed_admin.inbound.processlock_release_minutes.
  • Default stale lock release window is now 30 minutes (bounded to 5..720 minutes) and is applied to both handled and unhandled stuck inbound lock recovery.
  • Stale-lock recovery now evaluates lock age (when a row was lock-claimed) instead of only row creation age, reducing false unlocks for older rows that are actively being processed.
  • Lock claim/release handling now tracks lock timestamps so unlocked/handled paths clear lock age state consistently before cleanup.

Public Feedreader UI – Category Card Layout (/feed)

  • Reorganized the public feed list into clearer per-category card sections so each category is visually separated and easier to scan.
  • Kept category expand/collapse behavior and existing interactive controls (subscriptions, analytics variant selectors, read-more toggles, and AJAX actions) intact.
  • Preserved all existing feed rows and analytics data while improving readability with stronger card framing and category-focused visual hierarchy.
  • Category sections now start collapsed by default and use accordion behavior (opening one category collapses others) for a cleaner reading flow.
  • Category cards now expand/collapse by clicking the category header or arrow button (keyboard-accessible), replacing the separate Show details button.
  • Standardized category action button sizing in the card header to keep controls visually consistent.

Feed Questions – Default model and reasoning (/feed)

  • Updated the default feed-question answer model to gpt-5.4.
  • Feed-question requests now include reasoning_effort=medium for reasoning-capable models (gpt-5*, o1*, o3*, o4*).
  • Increased feed-question output token ceiling to support longer reasoning responses when needed.

2026-03-28

RSS Editor – Improved Auto-detect + AI Fallback + Blogspot Support

  • WordPress-first detection: The "🤖 Auto" button in the /rss editor now attempts the WordPress REST API (/wp-json/wp/v2/posts?per_page=100) before any other method. If the site serves valid WP-JSON posts the real_url is set to the REST API endpoint with ?per_page=100 (to retrieve more posts than the default RSS feed) and sitetype is set to wp. Falls through to standard RSS discovery if wp-json is disabled or unavailable.
  • HTML <link rel="alternate"> extraction: If WordPress REST API is not available, the editor now fetches the page HTML and parses all <link rel="alternate" type="application/rss+xml|atom+xml"> tags to discover the canonical feed URL declared by the site itself — avoiding guessing.
  • Blogspot/Blogger detection: Sites hosted on *.blogspot.* are now flagged in the auto-detect response. A warning is shown in the editor status line noting that Blogspot RSS output is minimal and that full-HTML scraping may be added in the future.
  • OpenAI AI fallback: If all structural methods fail (no WP-JSON, no <link rel="alternate">, no common feed pattern), the editor now calls OpenAI (gpt-4o-mini via the configured OpenAI provider) with a truncated snapshot of the page content and asks it to suggest a feed URL. The result is used as a fallback and clearly flagged with "🤖 AI-suggested URL – please verify" in the UI. Silently skipped if OpenAI is not configured.
  • Blogger default Atom feed (/feeds/posts/default) added to the common feed URL probe list.
  • Status display enriched: The analyze status line now distinguishes between AI-suggested, Blogspot, fully auto-detected, and fallback outcomes with colour-coded messages and an optional detail note.

RSS Extra Content Table (RSS DB)

  • New table extra_content added to the RSS database (migration 2026_03_28_100000_create_rss_extra_content_table).
  • Purpose: store supplementary scraped data for feeds that produce minimal RSS output (e.g. Blogspot), including full-HTML snapshots (html_blob) and screenshot file references (filepath).
  • Columns: urlid, contentid, link, content_hash, scrape_type, filepath, html_blob, scraped_at.
  • No scraper writes to this table yet; the schema is prepared for future extra-scraping workflows.

Feed Question Scope and Period (/feed)

  • Added a per-question Question period selector on /feed (daily, weekly, monthly, yearly) so each question can choose its own analysis window without changing global admin defaults.
  • Updated feed-question context generation to scale breadth by selected period (broader periods include larger context snapshots).
  • Added daily cache reuse for yearly feed-question context snapshots to reduce repeated heavy context assembly during the same day.
  • Updated focus-site selector behavior so the public Focus sites (optional) list excludes hidden feeds.
  • Updated default scope behavior so when no focus categories/sites are selected, question context can include all non-deleted feeds (including hidden) for broader analysis, while hidden feed links/keys are still not exposed in generated context text.

RSS Scraper + Ingest Diagnostics (/api/rss/data)

  • Updated scraper runtime progress output to include feed titles in addition to urlid and URL, improving operator visibility during batch runs.
  • Updated POST /api/rss/data response payload (received) so each row now also includes the feed title from urls.title.
  • Improved RSS/Atom conversion diagnostics with explicit per-inbound summary logging (candidates, converted, and skip counters) to make zero-entry outcomes easier to diagnose.
  • Added Atom/RSS entry-link fallback handling plus Google Alerts redirect normalization (https://www.google.com/url?...) to better preserve destination links during import.

2026-03-27

My Profile (self-service account settings)

  • Added a new authenticated My Profile page at /users/profile where users can update their own name and email.
  • Added optional password setup/change on the same page (current password required for sensitive updates when a password already exists).
  • Moved Google account linking/unlinking management from My API Keys to My Profile to keep sign-in settings in one place.
  • Added a new My Profile shortcut in the navbar for logged-in users.
  • Tightened navbar spacing/wrapping so the new profile link fits without overflow on smaller widths.

Stability fixes (RSS + MCU pages)

  • Fixed /online admin location visibility so tracked request URIs are matched more reliably against active sessions. Page-visit writes now tolerate missing optional columns during partial deployments, and online matching now falls back more safely for user/session/user-agent combinations.
  • Updated /online so authenticated admins/users with users permission now see the detailed location-aware online view on that same URL instead of the simplified public table.
  • Fixed RSS editor runtime error on /rss where legacy Blade flags ($hasProtectedColumn, $hasUseProtectedColumn) could be undefined if deprecated columns are absent.
  • Changed RSS admin access behavior so users without RSS admin permission are redirected to /feed when visiting /rss, /feed-admin, or /rss-maintenance-admin, instead of seeing login/403 pages.
  • Fixed RSS editor table column alignment on /rss by restoring row cells for protected and useProtected, preventing is_hidden/public_hash values from appearing under the wrong headers.
  • Updated RSS editor inline field is_hidden on /rss from a free-text input to a checkbox, with AJAX save now posting explicit 1/0 values.
  • /admin/jobs RSS Analytics Scheduler is now editable (enabled toggles + run_at per period) and includes a manual "Run Scheduler Check Now" action.
  • Added a strict scheduler window guard in rss:generate-analytics: direct cron runs now respect per-period enabled + run_at, skip before slot time, and skip already-completed period keys; manual override remains available via --ignore-scheduler-window.
  • Updated /mcu web route to redirect to the controller-backed MCU editor route (mcu.index) instead of rendering the editor Blade without required view data (which caused $perPage/filter variables to be undefined).

Google OAuth Login / Registration

  • Users can now sign in or register using their Google account from the login and register pages.
  • New users who register via Google are automatically email-verified; no verification email is required.
  • Account sync / linking: if you sign in with Google and your email already exists in the system, the Google account is linked to your existing account automatically on first Google sign-in.
  • Already-logged-in users can link their Google account from the My API Keys page (/keys/mine) → "Google Account" section.
  • Linked accounts can sign in with either Google or email/password.
  • Unlinking is possible from the same page; requires a password to be set first.
  • A single redirect URI (/auth/google/callback) handles both sign-in and linking flows.
  • Setup: add GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, and GOOGLE_REDIRECT_URI to .env (see .env.example).

Visit Statistics – RSS Scraper Exclusion

  • The API Requests Trend chart and total API request counter on /admin/visit-stats now exclude RSS scraper/cron paths by default.
  • Excluded paths: /api/rss/data, /api/rss/urls, /api/rss/update, /api/rss/feed/analytics-*, /api/rss/analytics/run.
  • A toggle button ("🤖 Scrapers: excluded / included") is shown next to the date-range selector to switch between filtered and unfiltered views.
  • The chart title and summary card badge reflect the active filter state.

MCU Timeline – Info and Support Links

  • Added "More info & live API" link to tools.tornevall.net and "GitHub / Support" link to https://github.com/Tornevall/mcu-timeline-api at the top of the MCU Timeline editor page.

DNS Editor / DNS API Cache Performance

  • Reworked DNS zone cache storage from a single serialized bulk blob per zone to row-based cache records (dns_zone_cache_records) to support very large AXFR zones with significantly faster paged reads.
  • Added an IP-extra index table (dns_zone_cache_ip_extras) populated for forward A/AAAA records and decoded reverse-owner entries (DNSBL/OPM style), so future CIDR workflows can use indexed IP metadata.
  • Changed record mutation behavior (POST /api/dns/records/add|delete|update|bulk) to synchronize cache rows after confirmed successful master DNS updates instead of invalidating full zone caches.
  • Added per-zone cache invalidation policy settings in dns_zone_settings:
    • cache_invalidate_enabled (default false)
    • cache_invalidate_interval_seconds (default 259200 = every 3 days when enabled)
    • last_invalidated_at
  • Added scheduled command dns:cache:invalidate (hourly scheduler hook) that only invalidates zones where policy is enabled and due.

2026-03-26

RSS Analytics Scheduler Timing

  • Tightened the built-in analytics scheduler so a period only executes at its configured time or as soon as possible after that time if the slot was missed.
  • First-run behavior no longer fires immediately at arbitrary times; it now waits until the configured period slot is actually due.
  • Added a static scheduler status card on /admin/jobs that shows per-period analytics run configuration (enabled, run_at) and latest scheduler state (last_run_at, status, key/reason), so operators can verify timing behavior without opening Feed Admin first.
  • Updated tornevall-tools-cron to run php artisan schedule:run every minute so configured analytics schedule times are actually respected by the scheduler.

Public Landing (/)

  • Expanded the authenticated "Your available tools" shortcut card to include a significantly broader permission-aware tool set (RSS, DNS, jobs, admin/security, messaging, integrations, and user tooling).

Online Sessions (/online admin view)

  • The admin online session list now shows each visitor's latest tracked location (request URI) when available.
  • Added a short recent path trail/backtrack list so admins can see where a visitor has just been moving inside the site.
  • Page visit tracking now stores authenticated user_id consistently and can also store Laravel session_id for more accurate session-to-location matching.

2026-03-25

Feed User Questions (/feed + /feed/user-questions)

  • Guest captcha fix: Fixed guest submissions on /feed so the question form now reuses the shared global Turnstile key when the older feed-specific provider key is missing. The page also now blocks submit until a captcha token is present and shows a clearer temporary-unavailable notice instead of failing silently when captcha keys are not configured correctly.
  • Per-question focus selectors added: When asking a question on /feed, users can now optionally focus the AI's analysis on specific categories or sites using two new multi-select dropdowns:
    • Focus categories (optional): Select one or more categories to narrow the context snapshot (helps de-prioritize less interesting feeds like general news).
    • Focus sites (optional): Select one or more feeds to focus on specific sources.
    • Leaving both empty uses all visible feeds (default behavior). If both are selected, the AI uses either match ("any" logic).
    • These selections override the global admin scope settings for that individual question only and are saved in question history for audit/replay.
  • Focus selection semantics tightened: For /feed user questions, selected focus_site_ids[] now override category focus for that question (instead of category-driven broad matching). This keeps context/query scope aligned with the explicitly selected sites and reduces oversized prompts.

RSS Analytics CLI / Scheduler

  • --force now actually forces overwrite: php artisan rss:generate-analytics now skips unchanged bucket+variant signatures by default and reuses the cached row instead of spending extra AI calls on identical snapshots.
  • Added --overwrite-current as a clearer alias for --force when operators explicitly want to re-run and overwrite the current bucket row.
  • Admin-configurable scheduler control: /feed-admin now includes per-period scheduler controls (daily/weekly/monthly/yearly) with enable/disable and run-time fields. A minute-level scheduler command (rss:run-scheduled-analytics) now handles due/catch-up execution from cron.
  • Robust catch-up + first-run behavior: If a period has never run before (no timestamp), it is executed immediately once. If a configured run time was missed, it executes as soon as possible afterward.
  • Automatic run identity + overwrite semantics: Scheduler-triggered runs use a dedicated auto variant title ([AUTO] Scheduled analytics) and always overwrite the current automatic bucket variant in place.
  • Timestamped analytics log output: rss:generate-analytics now prefixes output lines with date/time, making rss-analytics.log easier to audit per generation.

Public Landing / Services (/ and /services)

  • Unified the public service discovery pages so both / and /services now render the same landing view.
  • Removed the separate authenticated "admin card wall" from the landing page to keep the entry experience focused on public services.
  • Kept one maintained card layout for public services to avoid duplicated UI maintenance across multiple views.
  • Simplified navbar home/services navigation to normal route links (no homepage-only JavaScript branch).

2026-03-24

Menstrual Tracking (/menstrual-tracking)

  • Added a new authenticated user tool for menstrual cycle monitoring with a simple web UI.
  • Users can save profile baseline fields (birth_date, first_period_started_on) and register cycle starts either as exact dates or month-level entries.
  • The tool now shows a basic cycle overview: average cycle length, latest start, estimated next start, and current cycle day.
  • Added paginated cycle history with per-row delete in the same UI.
  • Added service discoverability links from /, /services, dashboard cards, and navbar.
  • Added EN/SV end-user docs page: /docs/en/menstrual-tracking and /docs/sv/menstrual-tracking.

Cron / Runtime Stability

  • Added optional runtime flag PHP_SUPPRESS_DEPRECATIONS in bootstrap to mute noisy PHP deprecation notices (E_DEPRECATED, E_USER_DEPRECATED) on PHP 8.4+ when running older vendor stacks.
  • Added .env.example documentation for the flag.
  • Added troubleshooting section in docs/cron.md for deprecation warnings in cron output.

2026-03-23

Feed User Questions (/feed mini-card + /feed/user-questions)

  • "Read more" for long answers: Recent questions on the /feed page now show a truncated preview (280 chars) of each answer. If an answer is longer, a read more / read less toggle button appears, allowing inline expansion without leaving the page.
  • Custom AI response tone (optional): The feed question settings page now allows tone to be left empty (no longer required). An empty tone falls back to the default neutral instruction when answering questions. Admins can still select: Neutral, Analytical, Brief, Friendly, or Critical from the dropdown.
  • More AI models available: The answer model selector is now a dropdown instead of a free-text field, with options:
    • gpt-4o-mini (default, fast)
    • gpt-4o (general purpose)
    • o1-mini (reasoning)
    • o1 (advanced reasoning)
    • o3-mini (extended reasoning)
    • Reasoning models (o1/o3) automatically fall back to gpt-4o-mini if the model is unavailable or fails, ensuring a graceful recovery without user-facing errors.

RSS Subscriptions (/feed/subscriptions) — Stricter Duplicate Filtering

  • Re-delivery of cycling versions prevented: The subscription notifier now tracks every content_hash that has ever been delivered for each link within a subscription. If a new import row has the same hash as any previously reported version of that link (even if it received a new contentid due to cycling content — A→B→A), it is silently skipped and not re-reported. This prevents notification spam from feeds that repeatedly publish minor or near-identical variations.

RSS Import — Global Version Deduplication

  • Cycle-safe global hash check in import: The import dedup guard (canQuery) now also checks whether the new entry's content_hash or meaningful_hash already exists in any previous row for that link, not just the most recent row. This closes the A→B→A cycling hole where version A was being reinserted into the content table even though it already existed in older history. Both content_hash and meaningful_hash are checked globally via indexed existence queries, preventing runaway version accumulation from feeds with cycling or near-identical content.

Disney / BAMGRID Scraper (projects/scraper/ondisney.php)

  • original_url fallback always populated: Disney sitemap URLs always carry locale prefixes (e.g. en-be, nl-nl). The scraper previously only set original_url on the rare case of a URL without a locale prefix, meaning most items never had original_url set and were silently dropped by the import engine. Now the scraper falls back to a preferred English locale (en-usen-gben-ca → first en-* → first available) and always sets original_url (and original_title if missing) on each item. Existing elements DB JSON for site 8 is unchanged; the fix is in the scraper output.

Development & Documentation

  • RSS elements format fully documented: Added a complete reference in the internal BAMGRID notes (docs/en/bamgrid-sitemap-extraction-notes.md, docs/sv/bamgrid-sitemap-extraction-notes.md) explaining both supported elements JSON formats:
    • Format A (begin/table): JSON traversal — begin navigates from root to the item array; table maps content columns to source keys (string = direct key, array = nested traversal).
    • Format B (XPath pipeline): 4-element array — row selector XPath, per-field sub-selectors, value type list, transform table. Used for raw HTML/XML feeds.
    • Included the correct elements DB JSON for Disney/BAMGRID site 8 with field-mapping table and explanation of why all locales need to be considered.

RSS Maintenance Admin (/rss-maintenance-admin)

  • "Mark as cautious" flag: Each suspicious link row now has a ⚠ Mark cautious button. Marking a link stores a persistent flag (rss_cautious_links table) so future imports of that link automatically run the dedup/purge pass after each new insert — only distinct meaningful versions are kept.
  • Unmark cautious: A ✓ Unmark cautious button appears on already-flagged rows to clear the flag.
  • Visual indicator: Cautious-flagged rows are highlighted in the table and show a badge next to the link.
  • AJAX-first interactions: Filter refresh, dry-run purge, purge now, mark cautious, and unmark cautious now run asynchronously in /rss-maintenance-admin (with non-JS form-submit fallback still intact).
  • Fixed mark-cautious request bug: POST /rss-maintenance-admin/mark-cautious now handles omitted reason safely (no more Undefined array key "reason" when the field is not provided).

RSS Feed Entry (/feed/entry/{contentId})

  • Admin actions block: Admins (users with permission:rss) now see an Admin actions panel at the bottom of every entry permalink page with:
    • ⚠ Mark as cautious — flag the entry's link so future imports auto-purge noisy duplicates.
    • ✓ Unmark cautious — remove an existing cautious flag.
    • 🗑 Purge noisy duplicates — instantly purge redundant versions of this specific entry, keeping the newest row per distinct meaningful hash. No more manual MySQL work.

RSS Subscriptions (/feed/subscriptions)

  • Fixed: save with all channels unchecked now works — previously, unchecking all channel checkboxes (Email, Slack, Discord) caused a validation error and the form could not be saved. Now saving with zero channels is allowed and disables all notifications for that subscription.
  • Autosave channel UX (AJAX-first) — channel checkboxes now save immediately, webhook/text fields save on blur, and the channel chips/state update inline without requiring a manual "Save channel settings" workflow.
  • Empty channel list now persists correctly — explicit channels_json=[] remains "all channels disabled" instead of defaulting back to Email (mail) in UI rendering.

Feed Admin (/feed-admin)

  • Public column visibility moved to feed-admin: The "Public column visibility" settings block has been moved from the public /feed page to /feed-admin. All RSS feed configuration for admins is now in one place. The public feed table column layout still reflects saved settings as before.
  • Segment-level anchor dates in generation cards: Category and site generation cards now include optional per-segment anchor date inputs. Generation precedence is now segment anchor -> global anchor -> current date, making historical runs explicit per card.

RSS Editor (/rss)

  • XPath-only elements visibility: The large elements JSON editor is now shown only for rows where sitetype is xpath, reducing table width pressure for normal RSS/WP rows.
  • Collapsible elements editor: XPath rules are now edited in an expandable/collapsible per-row panel so the main list remains compact.
  • elements save on blur (AJAX): elements textarea changes are now saved when the field loses focus; other inline fields keep instant AJAX save on change.

RSS Feed Analytics

  • Anchor-date calendar for generation: /feed-admin category/site analysis generation now includes an Anchor date picker. Admins can generate daily/weekly/monthly/yearly analytics for earlier periods (for example last week) if a cron run was missed.
  • Generation payload update (web UI): Feed-admin generation requests now send optional anchor_date (YYYY-MM-DD) to target historical period buckets when creating/replacing cached analysis variants.
  • Scheduler cadence aligned to 20:00 daily: app/Console/Kernel.php now schedules rss:generate-analytics --period=daily once per day at 20:00. Weekly/monthly/yearly remain available via manual CLI/API triggers.
  • Overwrite semantics clarified: Re-running generation for the same period bucket and same variant now explicitly documented as an in-place update/replace (no duplicate row for that same variant+bucket).
  • Replace UI fixed for all periods: /feed-admin now shows cached daily variants alongside weekly/monthly/yearly, and regenerate/replace actions refresh the page after save so overwritten category/site variants become visible immediately.

Development & Documentation

  • BAMGRID sitemap extraction notes (internal): Added internal documentation at docs/en/bamgrid-sitemap-extraction-notes.md and docs/sv/bamgrid-sitemap-extraction-notes.md with verified Disney sitemap structure, namespace-safe XPath candidates (url/loc/lastmod/xhtml:link), measured language-coverage impact (en-*-only would drop ~37.1% of unique IDs), and a recommended path toward a dedicated bamgrid import type with canonicalized per-content output.
  • RSS editor xpath/json rule hardening: /rss now exposes explicit xpath/json site types in Add URL flow, supports editing elements JSON rules inline when the column exists, validates elements payload format server-side, and requires elements when sitetype=xpath.

Public Feed UX

  • Feed question submit via AJAX: The Ask about all open feeds card on /feed now submits inline with JSON responses and shows status/answer directly on the page (no full reload). Non-JS fallback form behavior remains supported.
  • Feed question context upgraded + profile fallback: Feed Q&A prompt context now uses analysis-like aggregates from stored rss.content data for all non-hidden feeds (categories, sources, domains, terms, entry examples). Added OpenAI profile fallback handling so missing prompt-profile config no longer hard-fails with "No prompt profile configured".
  • Feed question scope is now configurable: Admins can now restrict /feed question context by period, allowed categories, allowed site ids, context entry cap, answer sentence cap, and mini-history size. The /feed card also shows a small recent-question history inline.
  • Question history pagination: /feed/user-questions now supports practical pagination defaults with configurable rows-per-page to keep large/spammy histories manageable.
  • AJAX delete for question history (admin): Admins can now delete question rows inline on /feed/user-questions via AJAX (DELETE /feed/user-questions/{question}), with non-JS redirect fallback preserved.
  • Q&A answer model + tone settings: Feed question admin settings now include configurable answer model and answer tone (available on both /feed/user-questions and /feed-admin settings blocks), and these settings are applied when OpenAI answers are generated.
  • Admin filters on question history: /feed/user-questions now supports filtering by status, user_id (or guest), ip, and deleted scope (active/deleted/all) to moderate noisy histories faster.
  • Bulk moderation delete with mode: Admins can now delete selected question rows in bulk (POST /feed/user-questions/bulk-delete) and choose hard (permanent) or soft delete mode.
  • Soft-delete persistence for questions: feed_user_questions now stores moderation metadata for soft-deleted rows (deleted_at, deleted_by_user_id, deleted_reason) while preserving hard-delete for irreversible cleanup.
  • Restore/undo support for soft-deleted questions: Admins can now restore soft-deleted rows from /feed/user-questions (POST /feed/user-questions/{question}/restore), including AJAX inline restore actions.

2026-03-22

UI Improvements

  • Navbar size reduced: The site navigation header is now more compact (smaller brand title, tighter padding) so it takes less vertical space.
  • Public feed layout clarity: Site-specific AI analysis rows now have a blue left-border accent and a tinted background to visually attach them to their parent feed entry. Category AI analysis blocks use a stronger border and clearer 📊 Category AI analysis label to distinguish category-level summaries from per-site rows.
  • Retranslate — no more language dropdown: In /feed-admin, the Retranslate action now reads the target language directly from the variant's stored label (e.g., a variant marked EN will always retranslate to English). The "Choose language" dropdown is removed — the labeled language IS the target.
  • Subscriptions actions via AJAX: /feed/subscriptions now saves channel settings inline and also handles Pause/Resume and Remove via AJAX (no full reload). Non-JS fallback submit remains supported.
  • Cron Admin discoverability: Added explicit Cron Admin/Scheduled Jobs entry points in both / dashboard and /services admin cards (route: /admin/jobs) so cron management is easier to find.
  • Discord OAuth webhook onboarding on subscriptions: /feed/subscriptions now includes Discord OAuth connect flow (/oauth/discord/start -> /oauth/discord/callback), callback URL visibility, and one-click reuse of the latest Discord webhook payload, matching Slack-style onboarding behavior for webhook setup.
  • Feed question mini-card + history: /feed now includes a mini Q&A card for questions about all open feeds. Added /feed/user-questions history page, guest Turnstile enforcement, configurable guest/user daily+weekly quotas, configurable guest concurrent intake guard, admin bypass/unlimited mode, and persistent question/answer audit records (timestamp, IP, actor metadata, status, response meta).

Bug Fixes

  • RSS inbound deadlock fix: Fixed SQLSTATE[40001] InnoDB deadlock on /api/rss/update that occurred when multiple concurrent scraper workers triggered the stale-lock cleanup (UPDATE inbound SET processlock=0 …) at the same time as the batch lock-acquisition. The stale-reset and old-row DELETE are now wrapped in a retryOnDeadlock() helper (up to 3 attempts with randomised back-off). fetchAndLockInboundBatch() is now fully atomic via DB::transaction() + lockForUpdate() (SELECT … FOR UPDATE), so two workers can never pick up the same batch.
  • Safer stale-unhandled inbound policy: /api/rss/update no longer purges old handled=0 rows by default. Purge is now opt-in (RSS_PURGE_STALE_UNHANDLED=true) and limited to abandoned locked rows (processlock=1) with deadlock-safe retries, reducing accidental backlog loss if triggers are paused.
  • SocialGPT API 500 fixed: Resolved Call to undefined method App\Services\OpenAI\RequestLogService::excerpt() on POST /api/ai/socialgpt/respond by restoring safe excerpt handling in request audit logging.
  • RSS inbound deadlock hardening (handled update): setEntryHandled() in RssController now executes through deadlock retry logic and explicitly releases processlock when marking rows handled, reducing SQLSTATE[40001] deadlocks during concurrent /api/rss/update workers.

Development & Documentation

  • Expanded analytics feed selectors + daily support: Analytics feeds under /api/rss/feed/{selector} now support analytics-daily, analytics-weekly, analytics-monthly, analytics-yearly, and analytics-bulk (with corresponding *-analytics aliases). Feed rows now link back to related category/site targets when available. Daily period support was also added to analytics generation flows (rss:generate-analytics, POST /api/rss/analytics/run, and scheduler wiring).
  • RSS DB env alignment (DB_RSS_*): config/database.php now treats DB_RSS_* as canonical while keeping legacy RSS_* fallback compatibility across host/database/username/password/charset/collation/socket/url fields, and .env/.env.example now include explicit DB_RSS_URL/DB_RSS_PORT/DB_RSS_SOCKET entries.
  • Cron trigger docs expanded: Added explicit RSS analytics trigger documentation for scheduler cron (schedule:run), manual artisan execution, and API trigger (POST /api/rss/analytics/run) in EN/SV docs.
  • Google Home API + admin console added: Added permission-gated Google Home endpoints (POST /api/google-home/request, /devices/query, /devices/request-sync) and a new web console at /admin/google-home. Regular users can use it when granted google-home.use permission.
  • Google Home mobile push notifications: Added Google Home push endpoints (/api/google-home/push/tokens/register, /push/tokens, /push/test) so mobile clients can register FCM tokens and receive push notifications when Google Home API calls complete.
  • Retranslate runtime fix: Category/site retranslate actions now use the current OpenAI engine profile API instead of the removed generateText() method, fixing the /feed-admin 500 error (Call to undefined method ... OpenAiEngine::generateText()).
  • nethandle-web planning baseline: Added initial projects/nethandle-ui/README.md + CHANGELOG.md, documented script-preserving migration strategy for web-to-terminal network control, and added runtime env/config scaffold (NETHANDLE_EXEC_*, NETHANDLE_RESOLVER_BASE) in .env, .env.example, and config/services.php.
  • Site analysis custom title field: /feed-admin site-level generation now has a dedicated per-site title input, so site variants no longer depend on the global title field.
  • Cleaner public /feed analytics UI: The Feed Analytics (OpenAI cached) category block now renders only when cached analytics exist for that category, reducing empty UI noise.
  • Concurrent scraper fetches (anti-stall): projects/scraper/scrape.php now fetches feed URLs with bounded curl_multi concurrency so a single slow/hanging site does not block the whole scrape batch. Concurrency/timeout can be tuned via CLI args or SCRAPE_CONCURRENCY and SCRAPE_FETCH_TIMEOUT.
  • Feed-admin variant actions always visible: Existing cached analytics variants (category and site) now show Retranslate and Regenerate (replace) controls directly in /feed-admin, instead of only appearing after a fresh generation in the current browser session.
  • Replace-in-place regeneration improved: Per-variant regenerate actions now force replace_existing for the selected period/variant id, so weekly/monthly/yearly entries are overwritten intentionally instead of silently creating extra variants.
  • Meaningful hash duplicate guard: RSS import duplicate detection now includes meaningful_hash and tiny-change suppression (isTinyMeaninglessChange) to avoid noisy pseudo-updates (markup jitter, micro text drift) creating endless new versions.
  • RSS Maintenance Admin added: New /rss-maintenance-admin page lists suspicious high-churn links and supports dry-run/live noise purge per link, keeping latest representative rows per meaningful change.
  • RSS content schema update: Added migration for content.meaningful_hash with index to support semantic duplicate filtering and maintenance diagnostics.
  • RSS queue diagnostics SQL added: Added SQL/RSS_QUEUE_DIAGNOSTICS.sql with reusable admin/internal queries for pending inbound rows, stale locks, recently handled rows, payload-shape previews, and recent content insert comparisons when /api/rss/update reports errors or stuck pending items.
  • RSS processing info logs: RssController now writes structured Log::info checkpoints for inbound row start/finish and batch summaries, making it easier to correlate pending/error runs with Laravel logs (and Slack-forwarded Laravel log categories when enabled).
  • Immediate RSS subscription delivery: After /api/rss/update converts new rows into content, the backend now performs an immediate targeted subscription-delivery pass for affected feed ids. The existing rss:notify-subscribers scheduler remains as the fallback safety net every 15 minutes.
  • Hidden RSS feeds with hash access: Public feed browsing now supports hidden feeds via urls.is_hidden + urls.public_hash. Hidden feeds are omitted from /feed, but can still be reached directly through /feed/key/{public-hash} and /api/rss/feed/{public-hash} without exposing numeric feed ids publicly.
  • RSS editor visibility controls: /rss now exposes protected, useProtected (when present in schema), is_hidden, and public_hash so alert-style feeds such as Google Alerts can stay scraper-visible but public-list-hidden.
  • Disney scraper without TorneLIB Domain parsing: projects/scraper/ondisney.php no longer depends on TorneLIB\Module\Network\Domain for URL-path parsing. The script now uses native URL parsing, reports sitemap shape samples during the run, and preserves compatibility with the existing JSON import rules by including the expected timestamp field.
  • RSS error logging modernization: Replaced legacy file_put_contents exception traces in RssController with structured Laravel Log::warning/Log::error events (including incomingidx/urlid context), so parser/import failures are centralized in Laravel logs and can be forwarded via the existing Slack log routing.
  • Disney scraper shard handling restored: projects/scraper/ondisney.php no longer assumes only d-sitemap-1..10; it now reads all available shard files from projects/scraper/sitemap-xml when present and otherwise probes remote Disney sitemap shards dynamically, restoring coverage when Disney expands to more XML files.
  • Scraper API metadata alignment: Disney ingest now sends agent_id consistently to /api/rss/data (query + payload), matching the RSS scraper identification contract used by scrape.php/trigger.php and improving backend agent attribution.
  • Disney sitemap helper scripts updated: projects/scraper/sitemap and projects/scraper/sitemap-mini now use dynamic shard discovery with miss-streak stop conditions instead of fixed hardcoded ranges, reducing maintenance when Disney changes shard counts.
  • RSS importer recovery: Restored the missing RSS/WordPress inbound conversion helpers in RssController after the recent cleanup pass. /api/rss/update can now convert queued inbound rows into content again instead of stalling with converted=0/errors>0 while fresh scraper payloads keep arriving.
  • RSS inbound queue hygiene: Handled rows are now cleaned before queue stats are reported, stale processlock=1 rows older than 30 minutes are unlocked for retry, and row-processing errors now catch all Throwables so aborted conversions stop leaving locked backlog behind.
  • AGENTS.md: Updated AI agent coding guide to reflect the full current state of the codebase
    • Corrected database architecture: documented all seven named connections (mysql, rss, mcu, gsm, spamassassin, tornis, irclog, firewall)
    • Added full IRC Memory Lane section covering the public viewer, admin import pipeline, sandbox/production workflow, and service layer
    • Added OpenAI Engine section documenting the shared AI service layer and admin panel
    • Added brief-reference sections for Facebook Admin Stats, Telldus IoT integration, Notifications system, Scheduled Jobs, Metrics/Stats, and Public Weight Tracking share tokens
    • Removed stale line-number references from Route Organization
    • Added RSS local DB schema (subscriptions, analytics, public settings) and orphan management methods
  • Internal docs: Applied admin-only front-matter markers to internal architecture and developer-notes files stored under /docs
  • Requirements: CHANGELOG, Documentation, and Documentation sync requirements now formally extended to projects/sc4a-insights and projects/socialgpt-chrome
  • Slack OAuth callback: Fixed invalid redirect status handling in /oauth/slack/callback (a boolean false was being passed as HTTP status, causing The HTTP status code "0" is not valid.)
  • Slack log routing: Added LOG_FORWARD_DEPRECATIONS setting (default false) so noisy PHP deprecation warnings (for example PHP 8.4 dynamic-property notices) can be excluded from Slack warning forwarding
  • API audit routing to Slack: Added per-API-group audit categories with separate checkboxes in Slack log routing settings. /api/* requests now forward success/error events by endpoint group (for example /api/rss, /api/sms, /api/social-media-tools, etc.) so admins can enable only the API groups they care about.
  • Slack social-media audit enriched: Social media request notifications now include app_host (identifies tools.tornevall.com vs tools.tornevall.net), tokens_in, tokens_out (prompt/completion token split), response_language, feature_slug, and used_fallback_model (yes/no). Short fields are shown side-by-side in Slack for readability.
  • Slack social-media audit: request content: context_excerpt (first 200 chars of context) and user_prompt_excerpt (first 200 chars of user prompt) are now included in the Slack audit message.
  • Slack OAuth: cookie-backed state: OAuth flow state is now stored in both the session and a short-lived HttpOnly/SameSite=Lax cookie. If the session is lost during the Slack redirect (e.g. due to browser SameSite policy), the cookie is used as fallback and the OAuth flow completes normally. Direct navigation to the callback URL without starting the flow shows a clear "Please click Connect Slack again" message instead of a cryptic error.
  • sc4a-insights docs: Added a Contact section in sc4a-insights/README.md with the Slack Marketplace entry for Tornevall Networks Tools (https://tornevall.slack.com/marketplace/A0AN2UJ2C4S-tornevall-networks-tools) to make Slack app/webhook setup easier to find.
  • Slack OAuth callback robustness: Callback handling now supports Slack-managed installs where state may be empty on both sides, while still enforcing strict state validation whenever a state value exists. Token exchange now sends redirect_uri only when it was actually present in the authorize step, matching Slack OAuth v2 parity rules.
  • AGENTS.md implementation locations: Added concrete locations for RSS subscription webhook UI, Slack OAuth return flow, and the public donation completion page so future maintenance sessions can jump straight to the right files.

RSS Feed Analytics UX

  • Public /feed now includes an admin-only Translate button next to each category analysis variant (weekly/monthly/yearly). The action is AJAX-driven and reuses the selected language from the variant language selector.
  • Feed-admin analytics generation now supports a Replace existing mode. When enabled, generation overwrites the currently selected cached variant for the chosen period instead of creating/updating a different variant key.

Social Media Tools Extension (socialgpt-chrome v1.2.9)

  • Context menus restored: Right-clicking anywhere on a page now shows two Toolbox actions:
    • "Open Toolbox" — opens the panel; selected text is automatically imported as context
    • "Verify fact with Toolbox" — starts fact verification using selected text, link, image URL, or page URL
  • Floating in-page action button renamed from "Fill in with Tools" to "Open Toolbox"
  • SoundCloud 4 Artists insights capture removed from the main extension; it is now handled exclusively by the sc4a-insights companion module

SoundCloud Companion Extension (sc4a-insights 2.0.0)

  • The SoundCloud companion module is now documented as a real 2.0.0 milestone against the original 1.0.0 2025 overlay-only release
  • The extension is now clearly positioned as the dedicated Tools companion for SoundCloud 4 Artists capture rather than a local-only overlay helper
  • New user documentation now covers:
    • personal Tools bearer-token setup
    • dev/prod host switching
    • auto-ingest into /api/social-media-tools/soundcloud/ingest
    • local pending queue and duplicate protection
    • popup diagnostics and debug window usage
    • the supported SoundCloud dataset types

RSS subscriptions

  • Subscription links now prefer Tools entry permalinks: Mail/Slack/Discord digests now include a direct Tools entry URL first (/feed/entry/{contentId}), with the original source URL included as secondary fallback.
  • User-level webhook onboarding: /feed/subscriptions now includes direct helper links for webhook setup:
    • Connect Slack app (get webhook) starts Slack OAuth with incoming-webhook scope and returns to subscriptions
    • How to create a Discord webhook links to Discord webhook setup docs
  • Slack OAuth quick-fill: If a recent Slack OAuth payload contains incoming_webhook.url, users can now click Use latest Slack OAuth webhook to fill the Slack webhook URL (and channel label when empty) directly in subscription settings.
  • Slack setup clarity: /feed/subscriptions now shows whether Slack OAuth is configured on the current host and displays the effective callback URL, making it clearer that TOOLSAPI_SLACK is the global platform webhook while subscription webhooks come from Slack OAuth (incoming_webhook.url).
  • Slack OAuth-first webhook field: When a recent Slack OAuth callback already contains incoming_webhook.url, the Slack webhook URL field is now auto-filled, saved server-side even without manual paste, and shown as readonly in the UI so users do not have to enter it manually.
  • Reuse latest Slack webhook button kept: The explicit button for reusing the latest Slack OAuth webhook remains available in /feed/subscriptions, so users can quickly continue sending notifications to the same Slack channel across subscriptions.

Donations

  • Added a public Thank you for your donation page at /donate/thankyou, intended for payment-provider completion redirects (for example PayPal return/thank-you URLs).
  • Added a public Donation cancelled page at /donate/cancel, intended as the canonical payment-provider cancellation/abort redirect.
  • Kept /donate/farewell as a compatibility alias; it now resolves to the same aborted-donation content via the canonical cancel flow.
  • Added a styled Donate link to the shared navbar, pointing to the hosted PayPal donation URL.
  • Added a prominent donation card at the top of /contact, including a styled PayPal donate button and the current donation QR image.
  • Centralized the hosted PayPal donation target in config/services.php (services.paypal.donate_url) so public donate entry points reuse the same URL.
  • Replaced the empty public/images/index.html with a playful “directory listing disabled” landing page, so direct browsing of the public image folder no longer lands on a blank file.

2026-03-21

RSS & Feed Experience

  • Public feed pages now show cached site-specific AI analysis directly on each feed row when available
  • Individual feed pages (/feed/{id}) now show site-level analytics variants (weekly/monthly/yearly)
  • Site-specific analysis focus can now be saved and reused in category-level analysis context
  • Feed content rendering now uses safer markdown-normalized output for better readability and more stable layout handling

Social Media Tools

  • SocialGPT verify-mode requests now support configurable reasoning effort for supported reasoning models
  • Verify-mode now retries safely without reasoning effort when a model rejects that parameter
  • Added an admin Social Media Tools Audit page with filters for status, request mode, model, and user
  • Extended SocialGPT API response metadata with request summary and verification context for easier troubleshooting

Documentation & Rendering

  • Added a reusable markdown normalizer service for shared formatting/parsing workflows
  • Documentation front matter parsing now uses the shared markdown normalizer

2026-03-20

Development & Documentation

  • Improved internal contributor documentation and editorial consistency for ongoing maintenance work

2026-03-19

RSS Feed Analytics

  • Added richer RSS category analytics in /feed, /rss, and /feed-admin
  • Feed-admin now supports auto-generating missing category instructions/description from live category flow content via AJAX (English-first output)
  • Feed-admin title handling is now dynamic: suggested analysis titles are generated from current instructions/description in real time when title is empty
  • Long public analytics can now be expanded with Read more / Show less
  • Analytics cards are visually separated by period
  • Markdown in analyses is rendered as readable HTML
  • Weekly, monthly, and yearly analysis periods are supported
  • Manual re-generation now updates the same period record instead of creating duplicate entries
  • Added optional analyst guidance before generation (watch_for)
  • Added category-specific guidance that can be combined with global focus
  • Added support for named analysis variants with different language, tone, and purpose
  • Public /feed can show different saved analysis variants via title/language selectors
  • Public /feed analytics sections are collapsed by default and can be expanded per category
  • RSS admins can now delete cached category analytics variants directly on /feed via AJAX
  • Analysis prompts now include example entry links and stronger activity signals
  • Added a second generation tool for site-level analytics (per writer/news site)

Feed Browsing

  • Public /feed is now sorted by category order
  • First Discovery and Last Discovery are shown for feeds
  • Legacy protected-feed filtering has been decommissioned; feeds are now treated as public by default

Documentation

  • Standardized public documentation page slugs across English and Swedish
  • Added missing public pages for parity between docs/en and docs/sv
  • Refocused documentation toward API usage and web UI workflows instead of internal implementation details
  • Updated changelog through the current release

2026-03-16

Social Media Tools

  • Improved extension settings behavior for faster autosave and cleaner setup
  • Refined SoundCloud capture handling and release notes continuity

2026-03-02

IRC Log Rollback

  • Fixed access handling for rollback features so authorized users can reach them reliably
  • Improved feedback around rollback-related errors

2026-03-01

IRC Log Management

  • Added rollback support for import mistakes
  • Improved import review and format handling
  • Made rollback workflows clearer and safer for administrators

2026-02-13

DNS API and Access

  • Added DNS zone discovery and browsing tools
  • Improved API key authentication and access control
  • Added IP-based allowlist support for selected endpoints
  • Expanded admin tools for key generation and DNS access management

OpenAI and Platform UX

  • Improved OpenAI testing and model selection in the admin UI
  • Expanded dashboard/service visibility so available tools are easier to find
  • Added user registration tracking and more flexible local SSL behavior for development environments

2026-02-11

Security and Administration

  • Added Turnstile CAPTCHA support for authentication flows
  • Improved admin warning flows and access ban management
  • Enhanced user management and API key management screens
  • Continued usability improvements in the OpenAI admin UI

2026-02-10

Dashboard and Editors

  • Improved dashboard/service navigation and overall admin consistency
  • Expanded MCU editor workflows and editor usability
  • Improved dark mode support and form readability
  • Refined RSS listing UI and general navigation

2026-02-09

Permissions and Documentation

  • Introduced permission-based access controls across protected tools
  • Standardized navigation and layout behavior across the platform
  • Added markdown-powered documentation with English/Swedish support

2026-02-08

RSS Watch

  • Expanded the public RSS feed viewer
  • Added history-aware feed viewing and edited-entry detection
  • Improved feed browsing, outbound link handling, and API output flexibility

Earlier Foundations

Earlier releases established the platform basics for:

  • RSS aggregation and feed serving
  • MCU timeline browsing
  • authentication, sessions, and access control
  • multi-database service support
  • markdown documentation and public API publishing

Maintained by: Tornevall Networks
Last updated: 2026-04-18