← Back to docs

Tools API - Changelog

Language: EN | EN | SV

Tools API - Changelog

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


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

  • 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

SocialGPT Facebook dashboard - approved and rejected actions now split join requests from pending posts

  • The Facebook admin statistics dashboard now derives a second moderation classification from the stored admin-log sentence, so Approved and Rejected can be separated into join requests versus pending posts.
  • Anonymous pending-post approvals/rejections are included in the pending-post totals, and the summary cards now also show the anonymous subset explicitly.
  • This was implemented at the reporting layer only, which means existing ingested extension payloads still work without any SocialGPT extension contract change.
  • The Swedish moderation keyword list behind that split is now stored with readable åäö spellings while matching remains accent-insensitive, so both förfrågan and ASCII-fallback strings such as forfragan classify the same way.
  • Public shared Facebook chart pages (/shared/facebook/chart/{token}) now auto-refresh every 60 seconds (was 300 seconds), with the on-screen countdown updated accordingly.

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.
  • Ingested Facebook admin-activity rows remain scoped to the owner of the bearer token that submitted them.

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.
  • Sandboxed frames that deny sessionStorage no longer trigger an uncaught Facebook admin reporter storage error inside the extension.

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 - Facebook admin statistics require popup opt-in first

  • Facebook admin_activities overlays now stay completely hidden unless the new popup checkbox Enable Facebook admin activity statistics is turned on.
  • After that popup-level opt-in, statistics submission is still page-local and remains disabled until the user enables it on the current Facebook page.
  • Turning the popup toggle off again now also stops further admin-log handling in the active tab instead of only hiding the UI.

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}).

Social media tools (Facebook admin stats) – rolling 40-day default window

  • The dashboard at /admin/social-media-tools/facebook now defaults to a rolling 40-day range ending today.
  • Example behavior: if today is 2026-04-10, default date_from is 2026-03-01 and date_to is 2026-04-10.
  • User-provided date filters still override the default window exactly as before.

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 /admin/social-media-tools/facebook 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.

Facebook Statistics – Strict Duplicate Report and Safe AJAX Cleanup

  • Added a strict duplicate report to Recent stored moderation events on the Facebook admin dashboard.
  • Duplicate groups are now explicitly marked as either Guaranteed clear or Blocked.
  • Cleanup is now AJAX-driven and removes only guaranteed duplicate rows from the currently filtered scope.
  • A group is clearable only when duplicate identity is strong (client_event_key or network_activity_id) and all key event fields match exactly.
  • Any mismatch (or heuristic-only identity) is blocked from deletion to prevent accidental removal of potentially unique events.

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