Changelog
A running, date-ordered log of notable framework changes — independent of version
releases . Newest first; browse older entries by month from the archive.
Looking for a specific version instead of a date? See the curated
Releases list.
Seven entries today:
Conversations: a person the reader sees anyway has a profile
Conversations: the dock and a basic page; the full page is the application's
Conversations: people's pictures, from the application
Moderation: bans, mutes, kicks, slow mode, a rate, a filter, reports and a log
Conversations: mark unread from a message
Conversations: a reply also sent to the conversation
Audience: the segments and groups list is a datatable
Continue reading
Forty-four entries today:
The rule editor offers templates and lists, and wears the theme
Person events are kept in the change log
A/B tests
Person events drain where the change log's feature is off
The audience screens, redesigned
Ask about audiences
The audience screens, second review
Event transports, and events:status
The event stream, on the database
The event stream on the Queue screen
The event stream on Redis Streams
Conversations: the engine
Conversations, live
Conversations: files
Conversations: search
Conversations: the HTTP API
Realtime: presence members and whispers in pfRealtime
Conversations: the page, the dock and the files screen
Conversations: the dock's windows, and a first review
Conversations: posting in place of a chat webhook
Conversations: references, commands, and a rule that tells the team
Conversations: @Ask
Conversations: the beacon, for when the server is down
Conversations: an app that opens while the server is down
Push: the site's worker, on a page with a worker of its own
Conversations: a sidebar in sections, and a window of their own
Conversations: their own window is the whole window
Conversations: threads, activity, drafts and sent, saved, all unread
Conversations: people who stopped signing in
Beacon: no signing key, no beacon
Ask: everybody's conversations, read only
Ask: the page's own links behind a gear
Ask: other people's conversations leave out the reader's own
Ask in the conversations: a conversation of its own, and the reader's rights elsewhere
Ask's answers reach the conversations from any worker
Ask's charts drawn in the conversations, its card in the reader's language
Tables in the conversations; a chart's long category names cut to fit
Other people's Ask conversations: folded steps, drawn charts
Help pages' headings are the levels they write
Conversations: history from another system, written through the API
Conversations: the application's own links at the foot of the list
Conversations: bringing history in from another system is the application's
Reactions: every emoji its own, on MySQL too
Conversations: a person, as a client's card and profile show them
Continue reading
Fifty entries today:
schedule:list sees the work process that is running
work no longer skips a minute
broadcast:serve holds its lock, so the orchestrator stops restarting it
A page signed in by an application's own login gets the realtime connection
queue:process writes a line per task into a file instead of its dashboard
A link can open its page in a dialog — data-pf-modal
The services screen is the state of the workers; a pool's settings have a page of their own
A slow command no longer holds the schedule, and every run is recorded
A page opened in a dialog shows its content, centred
data-confirm on a form asks when it is sent, not at every click inside it
Every tab of a site shares one realtime connection
./dockertest opens the coverage report only when asked
A shared realtime connection that cannot start leaves each page its own, and says what it is doing
A page can offer its forms to an AI agent in the browser (WebMCP), and the debug bar shows them
The schedule has a screen: what runs, when, how it went, and what is late
The admin and the debug toolbar offer tools to an AI agent in the browser
The notification bell links to every notification, and a message's subject is its title
Enabling the debug toolbar returns to the page it was opened from
A notification arriving changes the tab's title, can play a sound, and tells the page
A rich-text editor: pictures with captions, uploads, a link search, and plugins
The rich-text editor in a Svelte SPA
The rich-text editor can float a toolbar over the block at the cursor
An editor whose textarea sits in its label is drawn after the label
The rich editor takes a title into its column, and its packages go beside the project's own
The mail log records the account each message was for
The tab's title flashes only for a notification that is news
The rich editor's buttons have words of their own
The query string reaches $_REQUEST behind a rewrite without QSA
A message's text part reads as a message
Message forms have saved messages, placeholders, writing with AI and a signature
An email's preview opens its links and does not count as an open
The message composer asks in its own panels, and a message to one person inserts the person's name
Events that run in the queue
work loses no minute to a restart
The outbox deadline sweep gets an index
Domain events as classes
Scheduled tasks an operator may adjust
Audiences: attributes of a person, and filters over them
Segments: named audiences kept up to date
Group membership through one path, with events
Consent lists: what a send asks before it reaches somebody
An audience builder and picker with a live count
The Segments and Consent lists screens, and segments on the person card
A mass message's audience from segments, groups, a filter and a consent list
What people did: person events, recorded from event classes, pages, email and the past
Audiences do nothing until their tables exist
Rules: when, who, what, until — run in the queue
Trying a rule before it runs
Messages on the site: banners, popups and cards
The Rules screen: the list, the editor, a rule's boxes and its tools
Continue reading
Forty-one entries today:
The sso and registration headings, and two second-factor messages, in the site's language
A page that prints the time is cached again
A migration reaches the long-running workers
prepareQuery() on PostgreSQL reads a DATE_FORMAT pattern written for printf
A schema is no longer glued to the word before a backticked name on PostgreSQL
The query builder quotes a qualified mixed-case column on PostgreSQL
REGEXP, RLIKE and NOT REGEXP on PostgreSQL
A PHP bool reaches a smallint flag on PostgreSQL
User::save() writes active on PostgreSQL
An MCP client sees the tools again
Widening applications.callback no longer stops at a materialized usage_statistics
create_oauth2_application_grants_table replaces a legacy view of another shape
Model::_load() throws on PostgreSQL when its query fails
CONCAT() with a NULL is NULL on PostgreSQL too
applications.oauth2_active_tokens is one row per token again on PostgreSQL; the summary is …_summary
db:migrate-from-mysql makes a char(n) a varchar(n)
DELETE … LIMIT and UPDATE … LIMIT through query() on PostgreSQL
Clearing a cache whose prefix holds a : clears it
When the password last changed, and a way back from the change-password page
A webhook endpoint's signing secret can be shown again, and is never empty
Every built-in webhook type fires, and only to the user's own applications
An application can leave the framework's help pages out
whereLike(): text search that ignores case and accents on every database
mail:flush waits for mail queued for later
The daemon orchestrator stops its daemons before it exits, and its job name is its own
Queue handlers from app.php, and a worker that takes only what it can run
Scheduled commands in process, and the queue's retention as configuration
Alerts in the Tailwind views read as one block
The queue screen is a DataTable, with stop, duplicate and an application's own columns
mail:flush takes an application's own checks, counts and switch
The Roadmap notes a dashboard test that sometimes skips itself
Queued mail keeps its names, and a refusal for now waits before the next attempt
A notification reaches an open page at once
The queue screen shows what the queue is doing, each task, and its last two days
The queue screen's time columns say they are times
Email history: failed reads Failed, and an application's statuses, columns and resend
Http\Client::json() throws on data it cannot encode
broadcast:serve reads app.php and reaches every user's notifications
The orchestrator waits out its whole shutdown grace
An admin shell's breadcrumb is the only one, and the email preview stops throwing
The queue's task and statistics pages leave the breadcrumb to the shell too
Continue reading
Seventy entries today:
db:migrate-from-mysql: the dry run names a backfill time column without an index
db:migrate-from-mysql --defer-indexes
db:migrate-from-mysql --zero-date, NUL bytes counted, one conversion for move and verify
db:migrate-from-mysql: the dry run names triggers, views, routines and events
db:migrate-from-mysql: an enum keeps its list
The MCP endpoint lists only the scopes it takes (FW-157)
MCP initialize carries the analyst's instructions (FW-161)
A loopback redirect matches on any port (FW-159)
Public MCP tools: roles and gates, hooks around every call, a missing scope named (FW-158)
MCP scopes declared in app.php, for consent served by another application (FW-158 §3)
oauth.scope_refusal => 'drop': scopes a role may not grant left out, not refusing (FW-158 §4)
A box per scope on the consent page (FW-158 §5)
audited() names the account and the outcome; redacting() spares user (FW-162)
database.translate_mysql: MySQL-written SQL run on PostgreSQL (FW-160, phase 1)
PRAMNOS_TEST_SETTINGS: an application's suite against PostgreSQL (FW-160, phase 3)
db:dialect-lint: SQL that would mean something else on PostgreSQL (FW-160, phase 2)
AuthorizedApplications: one place to list and revoke what a person authorized (FW-163)
A test that left organisations in the shared test database
A refused query says why, to a developer (FW-165 §4)
A statement that failed inside a transaction no longer breaks the next one (FW-165 §1)
Wrong second-factor codes are limited (FW-164 §1)
second_factor_everywhere: the second factor owed by the whole session (FW-164)
The lockout fails open, the two-factor reset forgets trusted browsers, sign-in devices translate (FW-164 §2–§4)
Bytes on PostgreSQL: Binary, bytea read as bytes, text kept in a blob named (FW-165 §5)
db:migrate-from-mysql --implicit-defaults (FW-165 §6)
The crop dialog's tools are icons
The second-factor gate runs the application's login flow, counts against the lockout, returns to the page (FW-166)
db:migrate-from-mysql: bigint unsigned becomes bigint (FW-165 §2)
db:migrate-from-mysql: long index and key names stay distinct (FW-165 §3)
translate_mysql: an upsert added to after it was translated (FW-165 §7)
translate_mysql: SHA2, CAST AS SIGNED, DAY/MONTH/YEAR, named locks, JSON_EXTRACT, DELETE through a join, an alias in HAVING, FOREIGN_KEY_CHECKS (FW-165 §8)
db:dialect-lint: SQL built over several lines, prose left alone, ORDER BY, --schema (FW-165 §9)
Binary in prepareQuery()
Slow dashboard cards: shared figures and dashboard:precompute (FW-167)
What's new: help pages announced to the administrators (FW-168)
The second-factor gate: trusted browsers, a stopped session that stays stopped, a lockout said (FW-169)
dashboard:precompute: the application's cards, a controller, variants nobody saved (FW-170)
db:migrate-from-mysql: binary and bit defaults carry over (FW-172)
The page cache: a flush that clears only the pages, the shared Redis, pages kept gzipped (FW-171)
A second step owed before the application is up; the precomputed cards' controller (FW-173)
A message's body read from the file an application keeps it in
Click links issued from the message's send date, the same each time
The debug toolbar's timers measured with the monotonic clock
A sent message's body in the body store, read back as it was sent
mail:bodies-to-database: bodies from files into the compressed table
A templated message: kept once, filled for each recipient
Bounces and blocked addresses: no mail goes to them, a password reset included
Helpers::getBrowser() parses an agent once a day, not once a call
device-detector's rules kept in the application's cache
Erasure and retention reach the body store
WebhookHandler::onBranchStaged(): composer runs beside the live tree (FW-174)
mail:hypertable: the mail log as a TimescaleDB hypertable, with the site up
mail:bounces --imap: the bounce mailbox over IMAP, with a password or XOAUTH2
The Roadmap's mail-body entry removed: the body store shipped
The change log: events without a model, on behalf of somebody, and a page at a time
Helpers::objectDiff(): a null property is not a removed one
Writing history into compressed hypertables: the policy paused for a move, the queue for spooled late rows
The notification bell in every header and in the SPA, settling what is dealt with, and keeping the feed tidy
A record's history, in the administration, on the site and in the SPA
The Roadmap's remember-me entry brought up to date
The auth screens in Greek, and whose server it is on every card
/Status: a public status page
New scaffolds register no auth addon: remember-me works without one
Setting up an authorization server, end to end
A recreated test database starts with nothing remembered about the old one
mail:bounces --imap --limit reads the newest unseen messages
An organisation's roles, written and checked for a fraction of the queries
A controller segment reaches its class in any case
The auth screens, finished: the theme's colour, closed registration, the account overview and organisation screens in Greek
The page cache stores no page that prints who its visitor is
Continue reading
Eighteen entries today:
A user's _ properties are never written to userdetails
schema:diff on an installation that adopted its tables, and foreign keys looked for in their own database
db:migrate-from-mysql: an application's data from MySQL into PostgreSQL
db:migrate-from-mysql: a live move in three phases — operational, cutover, backfill
db:migrate-from-mysql --transport=fdw, and the test image with mysql_fdw
db:migrate-from-mysql from MariaDB
db:migrate-from-mysql --source-timezone: a DATETIME read in the zone that wrote it
db:migrate-from-mysql: a table it makes gets MySQL's indexes and foreign keys
db:migrate-from-mysql: verify, a cutover that compares, composite keys, deletions in ranges, no snapshot
The rate limit's token bucket survives a clock that steps back
The query builder prefixes a bare table name, as the schema builder does
/health/check keeps its findings for administrators; the client_credentials iss is the issuer
db:migrate-from-mysql: the source read in utf8mb4, double-encoded text found and repaired
db:migrate-from-mysql into identity and computed columns
db:migrate-from-mysql: the dry run names what compared without regard to case
db:migrate-from-mysql: hypertables verified window by window, and each backfill window checked
db:migrate-from-mysql --phase=operational takes up a copy that stopped
db:migrate-from-mysql --batch-mb: batches bounded by bytes
Continue reading
Three entries today:
The authorization server and the public MCP endpoint, from the first installation to switch them on
Migrations that converge with an older schema: order, names, a key rebuilt every run, duplicate tokens
schema:diff: an installation's tables against the framework's shape, and --apply
Continue reading
Forty-nine entries today:
The DevPanel's policy callback decides the development route only
schedule_task requires a kind
A page left open refreshes its CSRF tokens
Dashboard cards: four widths, settings, and drawn one at a time
A reminder asked for in a new conversation leads back to it
Dashboard cards are dragged and resized with a finger as with a mouse
A page for scheduled AI tasks and shared reports
The SPA's Scheduled screen: every kind and cadence, runs, shared reports
Deleting an account hands its tasks over and erases what the assistant kept
The dashboard's catalogue, fields, and figures only when read
A partial falls back to the bundled scaffold, as a template does
Dashboard drag that a theme cannot break, by the title too; flash messages translated
Scheduled tasks for the analyst, and the right time shown on MySQL
A card's title row keeps the pointer
The analyst's scheduled page: its buttons, its floor, its words and its links
Four more dashboard cards: queue, email, AI spend, errors
push:setup finds the worker it is told about, and checks the icon and the routing
Every enqueued script and stylesheet is on the page once
Hooks for the scheduled page, read-only AI cards, a push switch, and dashboard cards chosen by id
A shared report's maker chooses its readers
Push subscriptions need this site's token; push.js speaks the page's language and survives a replaced URL; admin pages' assets are not cached
Navigation preload, a worker address that versions itself, mtime asset versions, and a stored miss with its Cache-Control
The sign-in credential is a token per device, not the password hash
Readers' roles from the application, a hook when they change, translated refusals and card labels, cards tuned from app.php
Core Web Vitals from real visitors
Shared reports by email, and a task's model calls marked as its own
One broken browser can no longer fill the PHP pool with error reports
A dashboard card's period, set by each person
Every message channel on one card
What's new, from the help, on the dashboard
Browser errors of extensions and other hosts' scripts are not kept
A sign-in token is spent where the hash was expected, and "log in as" ends the token it replaces
Who is online now, and signing one session out
The page cache: cookies PHP queued, the remember-me cookie, and a CDN header
Signing in as somebody else, and back without a password
The worker's immutable addresses from its registration, and the page cache's Pramnos-Page signal
A login marker cookie
Speculation rules
Public pages in the service worker
A calendar: FullCalendar in the catalog, and pf-calendar.js
Signing a session out by its sid, and a page whose controller rewrites the query
Cropping and resizing a picture before it is uploaded, on the profile picture card first
project:install registers in code, and TestClient keeps the application's registrations
The activity log writes wherever its table is, oauth:keys, and a registered client never half-stored
An audit trail you can write and read, and the crop dialog in a real browser
Which OAuth scopes a person may grant, by role
The crop dialog zooms with a slider
The audit log's moments on MariaDB, objects beside targets, exclusions and a retention per event
No offset strings into MariaDB: the language-model tables, the push log and the activity log
Continue reading
Thirty-five entries today:
An administration area theme, in the Theme Guide
Writing views that survive a UI switch
Moving a separate admin application into the area
"Saved." is shown
project:switch-ui keeps what it replaces
Smaller things the documentation review found
An administration that is its own application, declared in its config
Testing an application's JavaScript with the framework's runner and helpers
A SPA section inside a server-rendered page
The SPA's shared components follow the UI system
The side panel's buttons and look are the theme's
The notification feed, and a bell for it
A whole Svelte SPA follows the UI system
The log screens under another address, and a search box with its term
The tailwind theme's component styles in a file of their own
Every UI system has its components.css
Charts with no inline script, in the theme's colours
A template cannot change the render that included it
Settings forms no longer turn checkboxes on or blank settings
Errors in the browser are recorded
The service worker can replace an application's own
Data tables draw with daisyUI or the plain stylesheet
A Redis token bucket refills whatever the float precision
A dashboard each administrator arranges
Html\Datatable: values in its script are encoded (XSS)
Html\Datatable: the footer's text search filters again
Html\Datatable: a column picker and a readable size under tailwind
The chat folds what the model said on the way
Charts added after load, and on a phone
Scheduled AI tasks: reminders, one-offs, runs that cannot run twice
The model schedules, changes and cancels tasks itself
Shared AI reports, subscribed to and continued in Ask
The service worker leaves the administration area and the API alone
project:resync --spa-components creates what an updated component imports
Roles: who counts as an administrator, a superuser or root, said once
Continue reading
Forty-six entries today:
The analyst's download link, from the queue worker
A side panel on every administration page: Ask about it, and its help
The examples go as soon as a question is sent
The analyst's links go where the question was asked
The analyst's scoped tools on MCP as well
Pagination says each link's page for a script
The examples go on Bootstrap too
Help pages are a feature
The side panel's conversation runs once, and the application's help page comes first
Each request's own page, where one process serves many
Nothing offers the AI until there is a key
A geometry value cannot leave its literal
Every guide checked against the code
A generated model loads
The webhook provider reads its documented configuration
health:check runs what /health runs
A logger's own log() is what the helpers reach
The AI's settings from Settings
Help links in an administration of its own
assetUrl() from a subdirectory
A migration cutoff in its own shape is read
An account's failed pushes are its own
CORS where init writes it
--rest-api alone is a yes
An application that is the administration says so
A regex keeps its commas; starts_with takes any of its values
A read on its own line goes to the replica
A namespaced seeder runs
Adminer's own login works when autologin is off
Small corrections in the commands
Docblocks that described what the code does not do
The page cache's lock is one lock on Memcached too
A theme is built from where it was found
The pager's window keeps its width at the ends
The datepicker brings its stylesheet
A search source's own rule and title
A search source without a count
What the model writes beside a tool call is kept
Help for the site's visitors, apart from the administration's
An export link is not a card number
Help in a SPA
Site help by plan, one page for several screens, and the assistant's help tool
Help search finds the page that lists the question, and the SPA panel draws it
The site's help button: an icon, or the application's own
Navigation, in the Theme Guide
How a controller finds its model
Continue reading
Thirty-eight entries today:
Response::send() leaves an earlier status line alone
Recording sends is an application setting; a missing SMTP host says so
Pagination reads a page number from a URL whatever it is
Closing a connection another instance closed is a no-op
A table's footer search reads nothing undeclared and stays within its table
A date field is ten characters wide, and its form-control is the project's to drop
A model in JSON is its data
Reading — and taking — a column's search value before the Datasource runs
A multiple-choice column filter reads as its values
A user that did not load is saved as a new one
Language models: a client, a tool loop, a call log and one admin screen
/admin/Ai shows the application's own sections
An empty order is no order
Conversations with a model: memory, background questions, schedules and consent
A conversation's assistant: its bag, its clock, and a limit only on adding
Screens for conversations: a JSON API, a page, a widget and three Svelte components
A resync keeps the components a project changed; conversation screens under llm/
The conversation screen: a waiting line, word of an answer, and the application's own formats
The administrators' analyst: questions about the installation, from its own figures
A reserved word is quoted wherever a column is named
A push is recorded at the moment it was sent, in every zone
Ask: the analyst's own page at /admin/Ai, and a conversation page that answers in place
The language-model tables are read under the installation's prefix
A page loading a framework script the project never published says so
An application's own chat tables take a conversation outside any organisation
A migration that made its table is not declined for want of its hypertable
Every framework migration runs on an installation with a table prefix
MariaDB: dropping a CHECK, and a column's CHECK after its COMMENT
The framework's queries name its tables as its models do
A queue worker's task sees the settings as they are now
A guest inside the administration area signs in and comes back to it
The analyst counts days in the application's zone, and its rule about people is the application's
A table in an answer is readable in every theme
Example questions above a new conversation
The analyst writes its own SQL, and files to download
A conversation reads like one: whose, when, and a copy button
A short export shows in the answer; a table copies into a spreadsheet
Charts in an answer
Continue reading
Four entries today:
A null date is no date, not today
Datasource answers a 1.9-era caller of a 1.10 request
Clearing a cache category works with an underscored prefix
Developer pages served from an area link within it
Continue reading
Eight entries today:
A grant row overrides its own grant, not the defaults
Default grants and client authentication methods in app.php
The password grant honours the login lockout
No redirect off the site after sign-in
Device sign-in events reach only the device's application
Public clients need not send the secret they ship with
Default scopes in app.php
A refresh after a failed sign-in keeps the destination
Continue reading
7 changes:
A grant for an application speaks that application's vocabulary
A client assertion is checked in full, and accepted once
Revocation authenticates the client, and reaches refresh tokens
An application can sign its user out and have them sent back
The device, jwt-bearer and token-exchange grants
One controller's legacy-ACL answer is no longer every controller's
What each application may do, and how much
Continue reading
5 changes:
The administration API answers to administrators, and the search box to everybody
A profile picture a user can set
A profile picture is always stored as JPEG, and WebP is accepted
A profile picture from Gravatar, on the user's request
A phone photo is turned upright
Continue reading