<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Tomasz</title>
    <description>The latest articles on DEV Community by Tomasz (@wrobeltomasz).</description>
    <link>https://dev.to/wrobeltomasz</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3933746%2F9020a328-6799-44e6-aa7c-c124d27fe4fb.jpg</url>
      <title>DEV Community: Tomasz</title>
      <link>https://dev.to/wrobeltomasz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wrobeltomasz"/>
    <language>en</language>
    <item>
      <title>OpenSparrow v4.5 adds a chat shortcut on user tooltips</title>
      <dc:creator>Tomasz</dc:creator>
      <pubDate>Thu, 29 Oct 2026 18:05:33 +0000</pubDate>
      <link>https://dev.to/wrobeltomasz/opensparrow-v45-adds-a-chat-shortcut-on-user-tooltips-4pc6</link>
      <guid>https://dev.to/wrobeltomasz/opensparrow-v45-adds-a-chat-shortcut-on-user-tooltips-4pc6</guid>
      <description>&lt;p&gt;&lt;strong&gt;OpenSparrow v4.5 adds a chat shortcut on user tooltips, a per‑page Click Statistics breakdown with date‑range filters, and a last‑edited‑by line in the Record History tab&lt;/strong&gt; — plus a base64 logo upload that works on shared hosting with an unwritable temp dir.&lt;/p&gt;

&lt;h2&gt;
  
  
  A chat shortcut on every user tooltip
&lt;/h2&gt;

&lt;p&gt;Every record owner and comment author tooltip now ends with a small chat button. User tooltips already went through one shared funnel (&lt;code&gt;enableUserTooltips()&lt;/code&gt; in &lt;code&gt;public/assets/js/util/user-tooltip.js&lt;/code&gt;), which fetches the user's contact card via &lt;code&gt;api/owners.php?action=user_info&lt;/code&gt; and renders it through the record tooltip; the model now also carries a &lt;code&gt;chat&lt;/code&gt; action when the target is not yourself (&lt;code&gt;info.id !== window.CURRENT_USER_ID&lt;/code&gt;). Clicking the button dynamically imports &lt;code&gt;chat-panel.js&lt;/code&gt; and calls &lt;code&gt;openChatPanel(peer)&lt;/code&gt;, so the messenger opens straight into that person's thread — even when no prior conversation exists (the empty thread renders and the first &lt;code&gt;send&lt;/code&gt; creates the message). The shortcut respects the existing &lt;code&gt;chat_enabled&lt;/code&gt; gate and simply does not appear for the self user.&lt;/p&gt;

&lt;h2&gt;
  
  
  Click Statistics: per‑page breakdown and date filtering
&lt;/h2&gt;

&lt;p&gt;The Click Statistics admin panel gains a &lt;strong&gt;Pages&lt;/strong&gt; tab: one tile per page script, each showing its icon and click count, aggregated from &lt;code&gt;spw_clickstats&lt;/code&gt; with the same element/user filters as the Log and Top Elements tabs. The rollup is computed server‑side (&lt;code&gt;GROUP BY c.page&lt;/code&gt;, &lt;code&gt;COALESCE(c.page, '')&lt;/code&gt; for empty pages) and rendered as a responsive icon grid, with unknown pages labelled "Unknown page". The Log, Top Elements and Pages tabs all accept a new &lt;strong&gt;From/To&lt;/strong&gt; date range, validated against a &lt;code&gt;YYYY-MM-DD&lt;/code&gt; shape before it reaches SQL and applied as a half‑open window (&lt;code&gt;created_at &amp;gt;= from&lt;/code&gt; and &lt;code&gt;created_at &amp;lt; to + 1 day&lt;/code&gt;) so both bounds are inclusive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Record History: who last edited the record
&lt;/h2&gt;

&lt;p&gt;The Record History tab now shows a &lt;strong&gt;"Last edit"&lt;/strong&gt; line under the owner section, answering "who edited this record and when" directly. It is read from the audit log (&lt;code&gt;spw_users_log&lt;/code&gt;) through a new &lt;code&gt;owners&lt;/code&gt; &lt;code&gt;last_edit&lt;/code&gt; API action: the latest &lt;code&gt;INSERT&lt;/code&gt;/&lt;code&gt;UPDATE&lt;/code&gt; row for &lt;code&gt;(target_table, record_id)&lt;/code&gt;, joined to &lt;code&gt;spw_users&lt;/code&gt; for the username and avatar. Mass edits and board/calendar moves log with &lt;code&gt;record_id = null&lt;/code&gt;, so they correctly never appear — the line reflects edit/create-form saves only. The table and record are gated through the usual &lt;code&gt;validatedTable()&lt;/code&gt; → &lt;code&gt;require_table_access()&lt;/code&gt; path, so a user only sees last‑edit info for tables they can already access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Branding logo upload that survives shared hosting
&lt;/h2&gt;

&lt;p&gt;The branding logo upload moved off multipart. A shared host with an unwritable &lt;code&gt;upload_tmp_dir&lt;/code&gt; previously failed every &lt;code&gt;$_FILES&lt;/code&gt; upload with "No file received", because PHP writes the temp file before any userland code runs and &lt;code&gt;upload_tmp_dir&lt;/code&gt;/&lt;code&gt;sys_temp_dir&lt;/code&gt; are &lt;code&gt;PHP_INI_SYSTEM&lt;/code&gt; (only settable in &lt;code&gt;php.ini&lt;/code&gt;). The admin now reads the image with &lt;code&gt;FileReader.readAsDataURL()&lt;/code&gt; and POSTs it as JSON base64 through &lt;code&gt;php://input&lt;/code&gt;, so no temp directory is ever touched. The backend guards the payload length before decoding, re‑checks the decoded size against the 2 MB limit, validates the MIME via &lt;code&gt;finfo::buffer()&lt;/code&gt;, and writes straight into &lt;code&gt;public/assets/img/uploads&lt;/code&gt;. The &lt;code&gt;$_FILES&lt;/code&gt; path stays as a fallback, and its error codes now map to distinct messages — including a dedicated "set &lt;code&gt;upload_tmp_dir&lt;/code&gt; in the hosting panel" hint for &lt;code&gt;UPLOAD_ERR_NO_TMP_DIR&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Also in this release
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Demo contact fields&lt;/strong&gt; — the CRM demo users are now seeded with first name, last name, email and phone, so the record‑history and user tooltips show full info right after install (reinstall from &lt;strong&gt;Admin → Demo&lt;/strong&gt; to pick them up).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit‑log last‑edit&lt;/strong&gt; — backed by the existing &lt;code&gt;spw_users_log&lt;/code&gt; trail, no new table or migration is required.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Following this series?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/wrobeltomasz/opensparrow-v44-adds-one-to-one-user-chat-453n"&gt;OpenSparrow v4.4 adds one-to-one user chat, an HTTP cron trigger, and content-sized admin tables&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Websites
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://opensparrow.org" rel="noopener noreferrer"&gt;https://opensparrow.org&lt;/a&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>OpenSparrow v4.4 adds one-to-one user chat</title>
      <dc:creator>Tomasz</dc:creator>
      <pubDate>Tue, 20 Oct 2026 08:25:29 +0000</pubDate>
      <link>https://dev.to/wrobeltomasz/opensparrow-v44-adds-one-to-one-user-chat-453n</link>
      <guid>https://dev.to/wrobeltomasz/opensparrow-v44-adds-one-to-one-user-chat-453n</guid>
      <description>&lt;p&gt;&lt;strong&gt;OpenSparrow v4.4 adds one-to-one user chat with bell notifications, a token‑protected HTTP cron trigger for shared hosting, and admin tables that size to their content&lt;/strong&gt; — plus a fixed chat notification title.&lt;/p&gt;

