Официальный сайт SLAED CMS
Журнал изменений
Step 102 of the roadmap. The data functions of the monitor move into core/monitor.php, where the module and the home cockpit read the same figures through one short-lived store. The cockpit gets its snapshot - four rings with the rows of their panels, the strip of the request and a tone - and fourteen signals of the lamp by the thresholds of the owner, answered as JSON by a poll; the view of the cockpit follows in step 103.
Core changes:
- Monitor in the core (core/monitor.php, admin/modules/monitor.php, tests/Unit/BackupContractTest.php):
getCpuDetails(), getCpuDetailsRaw(), getDirectorySizeBytes(), getBackupArtifacts(), getStorageDirectorySizes(), getLastBackupRunLabel(), getUploadsSizeLabel(), getDbIssueEventHours() and getMonitorRuntimeExtras() move unchanged; the module keeps its page, charts and template variables
getMonitorCache() replaces the four copies of the APCu block (memory on Windows, CPU details, panel snapshot, runtime extras)
- APCu when it is on, otherwise a JSON file of Cache in storage/cache/data, the request above both
- the key carries the site, so two sites on one PHP pool no longer share a snapshot of journals and backups
- getErrorLogCountHours() takes the journals to count; the monitor still counts all four
- Cockpit snapshot and signals (core/monitor.php, core/system.php, public/index.php):
getCockpitData(): CPU, RAM, disk and OPcache rings with value, tone and panel rows, the strip (generation, SQL queries, PHP memory, online) and the tone of the worst signal
getSystemSignals(): fourteen checks, failures first, each with level ok, warn or danger, the fact of a failure and its page; the journal, queue, schedule and backup counts are kept a minute
- the twelve of the etalon plus mail (a letter waiting longer than an hour) and update (a release found, red when
it fixes security)
- PHP errors warn from 1 and fail from 100 a day, database errors from 1 and 10, disk over 75% warns and under 10%
free fails, 20 refused logins and any security event warn, a job overdue by an hour warns, a backup older than
dump_age or switched off warns and none at all fails; HTTPS follows the address of the site
- getSelfCheckState() gives the verdict of the self-check as data; getSelfCheckAlert() draws from it
- the poll is go=5&op=getCockpitData with the token, for a super-administrator only
Backup age and mail queue (config/security.php, admin/modules/security.php, core/classes/mail.php, tests/Unit/MailQueueTest.php):
- the key dump_age (days, 7) with the field "Backup age limit, in days" under the backup switch of "Security"
- Mail::getPending(int $age = 0) is public and counts only rows waiting longer than $age seconds when asked
- Tests (tests/Unit/CockpitTest.php, tests/Support/cockpit_probe.php):
the signals over journals, self-check, backup, schedule, update, logins and mail in scratch, the rolled back rows not counted, HTTPS by the site address, the store kept and refreshed, every part of the snapshot
- Texts and plan (admin/lang/*.php, docs/12-ADMIN-UI-2026.md, docs/ROADMAP-2026.md):
_ADM_CPU, _ADM_RAM, _ADM_DISK, _ADM_CORES, _ADM_FREQ, _ADM_USED, _ADM_AVAIL, _ADM_FREE, _ADM_HITS, _ADM_MEMORY, _ADM_SCRIPTS, _ADM_QUERIES, _ADM_PHPMEM, the _ADM_SIG_* labels and facts and _SEC_DUMP_AGE in all six locales
- the answers of step 14 are recorded, step 102 is ticked; step 111a records the route race on MariaDB 11.6.2+
Benefits:
- the monitor and the cockpit read one source, and without APCu the monitor no longer recomputes on every poll
- one lamp sums what an administrator has to look at, with the page that answers each check
- the step-103 view only draws: every figure, tone and text comes ready from the snapshot
Technical notes:
- no schema or route change; new go=5 op getCockpitData and the security key dump_age
the home does not call getCockpitData() yet; on Windows a fresh snapshot costs about 2 s of PowerShell, so the view of step 103 fills its rings from the first poll
RouteRegistryTest::twoWritersRaceForOnePath fails on MariaDB 11.6.2+ (innodb_snapshot_isolation): Route::addRoute() gets error 1020 instead of waiting on the key; independent of this step, scheduled as step 111a
Steps 99-101 of the roadmap in one commit. "Configurations" takes the form canon with a check, a summary and a find of its settings, and every POST form of the panel asks before its changes are left behind. Thirty screens of groups A and B move onto getTplAdminList() with labelled filters, in-place tabs and pagers, a lamp in the row and deferred removal with Undo; no panel list but the file manager builds its head and foot any more.
Core changes:
Form canon (partials/form.html, partials/module-head.html, admin/modules/config.php, core/helpers.php, admin-ui.js, slaed.js, partials/group-form.html, admin/modules/groups.php):
the key is_valid gives a form novalidate, a check after the first try and on leaving a field, and the summary sl-form-sum with links into hidden tabs; a tab holding a change shows sl-tab-dot
- the key find of the module head (getTplAdminTabs()) searches the settings of the page; Ctrl+K lists them as a group
every POST form carries data-sl-dirty and data-sl-leave; setDirtyForms() measures against the loaded values and _ADM_LEAVE asks before a link, another form or the window takes the page
- the group form uses the same mechanism; _GROUPS_LEAVE and the data-sl-rank-* hooks are gone
- every field of the tab "Mail" has its label; logo and editor lists are named
Group A on the list canon (modules/node, modules/search, modules/voting, modules/account admin, admin/modules/ favorites.php, privat.php, ratings.php, admins.php, groups.php, categories.php, core/admin.php):
Node items, support queue and types, favorites, private messages, votings, search log and top, rating votes, users, new users, OAuth links, administrators and the points journal are drawn by getTplAdminList()
- getAdminFavoriteList(), getAdminPrivateList() and their go=5 entries leave core/admin.php; toplist() is folded into
search(bool $top)
- "New" and "OAuth" get working filters, OAuth a pager instead of LIMIT 100
- "Categories" and the group ladder stay components of their own with the canon filter, a lamp and deferred removal
- a lamp that stays in its row is data-sl-later="lamp <field>"; a Node move says _ADM_MOVED
- deleting a group clears grp of its users; a write that did not happen is refused
Group B on the list canon (admin/modules/messages.php, modules.php, lang.php, scheduler.php, database.php, security.php, referers.php, newsletter.php, uploads.php, modules/master/admin/index.php):
messages, modules, languages, scheduler, the three database tabs, the journals, journal view, logins and both ban lists of "Security", referers, campaigns, mail queue and dead addresses, master releases, sites and notes
every removal of the group is deferred without a question; TRUNCATE/DROP, clearing referers, module tables, releasing or stopping a mailing and the master keep it, and clearing referers gains one
- the lists of "Messages" and "Blocks" write no expiry: an expired row says _EXPIRED, turning it on clears the date
"Optimize" and "Repair" run only by POST (op optimize, repair) and redirect to their tab, which shows the result per table once; opening a tab runs nothing
- the ban lists are op=banlist&kind=ip|user, each with its add form under the list
- referers count their groups and page in SQL; master notes page by 50; a second list of a module keeps its own sort
"Blocks" stays its own component with a lamp and deferred removal; the file manager filter is the canon one and its boxes name their file
List canon frame (core/helpers.php, partials/admin-list.html, fragments/list-filter.html, tabs-link.html, toast.html, partials/div.html, theme.css, tools/ui-contract.php):
list-filter takes a plain field (itype, value, is_count) beside a list; admin-list takes foot_html for a form under the list inside the redrawn frame
- the dead flags is_search_line and is_inline_filter and the rules sl-inline-filter-form are removed
Classes and tests (core/classes/rating.php, core/classes/privat.php, tests/Support/rating_probe.php, privat_class_probe.php, tests/Unit/RatingTest.php, PrivatClassTest.php, PointOwnersTest.php):
- Rating::getRatingList($scope, $id, $page, $limit) pages by number, newest first
- Privat::deleteAdminMessage() refuses when no row was removed
- Texts, plan and baselines (admin/lang/.php, lang/.php, docs/, tools/-baseline.json):
_ADM_LEAVE, _ADM_UNSAVED, _ADM_FILL, _ADM_BADVAL, _ADM_EDITED, _ADM_SETFIND, _ADM_MOVED, _EXPIRED, _DATEFROM, _DATETO in all six locales; _GROUPS_LEAVE removed
- the answers of steps 11-13 are recorded, steps 99-101 ticked; docs/RATINGS.md and docs/POINTS.md follow the contract
- the label crawl holds 434 routes with no finding, the audit baseline is stored
Benefits:
- one list canon for every panel list: filter, address, pager, views, lamp and Undo behave the same everywhere
- no state change rides a GET any more on the database tabs, and no list writes while it is drawn
- refused writes are told instead of reported as success
Technical notes:
- no schema change; new ops optimize and repair of "Database", new address key kind of the ban lists
- getRatingList() changes its signature: the cursor after is replaced by page and limit
- the go=5 entries of favorites and private messages are removed; their lists are built by their modules
The comment list takes the third part of the list canon of the panel: status and removal act at once and reach the server only after ten seconds an Undo can still take back, the pick counts itself and can widen to the whole filtered list, which goes in portions, and the rows answer to keys. The section fan of the menu and the home cards switches a module the same way.
Core changes:
- Deferred actions (core/system.php, templates/admin/assets/js/admin-ui.js):
- setRedirect() answers a POST carrying later with 204 or 409 alone, without a flash or an address
a form marked data-sl-later acts at once and posts itself after ten seconds; Undo takes it back
- a row that leaves hands its focus to the next row; a section flips its lamp, its dim and its titles
- a refusal or any answer other than 204 brings the row back and says so in a warning notice
- leaving the page sends what waits by sendBeacon; a list about to redraw sends it first
- once nothing waits the list redraws quietly in place and keeps the focus on its row
- Comments on the action canon (admin/modules/comments.php, core/classes/comment.php):
the status is a button of its own column; the fan offers reply on the site, all comments of the author and of the address, and removal without a question
op sweep and Comment::setAdminPortion() work the whole filtered list in portions of 100 by ascending id
- each row goes through the single write, so points and counters move as before
- the bound is the newest id the list showed, so nothing that arrived after the decision is touched
- approve, delete and actions report a refused write instead of success; the view link opens the right page
List canon frame (core/helpers.php, partials/admin-list.html, fragments/table-cells.html, table-row.html, dial.html, checkbox.html, toast.html):
- getTplAdminList() draws the bulk row from data: the count "Selected: N" at a fixed width, a labelled action and OK
- a whole page picked offers all N of the filter, with progress and a pause while the portions run
- the cells take stat (the status button and its form), rows a name, fan items later, checkboxes aria_label
- Keys (templates/admin and templates/lite fragments/pager-keys.html, pager.html, admin-ui.js, slaed.js):
- J and K walk the rows, X picks, E edits, / goes to the search of the list
- the key tip is a fragment of each theme, the pagers stay one file; a list without pages keeps the tip on its left
- an address ending in #reply-ID opens the site page in the answer to that comment
- Theme (templates/admin/assets/css/theme.css, tools/ui-contract.php):
- sl-stat, sl-list-picked and the outline of the row in hand; the divergent key tip is explained in the contract
- the audit baseline is stored
Tests and tools (tests/Support/contract_probe.php, tests/Unit/CommentStateTest.php, PagerKeysTest.php, tools/label-audit.mjs, tools/label-audit-baseline.json):
- the sweep is proven against the single write: portions, cursor, scope, bound and the same points
- PagerKeysTest reads the tip from pager-keys.html of both themes
- the label crawl reads chng beside search as data; ten account routes became one, the baseline holds 432
- Texts, help and plan (admin/lang/.php, admin/info/comments/ru.md, admin/info/modules/ru.md, docs/):
_ADM_GONE, _ADM_TURNON, _ADM_TURNOFF, _ADM_PICKED, _ADM_PICKACT, _ADM_PAGEALL, _ADM_ALLN, _ADM_ALLON, _ADM_UNPICK, _ADM_SWEPT, _ADM_KEYROW, _ADM_KEYPICK, _ADM_KEYEDIT and _COMMENTS_AUTHOR, _COMMENTS_ADDR, _COMMENTS_ONSITE, _COMMENTS_PICK in all six locales
- the help describes the actions, Undo, the pick and the keys; the answers of step 10 are recorded and step 98 ticked
Benefits:
- moderation without a reload or a question, and every action can be taken back for ten seconds
- one action canon for every panel list of steps 12-13, driven by data attributes and one rule of setRedirect()
- axe on "Comments" falls to the one finding of the stand debugger
Technical notes:
- no schema or route change; new op sweep of "Comments"; a POST field later changes only the answer of setRedirect()
- the bulk action names a=status and d=delete as before; the form keeps working without JavaScript
- the stand's config/local.php has to be rebuilt after the CSS and JS change, as after every release
The comment list takes the second part of the list canon of the panel: the canon draws the rows from their cells and names every cell by its column, so one table carries four views, stays readable as cards on a phone and keeps its head in view. Tablesort now binds on the first load, which no sortable table of the panel or the site did before.
Core changes:
- List canon rows and view (core/helpers.php, partials/admin-list.html, fragments/table.html, table-cells.html):
getTplAdminList() takes rows as cells and gives each cell the name of its column; rows_html stays for the screens not yet on the canon
the view switch stands at the right end of the row over the table: "Стандартный", "Полный", "Компактный", "Карточки", one stop of Tab, the arrows walk it
- the cookie density, read by getTplAdminList(), puts data-sl-density on the table in the first byte
- table-cells.html gains label (data-label), stamp (<time datetime> and data-sort) and text_html (sl-cell-text)
- table.html carries the key a kept sort is stored under and the words that say it
- Option op (admin/index.php):
- setAdminOption() takes the key density: full, compact or cards set the cookie, anything else deletes it
- Comments on the row canon (admin/modules/comments.php):
the rows are cells; the date is a stamp, the comment text a text cell, its popover a prefix with its own label ("Подробности: Комментарий N") and the id of its panel
- the title of the comment cell no longer shows the markup of the search highlight
- Behaviour (public/plugins/system/slaed.js, templates/admin/assets/js/admin-ui.js):
slaed.js runs before tablesort.min.js in the deferred bundle, so the tables now wait for the end of the bundle; every data-sl-table-sort table of the panel and the site sorts and shows its arrows on the first load
- a table naming its key gets the last sort of its viewer back quietly and says a new one on data-sl-said
- the view applies at once and is stored through op option; a refusal puts the view back
- the date of a list reads relative in the language of the page, the exact moment stays in its title
- the rows a filter, a tab or a page brings enter one after another; a list Back restores only stands
- Theme and canon fragments (templates/admin, templates/lite, tools/ui-contract.php):
the views, the sticky head, the tint of the row in hand and the stronger one of a picked row in theme.css admin
- cards on a phone with the names of their columns; a pick puts the bulk action at the foot of the screen
- the cascade fills backwards only, so no row stays a stacking context over the tip of the row above
popover.html of both themes opens from a real button beside its panel, with tip_label and tip_id
- the panel is no part of the button any more, so the IP link in it stays a link (axe nested-interactive)
- --sl-d-order is declared in the data list of the contract; the audit baseline is stored
- Texts, help and plan (lang/.php, admin/lang/.php, admin/info/comments/ru.md, docs/*):
_DETAILS in the site language, _ADM_VIEW, _ADM_STD, _ADM_FULL, _ADM_DENSE, _ADM_CARDS in the panel language, all six locales
- the help of "Comments" describes the views, the sort, the date and the tip
- the answers of step 9 are recorded in docs/12-ADMIN-UI-2026.md and step 97 is ticked in docs/ROADMAP-2026.md
Benefits:
- one row canon for every panel list of steps 12-13: the module passes cells, the phone cards come with them
- sorting works on the first load on every sortable table, not only after an htmx swap
- the popover is a named button whose panel a screen reader reads, in both themes
Technical notes:
- no schema or route change; op option gains the key density and the cookie user_c-density
- the tables that never sorted on the first load now do: a visible change on every sortable screen of both themes
- the stand's config/local.php has to be rebuilt after the CSS and JS change, as after every release
The comment list is the first consumer of the list canon of the panel: one helper draws the head, the table or the empty state and a pager over and under it inside one element that htmx redraws in place, and the footer of the panel answers such a request with the title and the content only, so no module carries code of its own for it.
Core changes:
- List canon frame (core/helpers.php, core/system.php, partials/admin-list.html):
getTplAdminList() renders #sl-list with hx-target this, outerHTML, hx-history-elt and the header X-SL-Part
- the pager is drawn twice: the copy over the list carries "Down" to #sl-list-end instead of "Top"
- the empty state says "nothing found" under a filter and "all checked" in a calm tone for an empty queue
- the shell carries the line a screen reader hears, copied into the status line of the layout after a swap
- setFoot() answers X-SL-Part with the title and the content only; a request without a session gets HX-Refresh
- getTplAdminTabs() takes the key part: the tabs that switch the list in place carry hx-get with a pushed address
- Comments on the canon (admin/modules/comments.php, fragments/list-filter.html):
getCommentsFilter() replaces six copies of the filter parsing and keeps num in the way back
- edit, save, approve, delete and the bulk action return to the same page when the tab does not change
the filter moved from PHP strings into a fragment: every field labelled, the module applies at once, the search on OK
- OnChange="submit()" is gone; data-sl-filter-now submits the form, htmx carries it inside a live list
- Behaviour (public/plugins/system/slaed.js, templates/admin/assets/js/admin-ui.js):
- a list loading is aria-busy under the loading mark; a failed load keeps rows and filters and offers "Retry"
- the tab title takes the count of the queues again after a swap
- Back or Forward redraws a restored history element through htmx:historyRestore, the Node list of the site included
- Theme and canon fragments (templates/admin, templates/lite):
- pager.html of both themes gains is_top; the dead hx_get branch of tabs-link.html serves the in-place tabs
the filter, the list head, the notes, the empty and busy states in theme.css; the key tip and the ways up and down of every panel pager in their tones; under 900px the module head stacks its title over its filter
- fragments/module-foot.html and the .sl-module-foot rules are deleted: the canon owns the bottom row now
- three coincidental rule bodies are recorded in tools/ui-contract.php; the audit baseline is stored
- Texts and plan (lang/.php, admin/lang/.php, docs/12-ADMIN-UI-2026.md, docs/ROADMAP-2026.md):
- _PAGEEND in the site language, _ADM_LISTERR and _ADM_CLEAN in the panel language, all six locales
- the answers of step 8 are recorded, step 96 is ticked, the Tablesort load order found on the way goes to step 9
Benefits:
- one frame for every panel list of steps 12-13, without a partial check in any module
- a filter, a tab or a page no longer reloads the panel, and Back returns the previous view
- the filter is labelled and announced: axe on "Comments" falls from 30 to 27 nodes
Technical notes:
- no schema, route or op changes; the list keeps its query parameters modul, search, chng, status and num
- the stand's config/local.php has to be rebuilt after the CSS and JS change, as after every release
Steps 94 and 95 of the roadmap, batches 6 and 7 of the panel plan: the quick jump Ctrl+K and the rail of the etalon demo/admin moved into the theme admin, and the idle limit of the administrator session with a warning window that counts down, extends the session and leaves for the login at zero.
Core changes:
- Palette Ctrl+K (fragments/palette.html, layouts/admin.html, admin-ui.js, theme.css):
One palette on the shared setPaletteOpen(), printed next to "Опции"
- the group "Действия" folds the menu, sets the three colour modes, the motion and opens "Опции"
- "Разделы" are clones of a <template> row from the links of the menu or the cards of the home
- The home cards get their search; _EDITOR_NONE becomes the global _NONE_FOUND that both palettes read
- Rail (core/system.php, admin/index.php, layouts/admin.html, admin-ui.js, theme.css):
- The cookie rail goes through op option; isThemeFlag(key) replaces isThemeStill() and reads still and rail
- data-sl-rail stands in <html> from the first byte; the key [ folds and unfolds the menu
- The folded rail shows the menu and "Ждут внимания" as icons; the other blocks step out until batches 16 and 19
- Idle limit (config/security.php, core/security.php, admin/modules/security.php):
- The key idle (seconds, 3600) with a field in minutes on "Безопасность" and a hint of the limit in effect
- getAdminIdle() answers the lesser of idle and session.gc_maxlifetime, never under a minute
The time of the last action is $_SESSION[admin_c.'-time']; core/security.php drops an expired session on every request of the panel and the site, and renews it on every request of the panel without X-SL-Poll
- Both logins set the time: the panel (admin/index.php) and the light mode of an update (admin/update.php)
- End of session window (layouts/admin.html, admin/index.php, core/admin.php, admin-ui.js):
#sl-session on sl-modal sl-modal-sm sl-modal-warn opens two minutes before the limit and counts down
- "Продлить сессию" posts op keep to updateAdminSession(), which checks the token and answers 204
- "Выход" is the link op=logout; at zero the page leaves for the login with a GET of its own address
- The poll of the bell answers {left, rows}, so every tab follows the actions of the others
- Polls are no actions (admin-ui.js, public/plugins/system/slaed.js):
- The bell sends X-SL-Poll in its fetch
A request htmx fires by an every trigger without an event gets the header in htmx:configRequest
- the poll of "Мониторинг" and the live block "Пользователи" of the site no longer keep a session alive
- Language, tests and reference (admin/lang/.php, lang/.php, config/modules.php, tests/, tools/, docs/*):
_ADM_GOTO, _ADM_GOFIND, _ADM_PARTS, _ADM_ACTS, _ADM_FOLD, _ADM_UNFOLD, _ADM_END, _ADM_LEFT, _ADM_LOST, _ADM_KEEP, _SEC_IDLE and _SEC_IDLE_ON in the six panel locales, _NONE_FOUND in the six site locales
- The module master is registered in config/modules.php, inactive
- Every probe that fakes an administrator session sets its time as well
The audit baseline is stored; the answers of steps 6 and 7 stand in docs/12-ADMIN-UI-2026.md; steps 94 and 95 ticked in the roadmap
Benefits:
- A section, a theme mode or the menu fold is one key away on every screen of the panel
- An unattended panel closes itself after the idle limit and warns before work is lost
- A poll or a live block never holds a session open, whatever screen is left open
Technical notes:
- New config key security.idle and session key $conf['admin_c'].'-time'; new cookie $conf['user_c'].'-rail'
Breaking: a session opened before this version has no time and ends once; the JSON of getAdminQueueData is now an object {left, rows} instead of an array
- A template reads a {{ _CONST }} of at most 12 characters, hence the short _ADM_END … _ADM_KEEP
Step 93 of the roadmap, batch 5 of the panel plan: the window "Опции" of the etalon demo/admin stores every answer through one op option with a token and a 204, the page applies it at once, and the new row "Движение" stills the panel and the site from the first byte by a cookie the server reads.
Core changes:
- One op for the options (admin/index.php):
setAdminOption() checks the token of the scope option and stores the key mode, still or editor
- mode and still as cookies through setCookies(), the editor on the _admins row and in the session
- an unknown key or an unreadable session answers 400 and is logged as refused, a bad token 403
- The ops mode and changeeditor of the panel are gone with setAdminThemeMode() and updateAdminEditor()
- The language keeps its form with the op newlang and its redirect: the one row that reloads
- Motion from the first byte (core/system.php, core/security.php, five document templates):
- isThemeStill() reads the cookie still; the pages get is_still next to mode
data-sl-still stands in <html> of layouts/admin.html and bare.html of both themes and partials/site-header.html
- the switch of batch 1 (--sl-time-scale: 0) follows it in the panel and on the site, with no inline script
- Rows of the window (core/admin.php, fragments/settings-row.html, admin-ui.js):
getAdminSettingsRow() draws mode, motion, editor and language; each list is captioned by a label for its field
- no inline onchange and no aria-label carrying the current value; the theme row keeps the icon circle-half
A row marked data-sl-option applies data-theme or data-sl-still at once and posts the form with fetch silently
- a refused answer puts the row and the root attribute back to the value the server rendered
- Language and reference (lang/.php, admin/lang/.php, docs/*):
- _ON and _OFF in the six site locales, _ADM_MOTION in the six panel locales
- The answers of step 5 stand in docs/12-ADMIN-UI-2026.md; step 93 ticked in the roadmap
Benefits:
- No row of "Опции" but the language reloads the page; the theme and the motion change in place
- One op and one handler instead of three, each answering a status the page can act on
- The motion choice holds on every page without a flash, since the server writes it before the first paint
Technical notes:
- New cookie $conf['user_c'].'-still' with the value 1, one year, httponly like mode
- Breaking for scripts of the panel: admin.php?op=mode and op=changeeditor no longer exist; post op=option instead
Steps 91 and 92 of the roadmap, batches 3 and 4 of the panel plan: the menu of the etalon demo/admin with its filter and current section, the closed fan sl-dial as one stop of Tab in both themes, and one source of the queues of the panel that the bell, the block "Ждут внимания", the chips and the title of the tab read.
Core changes:
- Menu filter and current section (layouts/admin.html, admin/index.php, admin-ui.js, theme.css):
A find field sl-menu-find in the row sl-menu-top narrows the menu by name and module key; sl-menu-empty says so
- module screens only; the home gets its search in batch 6
- getAdminMenu() marks the open section with aria-current and carries the module key in data-sl-menu-item
- The open section takes the tint and the brand bar; the fan of a row and a card shows only on hover and focus
- Fan as one Tab stop (public/plugins/system/slaed.js, fragments/dial.html and share.html of both themes):
- The toggle carries aria-expanded; the items of a closed fan stay hidden until it opens
- Escape folds the fan and gives the focus back to its toggle; a fan folds once the focus leaves it
- Tab stops fell from 233 to 104 on the home and from 470 to 242 on "Комментарии"
- One source of the queues (core/system.php, core/admin.php, public/index.php):
getAdminQueues() answers key, mod, label, title, icon, href and num of every queue the administrator may open
- new users, comments for the super-administrator only, every Node type the administrator moderates
- the fullest first, counted once per request
- getAdminInfo() renders one block "Ждут внимания" from it in place of "Новые" and "Ждут проверки"
- getAdminCountRow() is gone; the op getAdminQueueData of go=5 answers the poll in JSON for every administrator
- Bell, chips and poll (fragments/bell.html, menu-list-item.html, menu-grid-item.html, admin-ui.js, theme.css):
- The bell stands in the header of every screen: the sum on its corner, the waiting rows in a tip sl-float
- The chip sl-queue sits on every section with a queue in the menu and on the cards; "Материалы" sums its types
A poll once a minute updates rows, order, chips and sum; a grown sum stirs the bell
- while the tab is not seen the title counts the arrivals as "(N)" and drops the count once it is
- Below 560 px the header items give up half their padding, so nine icons fit a phone
- Block tone by role (partials/block-sidebar.html, theme.css):
- sl-block-menu and sl-block-queue colour the heads of the column; the rules by sl-block-N are gone
- Language, tests and reference (admin/lang/.php, tests/Unit/, tools/, docs/):
- _ADM_FIND, _ADM_NOFIND, _ADM_BELL and _ADM_WAIT in the six panel locales
- CommentIsolationTest guards getTableCount() against the comment table; DeskKeysTest follows setDialState()
- The audit baseline is stored; docs/PERFORMANCE.md names the queue counts
- The answers of steps 3 and 4 stand in docs/12-ADMIN-UI-2026.md; steps 91 and 92 ticked in the roadmap
Benefits:
- One number for every place that shows a queue instead of two blocks built as HTML strings
- A keyboard walks the menu and the lists without passing every hidden fan item
- Block colours follow the role of a block, not its number or order
Technical notes:
- The comment queue is no longer shown to an administrator of modules, who cannot open that screen
- go=5 lets an administrator of modules reach the op getAdminQueueData only; every other op stays super-administrator
Steps 89 and 90 of the roadmap, batches 1 and 2 of the panel plan: the shared base in JS that the etalons need - one notice, one motion switch, the axis of the slide, the toggle storage and the palette controller - and the header of the panel moved from the etalon demo/admin into the theme.
Core changes:
- Notice with undo (public/plugins/system/slaed.js, fragments/toast.html of both themes):
setToast(text, opts) takes { warn, undo, time } and every call is moved onto it
- admin prints a flash alert with a countdown ring in sl-toast-host, lite a pill with sl-toast-undo
- _EDITOR_UNDO becomes the shared _UNDO in the six locales
- Motion switch (base.css of both themes, tools/ui-contract.php, tools/ui-audit.php):
--sl-time-scale multiplies every duration of the API block
- prefers-reduced-motion and :root[data-sl-still] set only --sl-time-scale: 0
- the component blocks of animation: none are gone; the audit no longer counts such root modes as scoped
- isMotionStill() reads the media and data-sl-still; setSlideMotion() gets its axis
- Shared helpers (slaed.js, editor.js, emoji.js, admin-ui.js):
- window.setPaletteOpen() is the one palette controller the editor calls
- window.getToggleState()/setToggleState() are open; the group fold and the recent emoji use them
- Header of the panel (layouts/admin.html, partials/dashboard-panel.html, fragments/link.html):
- The logo is a link sl-brand home to the panel without h1; the home carries one hidden h1
- The heads of the two home panels are buttons inside h2; a native button keeps its own keys in the toggle
- A skip link to #sl-main is the first stop of Tab; Options is an icon with its label
- Below 900 px the menu is a drawer behind a menu button: scrim, close button, Escape, the rest inert
- Tab title (core/system.php, core/helpers.php, admin/index.php):
- setFoot() names the section, an open tab other than the first, _ADMIN and the site, joined by defis
- getTplAdminTabs() records the section and the tab; setHead() keeps them when the tabs came first
- Theme, language and reference (theme.css, base.css, admin/lang/.php, docs/):
- --sl-layout-sidebar replaces the two literal sidebar widths; sl-visually-hidden is one rule in both themes
- _ADM_MENU and _ADM_SKIP in the six panel locales
- The answers of steps 1 and 2 stand in docs/12-ADMIN-UI-2026.md; steps 89 and 90 ticked in the roadmap
Benefits:
- One mechanism for notices, motion, storage and the palette instead of one per consumer
- One h1 per screen, a named browser tab and a usable panel on a phone
- The later batches of the plan build on the shared base instead of copying the etalon
Technical notes:
- No schema, route or API change; the ui-audit baseline is stored again
- Remembered emoji and group folds are reset once, their storage keys changed
- The tab title order changes from "Site - Administrator" to "Section - Administrator - Site"
Roadmap step 88 is batch 0 of docs/12-ADMIN-UI-2026.md: the owner answered the nineteen open questions, the panel lists are inventoried for the list canon and the baseline is taken. Taking it showed the label crawl had walked the site blind since the readable addresses of 2026-10-14; it reads paths again, the five unnamed hints it reported are tied to their controls, and its baseline is rebuilt green.
Core changes:
- Plan of the panel (docs/12-ADMIN-UI-2026.md):
"Ответы шага 0": the nineteen answers under their numbers
- the window "Наблюдение" shows only the present of _session, no graph, no history, no cache
- the session limit of the administrator is done now on the PHP session, a key in config/security.php
- every site sound with an event ships, the private messages by a poll; no zone for mentions
- undo by a deferred send, "all N" in portions from the browser, the prefix ADM, motion by tokens
"Инвентарь списков": 35 list screens with their op, kind of delete, mass actions, pager and filter; group A of 16 for batch 12, group B of 17 for batch 13; the file manager stays outside the canon; the defects found on the way
"Базовая линия": ui-audit counts, the label crawl, 264 shots, axe on the home, "Комментарии" and "Конфигурации", the response times under a session (home 338 ms, the measure of batch 14)
- Batches 7, 12, 13, 17, 21 and 23 follow the answers; the status line names batch 1 next
- Label crawl (tools/label-audit.mjs, tests/Support/address_probe.php, tools/label-audit-baseline.json):
getRouteSig() read the route only from name= and op=, so every readable path read as the home page and the site side walked 15 routes
The probe answers every new path by Route::isRouteReserved() (an operation, still under the allowlist of ops) or Route::getRoute() (a record, view#rec; a category or forum section is the list of its prefix); a path that is neither is skipped
The baseline is rebuilt from a clean run: 442 routes, no violation; the routes of the deleted types jokes and media left it, account:view became users:view#rec
Hints tied to their controls (admin/modules/update.php, public/templates/admin/partials/group-form.html, public/templates/admin/fragments/code-block.html, public/templates/lite/fragments/code-block.html):
The code blocks of the site id and the rescue code, the radio groups of the update settings and the colour of the group form carry a hint id and an aria-describedby
- code-block takes an optional describedby in both themes
- Shared login and axe (tools/ui-probe.mjs, tools/ui-shots.mjs, tools/browser-audit.mjs, package.json):
- setSession() lives once in ui-probe.mjs; the screenshot rig, the label crawl and the browser audit call it
- browser-audit.mjs gains --as admin|site and --axe (axe-core 4.14.0, WCAG 2.2 AA, a devDependency)
- Screenshot manifest (tools/ui-shots.json):
- node-view opens a live docs material instead of one that answers 404; node-media of the deleted type media leaves
- Stand (public/demo/admin/index.html, public/demo/assets/demo.js):
- The mark Э30 and its line of the legend leave: the live panel already gives the tab
Roadmap, ideas and reference (docs/ROADMAP-2026.md, docs/2027-IDEAS.md, docs/2027-CORE.md, docs/TESTS.md, docs/TEMPLATES.md):
- Step 88 closed; ideas 9 (history of online visitors) and 10 (mentions and replies to me with their sound)
- 2027-CORE question 2 links the session limit of the panel plan
- TESTS.md: the flags --as and --axe; TEMPLATES.md: how the crawl reads readable paths
Benefits:
- The label gate covers the lite theme again before the panel plan puts the menu and the block on the site
- Every later batch is measured against one recorded baseline
- One login for every browser tool
Technical notes:
- The probe reads the stand database and writes nothing
The full phpunit run has three failures that fail on the clean HEAD as well: AssetVersionTest (presentation.css), RouteRegistryTest::twoWritersRaceForOnePath and PhpFileFormatTest (tools/ui-contract.php)
- No schema, URL or API changes