23 October 2026¶
Twelve 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
- Datatable: its own words in the application's language, and its controls on one line
- Groups and segments: a tab each, and what a group needs to replace an application's own
- Groups: a group's page in the screens' own markup and the application's language
- Groups and segments: row actions, and where a group's name leads
- Groups and segments: the All tab back, and the tabs an application keeps
Conversations: a person the reader sees anyway has a profile¶
Changed¶
People::profile()andperson/{id}answer for somebody who may not use the conversations when the reader sees them anyway: they are in a conversation the reader reads — history brought in, an account whose access ended — or the reader reads them all. Such a profile hascanMessagefalse and no direct conversation. A stranger is still null, and 404.- Every profile carries
canMessage: false for the reader themselves too.
Documentation¶
- Conversations guide: The page and the dock (a person, as a client shows them), The HTTP API.
Conversations: the dock and a basic page; the full page is the application's¶
Changed¶
- The framework draws the dock over every page and a basic page at
/admin/Conversations; a full chat application — a sidebar in sections, threads and activity views, a quick switcher, its own window and install, the beacon's browser client — is the application's to build on the JSON API, which keeps every action it needs. - The dock's list is its own: the conversations newest first, a filter, a new conversation, the channels to join, files and space, the application's links, and the faces above the button.
- The basic page (
pf-conversations-basic.js,Client::basic()) is plain JavaScript with no build: the list with what is unread, one conversation, earlier messages, a box to write in, live or read again every few seconds. An example of the API, dressed by the theme's own classes.
Removed¶
- From
pf-conversations.js: the page, the sidebar, the inbox views, the switcher, the own window and install, the beacon's browser client; their logic helpers and tests. - The actions
manifestandworker,resources/js/conversations/sw.js, andClient::props()'sapp.Client::app()stays, for an application's own manifest. - The
pf-conv-*rules nothing draws any more, from each theme'scomponents.css; the words only they showed, fromel.php.
What breaks in subclasses¶
- A subclass of
Controllers\Conversationsthat calls or overridesmanifest()orworker(): serve them from the application's own controller; the base class no longer has them.
Documentation¶
- Conversations guide: The dock and the basic page, When the server is down — the beacon.
- Upgrade Guide: the full page is the application's.
Conversations: people's pictures, from the application¶
Added¶
conversations.avatars: a callable given the ids and the reader, answering an image's address for each person it has a picture of;People::avatars($ids, $reader)asks it once for many and keeps only http(s) addresses and paths of the site, for the ids asked about.- The JSON carries them:
avatarsinconversations,avataron a conversation's members and inperson/{id}. The dock draws them on its faces, its list, its windows and their messages, and falls back to the initials where a picture is missing or does not load.
Fixed¶
- A dock window's composer keeps a long placeholder (Message and three names) on its one line, cut short, instead of wrapping a second line out of sight.
Documentation¶
- Conversations guide: The dock and the basic page (faces), The HTTP API.
Moderation: bans, mutes, kicks, slow mode, a rate, a filter, reports and a log¶
Added¶
- The
moderationfeature (Pramnos\Moderation), for anything people write, in scopes the application names: - bans and mutes of a subject — an account, a visitor, an address or a nickname — in a scope or everywhere, for a while or for good, and lifting them;
- kicks, told as
moderation.kicked; - each scope's slow mode;
Gate::check(): a message refused for a restriction, the slow mode, the rate (moderation.rate) or the filter, or let through filtered;Filter: listed words masked or refused, links allowed, masked or kept to some sites, and markup that would run refused;Reports, filed once per reporter with what was said as it was, then resolved or dismissed;- and a log of all of it. Its tables are personal data.
- With
conversationstoo, every message and edit in a conversation goes through moderation in the scopeconversation:{id}, and the moderators — a conversation's owner, or whoever hasadmin.chat.moderate— have/mute,/unmute,/ban,/unban,/kickand/slow.
Documentation¶
- Moderation Guide, new.
- Conversations guide: Moderation.
Conversations: mark unread from a message¶
Added¶
Conversations::markUnread($id, $person, $messageId)(FW-257): what the person has read goes back to the top-level message before that one — before its root, for a reply — or to nothing, even when they had read further. The actionunread/{id}withmessage.conversation.readon the person's own channel, whenever they read or mark unread, with the conversation'slastReadId,unreadandmentions; the dock counts again on it.Conversations::counts($id, $person, $after): what is unread after a message, and the mentions.
Documentation¶
- Conversations guide: Messages, Live: what browsers are told, The HTTP API.
Conversations: a reply also sent to the conversation¶
Added¶
Messages::post()takesbroadcastwiththread(FW-258): the reply stays in its thread and is listed with the conversation's own messages too, withbroadcastand arootPreview— the root's id, writer and first words. It counts as unread like them and is the conversation's newest;broadcastwithout a thread is refused.post/{id}takesbroadcast.conversation_messages.broadcast, by the migrationadd_broadcast_to_conversation_messages.Messages::inStream(): a query kept to what a conversation lists.conversation.activitycarriesbroadcast; the dock shows such a reply in the conversation, with Replied to a thread: above it, which opens the thread.
Documentation¶
- Conversations guide: Messages, The HTTP API.
Audience: the segments and groups list is a datatable¶
Changed¶
/admin/Segmentslists segments and groups in a server-side datatable (FW-259). Its rows come from the newSegments/data: the name with the description, the kind, the members, who entered and who left in seven days, what uses it, the owner, and when it was last worked out. The most members come first. It sorts by any column but used by, searches name and description, pages, and copies or exports. The tabs stay above it with their counts and are sent as the filter. What uses a segment is worked out for the rows shown only./admin/Groupslists groups the same way, from the newGroups/data, the most members first. The members are counted in one query, where the list counted them one group at a time.
Added¶
Html\Datatable\ArraySource::answer(): a server-side datatable's answer from rows in memory, for a list that is not one table's rows. The cells are drawn for the page alone.Audience\Segments::movements(): every segment's week of entries and departures, in two queries.
Documentation¶
- HTML Components guide: A list that is not one table's rows —
ArraySource. - Audience guide: The screens. Authorization guide: The screen (groups).
Datatable: its own words in the application's language, and its controls on one line¶
Fixed¶
- DataTables' own words — "Show 50 entries", "Showing 1 to 5 of 5 entries", "Search:", First /
Previous / Next / Last, the empty and no-match notes — were DataTables' English on every
Html\Datatable. They come throught()now, under keys of their own (Show _MENU_ rows,Rows _START_ to _END_ of _TOTAL_,Search the table:,First page…), with the framework's Greek. - A table with Buttons puts the buttons, the page length and the search on one line, in
.pf-datatable-top, the search at its right; the buttons take the room left (at least 12rem) and wrap among themselves, and below 640px the line wraps. The search used to fall to a line of its own. - Column headings do not wrap, and
.pf-nowrapkeeps a cell on one line, so a wide table scrolls instead of breaking a name over three lines. - The segments library: the kind reads
Audience segmentorUser group— keys of their own, since an application's one-wordGroupcan already mean something else — the last working out isd/m/Y H:i, and the members, owner and date stay on one line.
Documentation¶
- HTML Components guide: It draws with the project's UI system.
Groups and segments: a tab each, and what a group needs to replace an application's own¶
Changed¶
/admin/Segmentsis Groups and segments (FW-260): a tab each for groups, segments and the account's own, groups first, each with its own datatable — columns, picker, export name — and no All tab or kind column. Groups show their number, members, order and what uses them, by the order; segments their members, the week's movement, uses, owner and last working out; one's own the same without the owner. A segment not yet worked out reads being worked out. The button makes what the tab lists.audience.default_tabnames the first tab. The column reads Members everywhere.- A group's page reads its members a page at a time from
Groups/members/{id}, searched by username, name, email or number, where it read every member at once. It says how many there are, what uses the group, and links the application's own list of them (audience.group_members_url). /admin/Groupslists the number and the order too, by the order first, and finds a group by its number. The form sets the order, -127 to 127 or none.- A group in use is not deleted: the refusal says what uses it.
Added¶
Audience\Groups::usage()/uses()/reset(): what uses a group — segments whose filter names it, rules that read it or change it, and what the application reports, with a link where there is a page.Filter::groupsIn(),Rules\Definition::groups(),Rules::linksUsingGroup().Controllers\Groups::all()(every group with its members, in one query) andmembersUrl().Html\Datatable::$exportName: the file an export is saved as.
Documentation¶
- Audience guide: Groups, The screens. Authorization guide: The screen (groups). HTML Components guide: It draws with the project's UI system. Upgrade Guide: a row.
Groups: a group's page in the screens' own markup and the application's language¶
Fixed¶
- A group's page (
/admin/Groups/view/{id}) is drawn in thepf-*markup the library and a segment's page use, the same in every theme, and every word on it goes throught()— the title, the buttons, the member search, the members' columns, the roles. The name is the title once, with the description under it; Delete, Members in the user list and Edit sit beside it; the members, the order and what uses the group are figures and links at the top, and Delete is disabled while something uses it. The breadcrumb leads back to the library's groups tab. The members export as Members of …. - The roles section shows only where there are roles to hold or give, and
'audience' => ['group_roles' => false]hides it. - The Groups screen opens where the
usergroupsfeature is on or its tables are there: an application that had the tables before the feature does not enable it, which would have its migrations make them again.
Documentation¶
- Authorization guide: The screen (groups). Upgrade Guide: a row.
Groups and segments: row actions, and where a group's name leads¶
Added¶
- Row actions on the library's groups tab (FW-261): open, edit and delete, as icons with their
names. Delete posts with the token and asks first; while something uses the group it is
disabled, its title saying what, and the server still refuses. Not sorted by, not exported.
'audience' => ['row_actions' => ['groups', 'segments']]gives segments and one's own the same;['groups']unless set. 'audience' => ['group_link' => 'members']: a group's name — in the library and on a person's card — leads togroup_members_url, the application's own list of its members, instead of the group's page.Controllers\Groups::linkFor()is the one place that decides.Html\Datatable\Column::$sClass, andpf-no-export: a column left out of what every button copies, prints and saves..pf-row-actions, and a disabled.pf-actiondimmed, in every theme'scomponents.css.
Documentation¶
- Audience guide: The screens. HTML Components guide: It draws with the project's UI system.
Groups and segments: the All tab back, and the tabs an application keeps¶
Changed¶
- The library has an All tab again (FW-263), first: groups and segments in one datatable
(
pf-all-table, exported as Groups and segments) with name, kind, members, what uses each and each row's own actions — groups first, each in its own order, and any column sorting the whole. A group's name followslinkFor(). Its count is both; it offers New group and New segment. 'audience' => ['tabs' => […]]keeps some of the four tabs, in the library's order; adefault_tabnot among them gives way to the first that is, and a request for a tab not shown is answered as that one.
Documentation¶
- Audience guide: The screens. Upgrade Guide: the groups-and-segments row brought up to date.
Fixed¶
- The library's names were the row-wide link (
.pf-row-link, stretched over the row), which covered the row's actions and used by chips: a click on Edit opened the group. A name is a plain link now,.pf-row-name, beside links of their own.