&lt;h2&gt;
  
  
  One-to-one user chat
&lt;/h2&gt;

&lt;p&gt;Frontend users can now message each other directly. The messenger is served by &lt;code&gt;public/api/chat.php&lt;/code&gt; (a thin router over &lt;code&gt;includes/Controller/Api/ChatController.php&lt;/code&gt;) and opens from the &lt;strong&gt;Chat&lt;/strong&gt; entry in the avatar menu (&lt;code&gt;public/assets/js/chat-panel.js&lt;/code&gt;, &lt;code&gt;openChatPanel()&lt;/code&gt;); a &lt;code&gt;?chat=1&lt;/code&gt; deep link opens it straight away. Conversations are strictly 1‑to‑1 — there are no group threads, and a message is visible only to its sender and recipient. Reading requires only a login; sending and hiding require the editor role, so viewers can follow their conversations but cannot write. Message bodies are trimmed and capped at 4000 characters in both PHP and a &lt;code&gt;CHECK&lt;/code&gt; constraint, and a user cannot message themselves. The search box finds users with an escaped &lt;code&gt;ILIKE&lt;/code&gt; match (at least two characters) and the conversation list collapses each peer to their latest message via &lt;code&gt;DISTINCT ON (peer_id)&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instant bell notifications
&lt;/h2&gt;

&lt;p&gt;Sending a message immediately inserts an in‑app notification for the recipient (&lt;code&gt;ChatController::notifyRecipient()&lt;/code&gt;) into &lt;code&gt;spw_users_notifications&lt;/code&gt;, with the source table &lt;code&gt;chat&lt;/code&gt; and a &lt;code&gt;?chat=1&lt;/code&gt; deep link, so the recipient is alerted without waiting for a cron run. The insert is idempotent (&lt;code&gt;ON CONFLICT … DO NOTHING&lt;/code&gt;). Either participant can hide a message; hiding is a soft delete that flips &lt;code&gt;is_visible&lt;/code&gt; without removing the row, and the ownership check refuses to hide a message you are not part of.&lt;/p&gt;

&lt;h2&gt;
  
  
  An HTTP cron trigger for shared hosting
&lt;/h2&gt;

&lt;p&gt;Most shared hosts run cron through a URL fetch and chroot the process to the web root, so they can never reach the CLI‑only &lt;code&gt;cron/&lt;/code&gt; directory. The notification cron can now also run over HTTP: &lt;code&gt;public/cron.php&lt;/code&gt; reads a &lt;code&gt;CRON_SECRET&lt;/code&gt; environment variable and a &lt;code&gt;token&lt;/code&gt; parameter, compares them with &lt;code&gt;hash_equals&lt;/code&gt;, and answers plain text. An empty &lt;code&gt;CRON_SECRET&lt;/code&gt; disables the endpoint entirely. The worker itself (&lt;code&gt;cron_notifications_run()&lt;/code&gt; in &lt;code&gt;includes/cron_notifications.php&lt;/code&gt;) is shared by both the CLI entry (&lt;code&gt;cron/cron_notifications.php&lt;/code&gt;) &lt;strong&gt;and&lt;/strong&gt; the HTTP endpoint, so there is no second runner to drift. &lt;code&gt;cron/&lt;/code&gt; ships a &lt;code&gt;Deny from all&lt;/code&gt; &lt;code&gt;.htaccess&lt;/code&gt; like every other backend folder. Secrets placed in a URL must be hex (&lt;code&gt;openssl rand -hex 32&lt;/code&gt;), never base64 — a &lt;code&gt;+&lt;/code&gt; decodes to a space inside a query string. The admin &lt;strong&gt;Cron → Setup&lt;/strong&gt; tab now includes a ready‑to‑copy HTTP trigger example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Admin tables that size to their content
&lt;/h2&gt;

&lt;p&gt;Admin tables no longer squeeze their columns into the panel width. &lt;code&gt;public/admin/style.css&lt;/code&gt; makes &lt;code&gt;.adm-tbl&lt;/code&gt; size to its content (&lt;code&gt;width: max-content; min-width: 100%&lt;/code&gt;) with &lt;code&gt;white-space: nowrap&lt;/code&gt; cells, and &lt;code&gt;.adm-sec-body&lt;/code&gt; scrolls horizontally, so timestamps, buttons and SQL stay readable instead of wrapping. The fix is global — it covers both the literal &lt;code&gt;adm-tbl&lt;/code&gt; tables and every &lt;code&gt;mkTable()&lt;/code&gt; caller — and the hand‑rolled per‑table &lt;code&gt;overflow-x:auto&lt;/code&gt; wrappers were removed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Also in this release
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Chat notification title fixed&lt;/strong&gt; — the new‑message alert rendered the raw i18n key &lt;code&gt;chat.new_message&lt;/code&gt;; the key is now defined in all 20 language files.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;4.4_user_chat&lt;/code&gt; migration&lt;/strong&gt; — creates &lt;code&gt;spw_chat_messages&lt;/code&gt; (sender, recipient, body, timestamp, soft‑hide flag, a foreign key to &lt;code&gt;spw_users&lt;/code&gt; with &lt;code&gt;ON DELETE SET NULL&lt;/code&gt;, and per‑participant indexes); apply it from &lt;strong&gt;System → Migrations → Initialize System Tables&lt;/strong&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Demo seed&lt;/strong&gt; — the CRM demo now ships a sample chat between the demo users, so the messenger is populated right after install.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Following this series?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/wrobeltomasz/opensparrow-v43-adds-record-links-and-mentions-to-comments-1k73"&gt;OpenSparrow v4.3 adds record links and @mentions to comments, user info tooltips, and a unified frontend&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Websites
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://opensparrow.org" rel="noopener noreferrer"&gt;https://opensparrow.org&lt;/a&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>nocode</category>
    </item>
    <item>
      <title>OpenSparrow v4.3 adds record links and @mentions to comments</title>
      <dc:creator>Tomasz</dc:creator>
      <pubDate>Sat, 17 Oct 2026 18:20:35 +0000</pubDate>
      <link>https://dev.to/wrobeltomasz/opensparrow-v43-adds-record-links-and-mentions-to-comments-1k73</link>
      <guid>https://dev.to/wrobeltomasz/opensparrow-v43-adds-record-links-and-mentions-to-comments-1k73</guid>
      <description>&lt;p&gt;&lt;strong&gt;OpenSparrow v4.3 adds record links and @mentions to comments, user info tooltips, and a unified frontend&lt;/strong&gt; — plus a shared help cursor, first-column row actions, and a simpler setup wizard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Record links in comments
&lt;/h2&gt;

&lt;p&gt;A comment body can now reference any record by writing &lt;code&gt;[Record: table:id]&lt;/code&gt;, which renders as a clickable link that opens that record. A record picker dialog (&lt;code&gt;public/assets/js/comments.js&lt;/code&gt;) inserts the marker for you — you never hand-type an id. Markers are extracted server-side (&lt;code&gt;includes/comment_links.php&lt;/code&gt;) and validated against the schema before being trusted, so a malformed or non-positive id is dropped rather than turned into a broken link.&lt;/p&gt;

&lt;h2&gt;
  
  
  @mentions with in-app notifications
&lt;/h2&gt;

&lt;p&gt;Typing &lt;code&gt;@username&lt;/code&gt; in a comment (with an autocomplete picker, &lt;code&gt;public/assets/js/comments/mention.js&lt;/code&gt;) mentions an active user and drops an in-app notification into their bell. &lt;code&gt;includes/comment_mentions.php&lt;/code&gt; parses the mention, matches it against &lt;code&gt;spw_users&lt;/code&gt; (active, non-author accounts only), and writes into &lt;code&gt;spw_users_notifications&lt;/code&gt; with a title (“X mentioned you”) and a deep link to the record’s &lt;strong&gt;Comments&lt;/strong&gt; tab:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mentions never notify the author of the comment&lt;/li&gt;
&lt;li&gt;The insert is idempotent (&lt;code&gt;ON CONFLICT … DO NOTHING&lt;/code&gt;), so repeating the same mention on the same record does not spam&lt;/li&gt;
&lt;li&gt;Any failure is logged server-side without leaking driver text to the client&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  User info tooltips
&lt;/h2&gt;

&lt;p&gt;Hovering a comment author or a record owner now shows a tooltip with the user’s full name, role, email and phone. Data comes from &lt;code&gt;includes/user_info.php&lt;/code&gt; and renders through the shared &lt;code&gt;enableUserTooltips()&lt;/code&gt; funnel (&lt;code&gt;public/assets/js/util/user-tooltip.js&lt;/code&gt;), which also upgrades every existing &lt;code&gt;data-username&lt;/code&gt; anchor — so &lt;code&gt;@mention&lt;/code&gt; chips and comment author links get the tooltip for free.&lt;/p&gt;

&lt;h2&gt;
  
  
  A unified frontend
&lt;/h2&gt;

&lt;p&gt;Buttons, fonts and tables now follow one shared set of design tokens in &lt;code&gt;public/assets/css/styles.css&lt;/code&gt; (&lt;code&gt;--accent&lt;/code&gt;, &lt;code&gt;--bg&lt;/code&gt;, &lt;code&gt;--border&lt;/code&gt;, &lt;code&gt;--radius&lt;/code&gt;, &lt;code&gt;--shadow-*&lt;/code&gt;, &lt;code&gt;--font-sans&lt;/code&gt;/&lt;code&gt;--font-mono&lt;/code&gt;). Buttons inherit the page font everywhere — a plain &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; no longer falls back to Arial — and the edit/create forms render Excel-style subtable borders, gray headers and zebra rows from the same recipe as the record tables. The grid’s row-actions column (edit/delete) moved to the leftmost position, and every record/user tooltip now shows the shared &lt;code&gt;?&lt;/code&gt; help cursor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Also in this release
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shared &lt;code&gt;?&lt;/code&gt; cursor&lt;/strong&gt; — all tooltips funnel through &lt;code&gt;showRecordTooltip()&lt;/code&gt; / &lt;code&gt;enableUserTooltips()&lt;/code&gt;, so grid cells, board cards, calendar chips, roadmap bars, drilldown rows, owners and comment authors all show a consistent question-mark cursor; a hand-rolled cursor on the grid cell was removed in favour of the single rule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simpler setup wizard&lt;/strong&gt; — the database step no longer prefills the database name, user or password (or a default schema); only &lt;code&gt;localhost&lt;/code&gt; and &lt;code&gt;5432&lt;/code&gt; are prefilled, so a fresh install can’t accidentally target placeholder credentials. The &lt;code&gt;config/demo_meta.json&lt;/code&gt; install marker is now documented.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Demo CRM phone masked for viewers&lt;/strong&gt; — the seeded &lt;code&gt;contact.phone&lt;/code&gt; column now carries the &lt;code&gt;mask_for_viewer&lt;/code&gt; flag, so read-only Viewers see &lt;code&gt;•••••&lt;/code&gt; while editors and admins keep the real values.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Following this series?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/wrobeltomasz/opensparrow-v42-adds-native-excel-export-column-masking-for-viewers-kfn"&gt;OpenSparrow v4.2 adds native Excel export, column masking for viewers, and record-tooltips in drilldowns&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Websites
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://opensparrow.org" rel="noopener noreferrer"&gt;https://opensparrow.org&lt;/a&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>opensource</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>OpenSparrow v4.2 adds native Excel export, column masking for viewers</title>
      <dc:creator>Tomasz</dc:creator>
      <pubDate>Thu, 15 Oct 2026 06:13:54 +0000</pubDate>
      <link>https://dev.to/wrobeltomasz/opensparrow-v42-adds-native-excel-export-column-masking-for-viewers-kfn</link>
      <guid>https://dev.to/wrobeltomasz/opensparrow-v42-adds-native-excel-export-column-masking-for-viewers-kfn</guid>
      <description>&lt;p&gt;&lt;strong&gt;OpenSparrow v4.2 adds native Excel export, column masking for viewers, and record‑tooltips in drilldowns&lt;/strong&gt; — plus a handful of UI polish and stability fixes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Native Excel export
&lt;/h2&gt;

&lt;p&gt;The record grid, public share links, saved views and the mass‑edit “Export Selected” panel can now download a real &lt;strong&gt;&lt;code&gt;.xlsx&lt;/code&gt;&lt;/strong&gt; workbook alongside the existing CSV. The workbook is built completely in the browser by a dependency‑free writer (&lt;code&gt;public/assets/js/export_xlsx.js&lt;/code&gt;) that emits a valid OPC package:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Typed cells for numbers, booleans and dates
&lt;/li&gt;
&lt;li&gt;XML‑escaped text and sanitized sheet names (max 31 chars)
&lt;/li&gt;
&lt;li&gt;Required &lt;code&gt;docProps/core.xml&lt;/code&gt; and &lt;code&gt;docProps/app.xml&lt;/code&gt; parts
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The UI now shows an &lt;strong&gt;Export Excel&lt;/strong&gt; button (also in the mobile actions menu), views and share pages render one next to &lt;strong&gt;Export CSV&lt;/strong&gt;, and the mass‑edit panel adds a &lt;strong&gt;Format&lt;/strong&gt; radio (CSV / Excel) that switches the download label. All new strings are translated in every one of the 20 languages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Viewer column masking
&lt;/h2&gt;

&lt;p&gt;A column can be flagged in the schema editor (&lt;strong&gt;Schema → table → column → Mask value for Viewer&lt;/strong&gt;) to hide its value from frontend &lt;strong&gt;Viewer&lt;/strong&gt; accounts. The column stays visible everywhere, but every value is replaced with the &lt;code&gt;•••••&lt;/code&gt; placeholder:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Grid, drill‑down sub‑tables, record form, board cards, calendar events, roadmap bars, dashboard lists and group/time‑series labels
&lt;/li&gt;
&lt;li&gt;M2M option labels, FK &lt;code&gt;__display&lt;/code&gt; labels, and public share links
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Masking is enforced server‑side – the raw value never leaves the backend for a Viewer – while Editors and Admins see the real data. The flag lives in the schema configuration (&lt;code&gt;columns.&amp;lt;col&amp;gt;.mask_for_viewer&lt;/code&gt;) and requires no migration; the schema editor marks such columns with a warning badge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Record tooltip in drilldowns
&lt;/h2&gt;

&lt;p&gt;Hovering a child row inside an expanded drill‑down now shows the same record tooltip as grid cells, with the child’s title and its configured columns. The tooltip logic was refactored to use a monotonic show token, eliminating race conditions between overlapping hovers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Also in this release
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;XLSX export hardening&lt;/strong&gt; – object‑URL revocation is now deferred (&lt;code&gt;setTimeout(…, 0)&lt;/code&gt;) so Firefox no longer cancels a download before it starts, and every download site (grid, views, mass‑edit CSV, share) uses the shared &lt;code&gt;downloadBlob&lt;/code&gt; helper instead of hand‑rolled &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; + &lt;code&gt;URL.createObjectURL&lt;/code&gt; code.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tooltip hide race fixed&lt;/strong&gt; – a stale hide timer could dismiss a tooltip that a newer hover had just shown; the new token‑based approach guarantees the correct tooltip stays visible.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Following this series?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/wrobeltomasz/opensparrow-v41-adds-optional-two-factor-login-by-email-5024"&gt;OpenSparrow v4.1 adds optional two‑factor login by email, streaming answers in Ask AI, and PostgreSQL views to the External API&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Websites
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://opensparrow.org" rel="noopener noreferrer"&gt;https://opensparrow.org&lt;/a&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>postgres</category>
    </item>
    <item>
      <title>OpenSparrow v4.1 adds optional two-factor login by email</title>
      <dc:creator>Tomasz</dc:creator>
      <pubDate>Sat, 10 Oct 2026 08:24:06 +0000</pubDate>
      <link>https://dev.to/wrobeltomasz/opensparrow-v41-adds-optional-two-factor-login-by-email-5024</link>
      <guid>https://dev.to/wrobeltomasz/opensparrow-v41-adds-optional-two-factor-login-by-email-5024</guid>
      <description>&lt;p&gt;&lt;strong&gt;OpenSparrow v4.1 adds optional two-factor login by email, streaming answers in Ask AI, and PostgreSQL views to the External API&lt;/strong&gt; — plus a security hardening pass across the write paths.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two-factor login, off by default
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;System → Settings → Login Security&lt;/strong&gt; gains a toggle: after a correct password, accounts with a contact email must enter a 6-digit code sent to their inbox — the pattern banking apps use, now built into your self-hosted instance.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Opt-in, per account&lt;/strong&gt; — off by default; accounts without an email keep password-only login, so nobody gets locked out during rollout&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Abuse-resistant&lt;/strong&gt; — codes expire after 60 seconds, allow 5 attempts, sending is throttled per IP and username, and a delivery failure fails closed instead of skipping the factor&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No new infrastructure&lt;/strong&gt; — reuses your existing cron/SMTP email settings and the contact fields from v3.3; no migration&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Ask AI streams its answers
&lt;/h2&gt;

&lt;p&gt;Responses arrive token by token instead of one block at the end — long analyses start appearing immediately while the model is still writing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Clickable record links&lt;/strong&gt; — answers reference rows as links straight to the record&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The answer of record&lt;/strong&gt; — the streamed text is a preview; the final payload carries the cleaned-up version with markdown, links and follow-up suggestions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A working off switch&lt;/strong&gt; — the admin's Ask AI enable flag finally gates the API (403) and hides the menu button and FAB&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  External API now serves PostgreSQL views
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;System → API&lt;/strong&gt; can bind a registered PostgreSQL view — aggregates included — instead of only a raw table, which is how integrations usually want to consume data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Columns and filters are re-checked against the &lt;strong&gt;live database&lt;/strong&gt; on every save and request; an altered view answers a clean &lt;code&gt;404&lt;/code&gt;, never a &lt;code&gt;500&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Views sort by their first selected column (no guaranteed &lt;code&gt;id&lt;/code&gt;); tables keep &lt;code&gt;id DESC&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Fixed: freshly generated API keys are adopted on save instead of being regenerated on every re-save&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Also in this release
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;CSV import moved to a proper service layer; cron output is plain text&lt;/li&gt;
&lt;li&gt;Version info moved from the login page to the footer&lt;/li&gt;
&lt;li&gt;New records open on the first tab&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Following this series?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/wrobeltomasz/opensparrow-v40-jsonb-columns-public-table-sharing-1dkk"&gt;OpenSparrow v4.0 adds a JSONB column type, public read-only table sharing, and many-to-many editing directly on the grid&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Websites
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://opensparrow.org" rel="noopener noreferrer"&gt;https://opensparrow.org&lt;/a&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>opensource</category>
      <category>rag</category>
      <category>ai</category>
    </item>
    <item>
      <title>OpenSparrow v4.0: JSONB columns, public table sharing</title>
      <dc:creator>Tomasz</dc:creator>
      <pubDate>Wed, 07 Oct 2026 15:35:03 +0000</pubDate>
      <link>https://dev.to/wrobeltomasz/opensparrow-v40-jsonb-columns-public-table-sharing-1dkk</link>
      <guid>https://dev.to/wrobeltomasz/opensparrow-v40-jsonb-columns-public-table-sharing-1dkk</guid>
      <description>&lt;p&gt;&lt;strong&gt;OpenSparrow v4.0 adds a JSONB column type, public read-only table sharing, and many-to-many editing directly on the grid&lt;/strong&gt; — plus a grid performance refactor: in-place cell updates, a side-fetch cache, and server-side sort and filters for large tables.&lt;/p&gt;

&lt;h2&gt;
  
  
  JSONB columns, validated before save
&lt;/h2&gt;

&lt;p&gt;The schema editor offers &lt;code&gt;jsonb&lt;/code&gt; alongside the existing types (&lt;code&gt;includes/admin/schema.php&lt;/code&gt;). JSON entered into such a column is validated client- and server-side before it reaches SQL (&lt;code&gt;validate_jsonb_column&lt;/code&gt; in &lt;code&gt;includes/api_helpers.php&lt;/code&gt;); the value is bound with a &lt;code&gt;::jsonb&lt;/code&gt; cast through &lt;code&gt;pg_query_params&lt;/code&gt;. Workflow form template fields understand the new type too. No migration — adding the column to an existing table is the usual schema-editor &lt;code&gt;ALTER TABLE&lt;/code&gt;, done on demand per table.&lt;/p&gt;

&lt;h2&gt;
  
  
  Public read-only table sharing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;System → Sharing&lt;/strong&gt; exposes one table through a public link — anyone with the link views the table grid without logging in, like sharing a spreadsheet on OneDrive. The page is &lt;code&gt;public/share.php&lt;/code&gt;, the data endpoint &lt;code&gt;public/api/share.php&lt;/code&gt;; config lives in &lt;code&gt;spw_config.shared_tables&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Security details that matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The token is never stored in plaintext — only &lt;code&gt;token_hash&lt;/code&gt; (via &lt;code&gt;secret_hash&lt;/code&gt;), and lookups use &lt;code&gt;hash_equals&lt;/code&gt; for constant-time comparison (&lt;code&gt;SharedTokenManager&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Hidden, system, and owner-restricted tables cannot be shared; the shared schema is stripped of subtables, many-to-many relations and images&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;m2m_options&lt;/code&gt; gates &lt;code&gt;other_table&lt;/code&gt;, so a share link cannot be pointed at an unrelated table; stale entries are pruned automatically on save&lt;/li&gt;
&lt;li&gt;The page sends &lt;code&gt;X-Robots-Tag: noindex, nofollow&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The grid, faster
&lt;/h2&gt;

&lt;p&gt;Four changes shipped together (&lt;code&gt;includes/frontapi/list.php&lt;/code&gt;, &lt;code&gt;public/assets/js/grid/&lt;/code&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Row cap&lt;/strong&gt;: full-table fetches carry an explicit cap — &lt;code&gt;MAX_LIST_ROWS&lt;/code&gt; (default 1000, overridable per table via &lt;code&gt;initial_limit&lt;/code&gt; in Schema → Table Properties). No loader can request an unbounded set.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Side-fetch cache&lt;/strong&gt;: the four auxiliary loaders (comment counts, subtable counts, m2m rows, image rows) fetch once per page signature and re-apply from their stores on re-render — two renders in a row don't refetch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In-place cell updates&lt;/strong&gt;: saving a cell mutates the local dataset and re-renders the grid — no full &lt;code&gt;loadTable&lt;/code&gt; round-trip.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hybrid client/server view&lt;/strong&gt;: if the response is truncated, the grid switches to server-side search, sort, and column filters — one combined request per change; "Load more" chunks continue the server ordering.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On top of that, the grid supports &lt;strong&gt;multi-column sorting&lt;/strong&gt; (up to three rules with priority icons), and virtual columns gained &lt;strong&gt;conditional icons&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Many-to-many editing on the grid
&lt;/h2&gt;

&lt;p&gt;M2M relations are now edited in a popover picker right on the grid, backed by two new API actions: &lt;code&gt;m2m_sync&lt;/code&gt; (writes, audited like any record change) and &lt;code&gt;m2m_options&lt;/code&gt; (reads, access-gated). The popover shares the same 1-second hover delay as the image preview.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backup retention, calendar week view, config autosave
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backup Tables → Global Settings&lt;/strong&gt; gained retention: keep the newest N backups, drop backups older than X days (both optional), a list of stored backups, and on-demand cleanup. Retention is applied automatically after each backup run (&lt;code&gt;backup_apply_retention&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;calendar&lt;/strong&gt; got a month/week view toggle — the week view renders an hourly grid with an all-day bar, and events move in both.&lt;/li&gt;
&lt;li&gt;The admin &lt;strong&gt;"Save config" button is gone&lt;/strong&gt;: configuration autosaves with optimistic versioning. If another admin saved in the meantime, you get a conflict notice instead of a silent overwrite.&lt;/li&gt;
&lt;li&gt;Schema and Views editors were restyled to the common section-card layout.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Following this series?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/wrobeltomasz/opensparrow-v39-makes-the-board-and-calendar-fully-touch-friendly-21ig"&gt;OpenSparrow v3.9 — touch drag &amp;amp; drop for board and calendar, and a demo-free setup wizard&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Websites
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://opensparrow.org" rel="noopener noreferrer"&gt;https://opensparrow.org&lt;/a&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>opensource</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>OpenSparrow v3.9 - makes the board and calendar fully touch-friendly</title>
      <dc:creator>Tomasz</dc:creator>
      <pubDate>Mon, 28 Sep 2026 15:30:03 +0000</pubDate>
      <link>https://dev.to/wrobeltomasz/opensparrow-v39-makes-the-board-and-calendar-fully-touch-friendly-21ig</link>
      <guid>https://dev.to/wrobeltomasz/opensparrow-v39-makes-the-board-and-calendar-fully-touch-friendly-21ig</guid>
      <description>&lt;p&gt;&lt;strong&gt;OpenSparrow v3.9 makes the board and calendar fully touch-friendly and moves the CRM demo out of the setup wizard.&lt;/strong&gt; A shared pointer-drag shim brings drag &amp;amp; drop to touch devices; the demo is now an opt-in in &lt;strong&gt;Admin → Demo&lt;/strong&gt; after first login.&lt;/p&gt;

&lt;h2&gt;
  
  
  Touch drag &amp;amp; drop for board and calendar
&lt;/h2&gt;

&lt;p&gt;HTML5 drag &amp;amp; drop never fires &lt;code&gt;dragstart&lt;/code&gt; / &lt;code&gt;dragover&lt;/code&gt; / &lt;code&gt;drop&lt;/code&gt; for touch pointers — dragging a board card on a phone did nothing. Workarounds (long-press timers, hijacked &lt;code&gt;touchmove&lt;/code&gt;) fail: the browser decides the scroll-vs-drag gesture before JavaScript can react.&lt;/p&gt;

&lt;p&gt;v3.9 fixes this with CSS, not JavaScript: the shim (&lt;code&gt;public/assets/js/util/touch-dnd.js&lt;/code&gt;) sets &lt;code&gt;touch-action&lt;/code&gt; at wiring time — &lt;code&gt;pan-y&lt;/code&gt; on board cards (horizontal drags, vertical swipes scroll) and &lt;code&gt;none&lt;/code&gt; on calendar chips. The browser routes the gesture natively; no timers, no hijacked scrolling.&lt;/p&gt;

&lt;p&gt;Drags start by &lt;strong&gt;movement, not hold time&lt;/strong&gt; — a &amp;gt;12 px, direction-aware slop keeps taps as clicks. A ghost follows the finger, drop targets highlight, and &lt;code&gt;pointercancel&lt;/code&gt; cleans up. Gated on &lt;code&gt;pointerType === 'touch'&lt;/code&gt;, the shim leaves desktop HTML5 DnD untouched; both paths call the same move function (&lt;code&gt;moveCard&lt;/code&gt;, &lt;code&gt;moveEventTo&lt;/code&gt;) — one source of truth for optimistic update, API call and rollback. It wires only where modules gate on &lt;code&gt;canEdit&lt;/code&gt;. A same-pass fix puts the &lt;code&gt;drop-target&lt;/code&gt; class on the lane element itself, so the highlight renders.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup wizard no longer installs the demo
&lt;/h2&gt;

&lt;p&gt;The demo checkbox is gone; the wizard now does one thing: schema, system tables, migrations, and the first admin account with a one-time random password. The demo itself is untouched — after first login, &lt;strong&gt;Admin → Demo&lt;/strong&gt; offers every part (sample tables, dashboards, board, RAG knowledge base, demo accounts) as a reversible opt-in. &lt;code&gt;public/setup_api.php&lt;/code&gt; no longer fakes a logged-in admin session mid-setup, and the four &lt;code&gt;setup.*demo*&lt;/code&gt; i18n keys were dropped from all 20 language files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Smaller interface changes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nav menu items&lt;/strong&gt; got thin &lt;code&gt;1px&lt;/code&gt; borders, stronger on hover and active — structure without visual noise&lt;/li&gt;
&lt;li&gt;The sidebar &lt;strong&gt;collapse icon&lt;/strong&gt; right-aligns to the far edge of the menu, where it reads as "collapse this"&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Upgrade notes
&lt;/h2&gt;

&lt;p&gt;No database migration, no removed configuration keys; the release migration only deletes the unused &lt;code&gt;public/mock.html&lt;/code&gt;. One caveat: emulated pointers bypass native scrolling, so the shim was validated on real devices before shipping.&lt;/p&gt;

&lt;h2&gt;
  
  
  Following this series?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/wrobeltomasz/opensparrow-v38-roadmap-gantt-module-and-new-style-grid-4mei"&gt;OpenSparrow v3.8 — a Roadmap (Gantt) module and a spreadsheet-style grid&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Websites
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://opensparrow.org" rel="noopener noreferrer"&gt;https://opensparrow.org&lt;/a&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>nocode</category>
      <category>productivity</category>
      <category>opensource</category>
    </item>
    <item>
      <title>OpenSparrow v3.8 - Roadmap (Gantt) module and new style grid</title>
      <dc:creator>Tomasz</dc:creator>
      <pubDate>Tue, 22 Sep 2026 14:16:05 +0000</pubDate>
      <link>https://dev.to/wrobeltomasz/opensparrow-v38-roadmap-gantt-module-and-new-style-grid-4mei</link>
      <guid>https://dev.to/wrobeltomasz/opensparrow-v38-roadmap-gantt-module-and-new-style-grid-4mei</guid>
      <description>&lt;p&gt;&lt;strong&gt;OpenSparrow v3.8 ships a brand-new Roadmap (Gantt) module and gives the record grid a long-overdue new-style facelift.&lt;/strong&gt; The roadmap module turns any schema table into a read-only Gantt timeline — multiple roadmaps, category color chips, milestone markers — with zero new database objects. The rest of the release polishes what you look at all day: crisper table borders, a relocated sidebar toggle, refreshed branding and better color contrast on enum dropdowns. Below is a breakdown of what changed and why it matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Roadmap (Gantt) module
&lt;/h2&gt;

&lt;p&gt;The headline feature lives in &lt;code&gt;Admin → Data Management → Roadmap&lt;/code&gt;. You define one or more roadmaps, each bound to a schema table through a set of column mappings: which column holds the title, which ones the start and end dates, optionally a category column, a progress column and card columns for the row details. Everything is configuration in the existing &lt;code&gt;spw_config&lt;/code&gt; store — there is no new table, no migration, nothing to run after upgrading.&lt;/p&gt;

&lt;p&gt;On the frontend each roadmap renders as a read-only Gantt timeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A month grid with quarter shading and prev/next navigation to scroll through time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Milestone markers&lt;/strong&gt; for rows without an end date — one-off deadlines show up as distinct markers instead of phantom bars&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;category filter bar&lt;/strong&gt; — colored chips with a legend dot per category; click a chip to toggle the whole category on or off, and the filter state is remembered&lt;/li&gt;
&lt;li&gt;A per-roadmap color for the bars, plus configurable card columns showing extra data when you inspect a row&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbarf73ijila2ex7v5z34.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbarf73ijila2ex7v5z34.png" alt="New grid style" width="800" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Access control follows the existing per-user scope model rather than inventing a new one. A new &lt;code&gt;roadmaps&lt;/code&gt; scope gates the menu entries and the front API, and the bound table must additionally be within the user's table access — the same empty-grant-list-means-unrestricted semantics as every other scope. Granting a user a roadmap does not grant its table.&lt;/p&gt;

&lt;p&gt;The CRM demo dataset gained a &lt;code&gt;projects&lt;/code&gt; table with sample rows so you can seed a working roadmap in seconds, the UI strings are translated into all 20 locales, and the new endpoints are pinned by guard tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  The grid now looks like a spreadsheet
&lt;/h2&gt;

&lt;p&gt;If you spend your day inside the record grid, this is the change you'll notice first. The grid and view tables dropped the zebra striping in favor of new style cell borders every cell gets a border, the header row sits on a light background, and hover states highlight individual cells instead of whole rows. The result reads like a spreadsheet: dense, regular, easier to scan column-by-column. All colors come from the existing design tokens, so custom themes keep working.&lt;/p&gt;

&lt;p&gt;A static design reference for the new look ships as &lt;code&gt;public/mock.html&lt;/code&gt; — it isn't referenced by the application and is safe to delete from production deployments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Smaller interface changes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sidebar toggle relocated&lt;/strong&gt; — the desktop collapse toggle moved from the header to the bottom of the menu, where it belongs; the hamburger stays in the header on mobile&lt;/li&gt;
&lt;li&gt;Menu-list nav links now render at 14px &lt;code&gt;font-sans&lt;/code&gt;, aligning with the grid typography&lt;/li&gt;
&lt;li&gt;Branding assets were refreshed — a new &lt;code&gt;logo.png&lt;/code&gt; and a favicon that dropped from 207 KB to under 3 KB&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fixed
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Enum dropdowns keep readable text color via &lt;strong&gt;YIQ contrast&lt;/strong&gt; on both the grid and the edit/create form — previously a custom enum color could render white-on-light or dark-on-dark option text. The contrast is computed server-side and applied consistently&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docs.js&lt;/code&gt; inconsistencies across admin modules were repaired, along with a broken Polish string in &lt;code&gt;docs-strings.js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The cron, anonymization and ETL log docs now describe the status values correctly as &lt;code&gt;running / success / error&lt;/code&gt; (they were documented as &lt;code&gt;ok&lt;/code&gt;), in &lt;code&gt;docs/DATABASE.md&lt;/code&gt; and in the column comments emitted by the system-tables DDL&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Upgrade note
&lt;/h2&gt;

&lt;p&gt;No database migration and no configuration keys are removed by this release. Roadmap configuration lives in the existing &lt;code&gt;spw_config&lt;/code&gt; store under the &lt;code&gt;roadmap&lt;/code&gt; key; there is nothing to run or clean up after upgrading. For per-user access, remember the &lt;code&gt;roadmaps&lt;/code&gt; scope is independent — users also need table access to see any roadmap content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Following this series?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/wrobeltomasz/opensparrow-v37-brings-an-automation-email-queue-to-the-admin-panel-and-unifies-the-entire-admin-43d8"&gt;OpenSparrow v3.7 — Automation Email Queue and a Unified Admin UI&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Websites
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://opensparrow.org" rel="noopener noreferrer"&gt;https://opensparrow.org&lt;/a&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>postgres</category>
    </item>
    <item>
      <title>OpenSparrow v3.7 - brings an automation email queue to the admin panel and unifies the entire admin UI</title>
      <dc:creator>Tomasz</dc:creator>
      <pubDate>Fri, 18 Sep 2026 05:58:12 +0000</pubDate>
      <link>https://dev.to/wrobeltomasz/opensparrow-v37-brings-an-automation-email-queue-to-the-admin-panel-and-unifies-the-entire-admin-43d8</link>
      <guid>https://dev.to/wrobeltomasz/opensparrow-v37-brings-an-automation-email-queue-to-the-admin-panel-and-unifies-the-entire-admin-43d8</guid>
      <description>&lt;p&gt;&lt;strong&gt;OpenSparrow v3.7 brings an automation email queue to the admin panel and unifies the entire admin UI around a single section-card pattern.&lt;/strong&gt; The email queue gives admins full visibility and control over outgoing automation emails — filter, requeue, bulk delete, purge — while the rest of the release is a consistent overhaul of how admin screens are built: one card style, one width, one typography scale, one statistics tile pattern. Below is a breakdown of what changed and why it matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automation email queue
&lt;/h2&gt;

&lt;p&gt;A new section in &lt;code&gt;Admin → System → Cron&lt;/code&gt; lists the queued automation emails stored in the existing &lt;code&gt;spw_automation_emails&lt;/code&gt; table. Until now those rows were written by the notification cron but invisible from the UI — if a message bounced or sat undelivered, there was nowhere to see it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Filter the queue by status — pending / sent / error — with live per-status counters&lt;/li&gt;
&lt;li&gt;Bulk requeue resets the attempts counter and error message, so failed sends retry cleanly&lt;/li&gt;
&lt;li&gt;Bulk delete works on up to 500 rows at once&lt;/li&gt;
&lt;li&gt;Purge by status, optionally limited to entries older than a configurable retention window&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The queue tab reports a missing table instead of failing, so an instance that never ran email automations degrades gracefully.&lt;/p&gt;

&lt;h2&gt;
  
  
  RAG document preview
&lt;/h2&gt;

&lt;p&gt;A Preview button in the Centrum AI documents list opens a modal with the stored document content — filename, tags, size and dates included — so you can inspect what the RAG pipeline actually chunked without downloading the file. The rechunk action now shows success feedback, and the documents table respects container width with vertically stacked action buttons on narrow screens.&lt;/p&gt;

&lt;h2&gt;
  
  
  The RAG section is now Centrum AI
&lt;/h2&gt;

&lt;p&gt;The admin section formerly known as RAG is renamed to &lt;strong&gt;Centrum AI&lt;/strong&gt; across the sidebar, breadcrumbs, demo copy and guidance docs. The RAG settings fields gained explanatory tooltips covering context-file limits, upload size and timeout constraints — previously you had to check the docs to know what a field accepted.&lt;/p&gt;

&lt;h2&gt;
  
  
  One admin UI pattern instead of twenty
&lt;/h2&gt;

&lt;p&gt;The bulk of the release is invisible until you open two tabs side by side. Every settings form, data table and log in the admin panel now renders inside a &lt;code&gt;buildSectionCard&lt;/code&gt; section with a consistent header style:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Section cards are never nested — one card is one section, a tab is a flat sequence of cards in reading order&lt;/li&gt;
&lt;li&gt;All settings tabs share one width via the &lt;code&gt;.admin-page&lt;/code&gt; wrapper; per-panel &lt;code&gt;max-width&lt;/code&gt; inline styles were removed&lt;/li&gt;
&lt;li&gt;Statistics and log tabs (External API Usage, Click Statistics, Cron email queue, Users) follow the same tile-grid pattern as Centrum AI → Query Statistics&lt;/li&gt;
&lt;li&gt;Typography was unified to three sizes and two weights via CSS tokens; &lt;code&gt;.adm-sec-hdr&lt;/code&gt; styling now lives in the stylesheet instead of inline overrides&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fixed
&lt;/h2&gt;

&lt;p&gt;Admin tab renderers no longer wipe section descriptions when re-rendering a tab — content is now rendered into the inner content div instead of replacing the whole panel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upgrade note
&lt;/h2&gt;

&lt;p&gt;No database migration and no configuration keys are removed by this release. The email queue operates on the existing &lt;code&gt;spw_automation_emails&lt;/code&gt; table (created by earlier releases); if the table does not exist, the queue tab says so instead of erroring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Following this series?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/wrobeltomasz/opensparrow-v36-adds-an-external-api-module-dj1"&gt;OpenSparrow v3.6 — External API Module with Encrypted Read-Only Keys&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Websites
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://opensparrow.org" rel="noopener noreferrer"&gt;https://opensparrow.org&lt;/a&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>nocode</category>
      <category>productivity</category>
      <category>database</category>
    </item>
    <item>
      <title>OpenSparrow v3.6 adds an External API module</title>
      <dc:creator>Tomasz</dc:creator>
      <pubDate>Tue, 08 Sep 2026 15:41:24 +0000</pubDate>
      <link>https://dev.to/wrobeltomasz/opensparrow-v36-adds-an-external-api-module-dj1</link>
      <guid>https://dev.to/wrobeltomasz/opensparrow-v36-adds-an-external-api-module-dj1</guid>
      <description>&lt;p&gt;&lt;strong&gt;OpenSparrow v3.6 adds an External API module that lets admins expose table data to external services through read-only, encrypted API keys.&lt;/strong&gt; Each key is bound to a single table with a fixed column set, server-side filters and a row limit, and is served by a sessionless JSON endpoint that accepts nothing from the client except the key itself. Below is a breakdown of what changed and why it matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  External API module
&lt;/h2&gt;

&lt;p&gt;A new &lt;code&gt;Admin → System → API&lt;/code&gt; tab lets admins define read-only API keys. Each definition is bound to one schema table, a chosen set of columns, fixed server-side filters and a row limit, so the data an external service can see is decided entirely by the admin — never by the caller.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keys are stored encrypted (&lt;code&gt;key_enc&lt;/code&gt;) plus an HMAC &lt;code&gt;key_hash&lt;/code&gt; for constant-time lookup, and are never returned to the browser after generation&lt;/li&gt;
&lt;li&gt;A new or regenerated key is generated server-side and shown exactly once in a modal&lt;/li&gt;
&lt;li&gt;Filter values are type-checked against the column's schema type on save, so a stored filter can't 500 at query time&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A sessionless JSON endpoint
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;public/api/external.php&lt;/code&gt; is the single endpoint. External services authenticate with an &lt;code&gt;Authorization: Bearer &amp;lt;key&amp;gt;&lt;/code&gt; header and receive JSON rows.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The endpoint accepts no table, column or filter names from the client — everything comes from the config&lt;/li&gt;
&lt;li&gt;Hidden and system (&lt;code&gt;spw_&lt;/code&gt;) tables are refused, both on save and at request time&lt;/li&gt;
&lt;li&gt;Rate limiting is enforced per key and per IP, answering &lt;code&gt;429&lt;/code&gt; with a &lt;code&gt;Retry-After&lt;/code&gt; header&lt;/li&gt;
&lt;li&gt;Responses are &lt;code&gt;401&lt;/code&gt; (missing/bad key), &lt;code&gt;403&lt;/code&gt; (disabled), &lt;code&gt;404&lt;/code&gt; (configured table gone) and &lt;code&gt;429&lt;/code&gt; (rate limited)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Usage statistics
&lt;/h2&gt;

&lt;p&gt;A new &lt;code&gt;spw_external_api_log&lt;/code&gt; table records each successful request — API, table, rows returned and duration — with a stats/log view and a purge action in the admin module, so you can see exactly how your keys are being used.&lt;/p&gt;

&lt;h2&gt;
  
  
  Changed
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;includes/db.php&lt;/code&gt; gained &lt;code&gt;sys_table_prefix()&lt;/code&gt; / &lt;code&gt;is_system_table()&lt;/code&gt; helpers so the endpoint can refuse system tables.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;includes/crypto.php&lt;/code&gt; gained &lt;code&gt;secret_hash()&lt;/code&gt; (HMAC-SHA256) so keys can be matched without decrypting every entry.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fixed
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;nginx and Apache configs now forward the &lt;code&gt;Authorization&lt;/code&gt; header to PHP (&lt;code&gt;fastcgi_param HTTP_AUTHORIZATION&lt;/code&gt; / &lt;code&gt;SetEnvIf&lt;/code&gt;), so Bearer auth works out of the box instead of silently 401ing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Upgrade note
&lt;/h2&gt;

&lt;p&gt;No database migration and no configuration keys are removed by this release. The &lt;code&gt;external_api&lt;/code&gt; config key is created on first save; an unknown key is ignored, so the document upgrades itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Following this series?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/wrobeltomasz/opensparrow-v35-login-language-selector-searchable-fk-pickers-notification-polish-1089"&gt;OpenSparrow v3.5 — Language Selector, Searchable FK Pickers and Notification Timestamps&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Websites
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://opensparrow.org" rel="noopener noreferrer"&gt;https://opensparrow.org&lt;/a&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>php</category>
      <category>postgres</category>
      <category>webdev</category>
    </item>
    <item>
      <title>OpenSparrow v3.5 - Login language selector, searchable FK pickers &amp; notification polish</title>
      <dc:creator>Tomasz</dc:creator>
      <pubDate>Fri, 04 Sep 2026 15:13:20 +0000</pubDate>
      <link>https://dev.to/wrobeltomasz/opensparrow-v35-login-language-selector-searchable-fk-pickers-notification-polish-1089</link>
      <guid>https://dev.to/wrobeltomasz/opensparrow-v35-login-language-selector-searchable-fk-pickers-notification-polish-1089</guid>
      <description>&lt;p&gt;&lt;strong&gt;OpenSparrow v3.5 is a usability release that adds a language selector to the login page, searchable foreign-key pickers, and notification timestamps, while unifying the notifications dropdown with the shared panel style.&lt;/strong&gt; This update makes the app friendlier for international users, speeds up data entry on long foreign-key lists, and tidies a few small UI details. Below is a breakdown of what changed and why it matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Language selector on the login page
&lt;/h2&gt;

&lt;p&gt;Users can now pick their locale before signing in. The login form gains a language &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; populated from the available locales, and the &lt;code&gt;&amp;lt;html lang&amp;gt;&lt;/code&gt; attribute follows the active locale so screen readers and browsers render the page in the right language from the very first screen.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Locale is chosen before authentication, so the whole session starts in the user's language&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;&amp;lt;html lang&amp;gt;&lt;/code&gt; attribute now tracks the active locale instead of a hardcoded value&lt;/li&gt;
&lt;li&gt;Selection is driven by &lt;code&gt;I18n::availableLanguageMeta()&lt;/code&gt;, so new locales appear automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Searchable foreign-key pickers
&lt;/h2&gt;

&lt;p&gt;Edit and create forms replace the plain &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; on foreign-key fields with a searchable input backed by a &lt;code&gt;&amp;lt;datalist&amp;gt;&lt;/code&gt;. Long FK lists are now filterable by typing instead of scrolling through a dropdown.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New &lt;code&gt;public/assets/js/edit/fk-search.js&lt;/code&gt; wires up the search behaviour&lt;/li&gt;
&lt;li&gt;Typing filters the datalist; the selected value is synced to a hidden field carrying the real FK id&lt;/li&gt;
&lt;li&gt;Blurring with an unmatched value restores the original label, so invalid selections can't slip through&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Notification timestamps
&lt;/h2&gt;

&lt;p&gt;Each item in the notifications dropdown now shows its &lt;code&gt;created_at&lt;/code&gt; time, formatted with the browser's locale. You can tell at a glance how old a notification is.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub link in the footer
&lt;/h2&gt;

&lt;p&gt;A GitHub lockup badge now links to the project repository from the footer, so the source is one click away from anywhere in the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Changed
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The notifications dropdown was unified with the shared &lt;code&gt;bp-panel&lt;/code&gt; style (header, close button, overlay) instead of its own bespoke markup.&lt;/li&gt;
&lt;li&gt;Notes UI strings are now translated into all 19 locales.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fixed
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;#debug&lt;/code&gt; bar no longer flashes before JS hides it on page load — it is now hidden inline.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Upgrade note
&lt;/h2&gt;

&lt;p&gt;No database migration and no configuration keys are removed by this release. Upgrading is a drop-in replacement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Following this series?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/wrobeltomasz/opensparrow-v34-replaces-the-bundled-icon-set-with-material-symbols-outlined-and-adds-a-3jjj"&gt;OpenSparrow v3.4 — Material Symbols Outlined and a Searchable Icon Picker&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Websites
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://opensparrow.org" rel="noopener noreferrer"&gt;https://opensparrow.org&lt;/a&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>nocode</category>
      <category>postgres</category>
      <category>javascript</category>
    </item>
    <item>
      <title>OpenSparrow v3.4 — replaces the bundled icon set with Material Symbols Outlined and adds a searchable icon picker</title>
      <dc:creator>Tomasz</dc:creator>
      <pubDate>Wed, 02 Sep 2026 06:30:41 +0000</pubDate>
      <link>https://dev.to/wrobeltomasz/opensparrow-v34-replaces-the-bundled-icon-set-with-material-symbols-outlined-and-adds-a-3jjj</link>
      <guid>https://dev.to/wrobeltomasz/opensparrow-v34-replaces-the-bundled-icon-set-with-material-symbols-outlined-and-adds-a-3jjj</guid>
      <description>&lt;p&gt;&lt;strong&gt;OpenSparrow v3.4 is a design-focused release that replaces the bundled icon set with Material Symbols Outlined and adds a searchable icon picker to the admin panel.&lt;/strong&gt; This update modernizes the visual language across the whole application, gives admins a fast way to find the right icon, and cleans up a handful of smaller UI details. Below is a breakdown of what changed and why it matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Material Symbols Outlined
&lt;/h2&gt;

&lt;p&gt;The bundled icon set has been replaced with Google's Material Symbols Outlined. The new set lives in &lt;code&gt;public/assets/icons/material/&lt;/code&gt; and carries the full collection — &lt;strong&gt;3,899 SVG files&lt;/strong&gt; at optical size 24, normalized to 48×48 so they match the intrinsic size of the PNG icons they sit next to.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7cp7sl6myne0faxsa4ma.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7cp7sl6myne0faxsa4ma.jpg" alt="Icon picker screenshot" width="800" height="589"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;84 files removed from &lt;code&gt;public/assets/icons/&lt;/code&gt; (83 PNG plus &lt;code&gt;chevron-down-white.svg&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The dropdown arrow on &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; elements is now an inline data URI in &lt;code&gt;styles.css&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Eight PNG icons the system still references by name (&lt;code&gt;book_3s&lt;/code&gt;, &lt;code&gt;delete&lt;/code&gt;, &lt;code&gt;folder_zip&lt;/code&gt;, &lt;code&gt;grid_on&lt;/code&gt;, &lt;code&gt;notifications&lt;/code&gt;, &lt;code&gt;picture_as_pdf&lt;/code&gt;, &lt;code&gt;search&lt;/code&gt;, &lt;code&gt;settings&lt;/code&gt;) stay where they are&lt;/li&gt;
&lt;li&gt;Generated manifests (&lt;code&gt;index.json&lt;/code&gt;, &lt;code&gt;tags.json&lt;/code&gt;, &lt;code&gt;README.txt&lt;/code&gt;) ship alongside the SVGs&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Searchable icon picker
&lt;/h2&gt;

&lt;p&gt;The icon field in the admin now gets a proper picker instead of a bare text input:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Matches icon names and the full Google tag list&lt;/li&gt;
&lt;li&gt;Understands common Polish words (e.g. "ciężarówka" → local shipping)&lt;/li&gt;
&lt;li&gt;Category chips for quick filtering&lt;/li&gt;
&lt;li&gt;Remembers recently picked icons in the browser&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Upgrade note: icon paths live in the database
&lt;/h2&gt;

&lt;p&gt;This is the one thing to know before upgrading. Icon paths are stored in the database, not in code — the &lt;code&gt;menu_icon&lt;/code&gt; keys of the dashboard, calendar, files, board, views and workflows config, the per-table icon in the schema config, and the printout icons.&lt;/p&gt;

&lt;p&gt;A stored path naming one of the removed files stays valid as far as validation is concerned and renders as a broken image — nothing rewrites those values automatically. Repoint them in the admin with the Browse picker. 79 of the 84 removed names have an identically named &lt;code&gt;.svg&lt;/code&gt; under &lt;code&gt;assets/icons/material/&lt;/code&gt;, so &lt;code&gt;assets/icons/NAME.png&lt;/code&gt; usually becomes &lt;code&gt;assets/icons/material/NAME.svg&lt;/code&gt;. The exceptions are &lt;code&gt;calendar.png&lt;/code&gt; (use &lt;code&gt;calendar_month.svg&lt;/code&gt;) and &lt;code&gt;folder_zip_blue.png&lt;/code&gt;, &lt;code&gt;grid_on_blue.png&lt;/code&gt;, &lt;code&gt;picture_as_pdf_blue.png&lt;/code&gt;, which have no direct counterpart.&lt;/p&gt;

&lt;p&gt;No database migration and no configuration keys are removed by this release.&lt;/p&gt;

&lt;h2&gt;
  
  
  Also in this release
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Removed the GitHub link from the login page&lt;/li&gt;
&lt;li&gt;Fixed demo image seeding; long lines wrapped; Files menu icon unified&lt;/li&gt;
&lt;li&gt;Updated grid action icon names in the admin docs&lt;/li&gt;
&lt;li&gt;Refreshed module demo video links and images in the README&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Following this series?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/wrobeltomasz/opensparrow-v33-click-statistics-production-readiness-check-and-deployment-hardening-170h"&gt;OpenSparrow v3.3 — Click Statistics, Production Readiness Checks, and Deployment Hardening&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Websites
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://opensparrow.org" rel="noopener noreferrer"&gt;https://opensparrow.org&lt;/a&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>opensource</category>
      <category>productivity</category>
      <category>nocode</category>
    </item>
  </channel>
</rss>
