Официальный сайт SLAED CMS
Журнал изменений
The panel and the site told facts in four ways - alerts, sentences glued to a value, heading rows, windows round a single list. A fact is now a row of kv-list (panel) or field-value and the profile cards (site), its state a chip beside the value and the reason of a bad state a warning under the list; a window of a board no longer wraps a list or a page, and a post button carries the weight of its step.
Core changes:
- Facts of a page (kv-list.html, admin/modules/update.php, config.php, blocks.php):
- kv-list takes an optional chip with a tone (success, warn, danger, neutral) after the value
- On a page the list lines up with the rows of the form: two halves, the same padding and solid rule
- Inside a board window it keeps its narrow form: value to the right, dashed rule, small text
- The updater lists its facts with chips: check refused, no unpacker, rollback, self-check of the last run,
staged package, refused archive, integrity; each sentence of a cause is a warning under the list
- getUpdateWarn() escapes the words of the master or the archive once
- The space a test needs is a meter, not an alert
- Optimization lists cache folders by full path without a heading row
- A block form drops the info alert with the absolute path; the block edit page names the block in a row with the chip of its kind
- New and reworded UPDATE* constants in the six admin locales
- Facts on the site (modules/account/index.php, account-profile.html, lite theme.css):
- The extra fields of the public profile are a fifth card among Profile and Personal; a text area stands under the cards as field-value rows in a wide panel, so its paragraphs stay whole
- field-value draws a rule between two rows, a muted caption and a weighed short value; paragraphs of a text area keep the weight of text (both themes, the reason of the layout difference written into tools/ui-contract.php)
- Panel window and button weight (admin-list.html, helpers.php, post-button.html, database, newsletter, search, sitemap, monitor):
- getTplAdminList() drops its pane option: a list draws no window, its tiles stand above the table
- Database, newsletter, search and sitemap show their tiles without a window; sitemap names its address in a row
- Database counts its tables in a tile; _TOTALSPACE and _TOTALFREE give way to _DB_TOTAL and _DBFREE
- getTplPostButton() takes the look main, check or risk, which the updater uses; the setup, the layout field and the icon picker draw their buttons by the same weight
- Monitor tiles and the demo use outline icons; tile icon width of the panel is 32px
- One empty face (empty.html, file-browser-props.html, file-manager.html, privat-view.html, core/admin.php, user.php):
- An empty file manager, empty file properties and the blank private message pane use fragments/empty.html with the new is_pane variant; the file manager reset button carries its icon
- _UPLOADS_EMPTY* and _UPLOADS_FAIL* give way to the shared EDITOR* words
- The pager hides its "Page" caption visually and keeps it for a screen reader
- Documentation and tests (docs/TEMPLATES.md, admin/info, tests):
- docs/TEMPLATES.md: sections Panel Window, Button Weight and Facts Of A Page
- Help of update, config, blocks, database, newsletter, privat, search and sitemap describes the current page
- SeriesPartsTest checks the chip and its escaping; PanelPartsTest checks that a list draws no window
Benefits:
- One way to tell a fact, a state and its cause everywhere
- Fewer alerts: an alert is a message or the permanent note of a page again
- A list or a page reads as one table with its form
Technical notes:
- getTplAdminList() no longer reads 'pane'; getUpdatePack() takes a third argument $look
- The account profile part drops has_field/field and takes field_html
- No schema or URL change
Nothing to show was told four ways: a grey line in the lists of the panel, a blue alert, a yellow warning on the search of the site, and blocks of their own in the files, the mailbox, the profile feed and the favorites. One fragment now draws it in both themes, in three kinds, while an alert is left to messages: saved, refused, failed.
Core changes:
- The fragment (public/templates/*/fragments/empty.html, assets/css/base.css, assets/css/theme.css):
- One identical fragment of both themes: an icon, a title, an optional line and an optional button
- is_found: "Ничего не найдено" with "Сбросить фильтр"; is_calm: "Всё проверено: очередь пуста." in a calm green
- Without either: "Нет информации", with "Добавить" where the caller passes a form of a new entry
- is_small for a block, a column or a tab; act_target swaps a part with htmx instead of a link
- sl-empty replaces sl-fm-empty and sl-profile-empty; the new token --sl-empty-width, the component empty in tools/ui-contract.php, whose two allowlist entries now name the new selectors
- The window of files keeps --sl-fm-empty-width for the line under its title; sl-list-calm is gone
- The lists of the panel (core/helpers.php, partials/admin-list.html and their callers):
- getTplAdminList() draws the fragment from none_text, is_calm, reset_url and add_url
- A caller no longer picks the words: it passes the address of the list without its filter when one is set
- A type or a tab of a status is a place, not a condition: the trash of a Node type is empty, not "nothing found"
- "Добавить" on an empty list of Node (draft and published only), polls, campaigns, groups and categories
- The single alerts of config, groups, logs, master, search and the categories are the fragment
- The file browser of the panel draws its empty catalogue and its failed list with the same face
- The site (modules/node, users, voting, sitemap, forum, search, changelog, account, core/user.php, core/system.php):
- The lists of Node, users, polls, forum topics and the sitemap; the search without a hit offers the search page
- A Node list empty under a letter, "new" or favorites offers the reset, in the archive of events as well; an empty type offers its form of a new entry to whom checkNodeFlow() allows it
- The comments of a material, a poll that is gone from its block, the profile feed: the small kind
- The mailbox, the favorites, "Мои материалы" and the logins of the cabinet: the fragment in place of the list, no longer an info note in the slot of the cabinet
- The changelog says the fragment when no commit came and keeps the alert for an error of loading
- Documents, help and tests (docs/TEMPLATES.md, admin/info, modules/*/admin/info, tests):
- docs/TEMPLATES.md, section Empty State: the keys, the three kinds, where a button stands, what is no empty state
- The help of newsletter, privat, voting, changelog and account names the reset and the add button
- AdminListRowTest holds reset_url on every list under a filter bar and is_calm on the queues; FileManagerCatalogTest and the profile feed of contract_probe.php read the new keys
Benefits:
- One look and one wording for nothing to show, so an empty place no longer reads as a message or a warning
- An empty result always offers the way back; an empty section offers the next step where the reader may take it
- One rule in place of a ternary of texts in thirty callers
Technical notes:
- Removed constants, all six locales: _UPLOADS_NOFIND, _NOMATCHES, _PRNOHIT, _FAVOR_NONE
- The partial keys empty_alert, empty_title, empty_icon, empty_text, none_text (favorites) and is_empty (file list) give way to one array for the fragment
- The value placeholder "Нет информации" in a cell or a card, the live filter of a picker and the narrow panes of the layout screen stay as they were
Every module counted its pages its own way: six sizes of a page (10 to 100) and four windows of numbers (5, 8, 10 or every page), settings with seven names, numbers written into code and two settings nothing read. The standard is now one section of the general config that every list inherits, as "Use Global" of other systems does; a module of the site may keep its own number where its place needs one, the panel has one number for all its lists.
Core changes:
- The general numbers (config/global.php, admin/modules/config.php, core/helpers.php):
- pager: rows 15 and span 8 for the site, arows 50 and aspan 10 for the panel, four fields of the general settings screen
- getPageRows($own, $key) answers the own rows of a module, else the general one, capped by the own number of a visitor from the cabinet (getUserNews(), 3-20)
- getPageSpan($own) answers aspan in the panel, the own span of a module or the general span on the site
- getListLimit(int|Closure $total) takes the general arows; getTplAdminList() takes arows and aspan itself and loses the keys base and maxpg; getTplPager() and getPageNumbers() take the general numbers when none is passed
- The lists of the panel (admin/modules/.php, modules//admin/index.php, core/classes/comment.php, privat.php, mail.php):
- comments, privat, search, voting, users, favorites, changelog, groups, logs, ratings, master, newsletter and the Node lists take the general rows and window; the own numbers 50, 25, 20 and 10 and the windows 8 and 10 leave
- The pager of the language editor shows the general window in place of every page
- The lists of the site (core/user.php, modules/*/index.php, modules/changelog/common.php):
- comments, privat, search, voting, forum and changelog read getPageRows() and getPageSpan() of their section
- The members list, the logins and "My materials" of the cabinet take the general numbers in place of 50 and 20
- The Node list caps the list.limit of its type by the own number of the visitor, which the hint of that setting always promised; the window is the general one in place of 8
- The topics of the forum keep their own listnum (50), the private messages their own span (5)
- The settings of the modules (core/admin.php, the config forms of six modules, config/*.php):
- getTplPagerFields() draws the two fields rows and span, empty for the general number shown as its placeholder; getPagerPost() reads them; both replace the same fields of six forms
- anum, anump, num, nump, snum, snump, pnum and perpage leave the configs and the forms; favorites.num and favorites.nump, read by nothing, leave with them
- Texts, help and documents (admin/lang, modules/account/lang, modules/changelog/admin/lang, admin/info, modules/*/admin/info, docs/):
- _C_33 to _C_36 name the four numbers plainly, _PAGER_OWN says that an empty field takes the general number, _ADM_ROWSDEF and _ADM_ROWSNOTE name the general default, _ACCOUNT_STORYNOTE names every list of the site; _SEARCHNUM and _CHLOG_PER_PAGE leave; six locales
- The help of config, panel, comments, privat, account, changelog, search, forum and voting describes the fields
- docs/TEMPLATES.md (Admin List Contract, Rows per page, Pager), POINTS.md, RATINGS.md, VERSIONS.md
- Tests (tests/Support/*.php, tests/Unit/AdminListRowTest.php, PointOwnersTest.php):
- The probes of rows, comments, privat, search, account and changelog post and read the new keys
- The list canon test defines the texts of the rows chip, which every paged list now draws
Benefits:
- One place decides the density of all lists; a module states only what differs
- The own number of a visitor works on every list of the site, not on the forum alone
- 434 lines leave, 271 come
Technical notes:
- Breaking: the config keys above are removed; a site updated by the merge of config/ gets pager, rows and span and keeps the old keys unread
- config/local.php must be deleted after the files arrive, or the site runs on the cached old keys and the defaults in code
- A Node type keeps list.limit as before; no URL, schema or API contract changes
A pass over the frame cost of /presentation: the half of a tall section below the window no longer runs, the dashes of the block scene no longer raster the whole scene every frame, and the pulse line stops redrawing once it reached its aim. The picture stays within the pixel tolerance of the rig on every touched part.
Core changes:
- The off-screen pause by part (public/plugins/presentation/presentation.js):
- setOffScreen() watches each scene and block (.sl-pres-scene, a child of .sl-pres-body or of the hero) beside the
sections, so data-sl-pres-off marks the part that left the window
- an SVG pauses its SMIL while any part round it is off, under still motion as before
- an arc in a part still off waits for that part and fills from zero when it first comes into view
- The development screen ran the PDO packet of the section above it on left, a refused animation; it now stands
- The block wires on a layer (public/templates/lite/assets/css/presentation.css):
- .sl-pres-blocks-stage > .sl-pres-wires takes will-change: transform: the dashes running by stroke-dashoffset repaint on their own layer instead of the scene under them
- The map keeps its wires in the scene: its core stands on a half pixel and rastered 90 of 255 apart on a layer (owner, 2026-11-07)
- The pulse strip (public/plugins/presentation/presentation.js):
- setPulse() sleeps by a timer until the next step once the largest move of a frame is under 0.005 of the scale, about 0.03 px; at 60 Hz the line never reaches it and draws every frame as before
- The rig and the documents (tools/frame-cost.mjs, docs/PERFORMANCE.md, docs/TEMPLATES.md):
- States for every section of the presentation (pres-workbench, pres-runtime, pres-dev, pres-live, pres-dna, pres-archive, pres-showcase, pres-voices, pres-pulse) and the part blocks for the pixels
- The pair of the pass, the forms measured and not taken, the memory of the new layer and the share of the cockpit lamp in PERFORMANCE.md; the laws of the part pause and of the running dash in TEMPLATES.md
Benefits:
- System screen: GPU 119 -> 61 %, renderer 50 -> 48 % (medians of 5 rounds against HEAD)
- Development screen: renderer 51 -> 35 %, layout 17 -> 11 ms/s
- Guard screen: renderer 82 -> 78 %, style 69 -> 61 ms/s; top: script 13 -> 10 ms/s
Technical notes:
- Pixels within 16 of 255 in both schemes on hero, system, blocks, guard, runtime, development; pipeline differs by 20 on 2 pixels in the light scheme, under its own noise of 27
- The new layer costs 14 layers and about 9 MB of compositor resources; the raster scratch of Skia falls from about 170 to 130 MB; heap, nodes and listeners stand still over a minute
- No markup, URL or template key changed
Three things the Node types still lacked before 8.0: a dated listing with an archive, ads that do not stay forever, and two profiles built on them. The extension event keeps a start, an end and a place per material in a table of its own and orders the list of its type; workflow.lifetime ends an author's publication after a set number of days and the cabinet lets the author renew it.
Core changes:
- The extension event (core/classes/node/ext/event.php, core/classes/node/ext/load.php, core/classes/node/extension.php, core/classes/node/ext/support.php, core/classes/node/ext/sync.php, core/classes/node/query.php):
- NodeEvent stores the start, an optional end and the place of a material in the new table _node_event
- the list of its type shows the upcoming events, the nearest first; an event stays upcoming until its end, or without one until the end of the day it starts
- the argument archive shows the past ones, the newest first, in place of the sort and the direction
- The interface NodeExtension takes ten methods: getNodeScope() receives the list arguments, getNodeOrder() is new
- NodeQuery::setNodeArgs() hands the arguments of the list to the extension of the single selected type; getExtOrder() and the column check RANK keep the order of the extension trusted
- support and sync return no order and keep the order of their type
- The display mode event is in NodeQuery::MODES and belongs to the type of that extension alone (EXTMODES)
- The lifetime of a publication (core/classes/node/service.php, core/classes/node/query.php, config/node.php, modules/node/admin/index.php, modules/node/admin/lang/*.php, modules/node/admin/info/ru.md):
- The key workflow.lifetime, days from 0 to NodeQuery::LIFEMAX (3650), checked with the workflow section; 0 keeps the old behaviour and is the default of config/node.php
- NodeService sets the end of a material one lifetime after its publication when an author who is no moderator
publishes it directly, or when a moderator approves it from «На проверке», each only while no end is set
- a moderator's own material keeps the end the moderator gives
- an edit of a type without scheduling keeps the stored end
- NodeService::updateNodeTerm() renews a published material of the signed-in author from the clock of the database, a repeat sets the same end, never a sum
- The form of a type has the field «Срок показа, дней»; the help describes it
- The cabinet screen «Мои материалы» (modules/account/index.php, modules/account/lang/*.php, core/user.php, core/system.php, core/classes/route.php):
- materials() lists the author's published materials of active types with a lifetime, shown and expired, with their end, through NodeQuery::getNodeTermList()
- renew() is a POST with the token through checkSitePost('account') and calls updateNodeTerm()
- getUserNavItems() adds the item while an active type has a lifetime; getIconName() knows renew and terms; Route::OPS of account carries materials and renew
- The forms, the public module and the lite theme (modules/node/index.php, modules/node/admin/index.php, modules/node/lang/*.php,public/templates/lite/partials/node/head.html, public/templates/lite/partials/node/event/view.html, public/templates/lite/fragments/node/event/card.html, public/templates/lite/assets/css/theme.css):
- The list of an event type reads archive, keeps it in the pager, the canonical address and the sort links, and the header shows the switch «Архив»
- The forms of the panel and the site show the start, the end and the place; a refused form of the panel names a changed event field among the differences
- The mode event draws the start as a calendar leaf on the card and the page
- The profiles (modules/node/profiles/.json, lang/.php, tools/node-profile.php):
- events: the extension event with a cover and a gallery of up to 20 images, categories, comments, favorites, the home page, pinning and moderated submissions
- jobs: salary, city, employment (full-time, part-time, remote), e-mail and address, a cover for the logo and the switches of board
- board and jobs take workflow.lifetime 30; every other profile carries lifetime 0; seven constants in six languages
- The load profile fills _node_event for event types
- The schema (storage/update/sql/table.sql, storage/update/sql/table_update6_3.sql):
- CREATE TABLE _node_event (nid, starts, ends, place) with the key starts, a cascade from _nodes and the check that an end lies after the start; the update creates it and converts its collation
- Tests and documentation (tests/Unit/NodeEventTest.php, tests/Unit/NodeTest.php, tests/Unit/RequestWatchTest.php, tests/Support/_probe.php, docs/NODE.md, docs/VERSIONS.md):
- NodeEventTest checks the extension, the writes and reads through NodeService and NodeQuery, the routes and the statement budget of the list
- NodeServiceTest checks the publication, the approval, the moderator's own material and the renewal; NodeProfileTest and the installation probe check the sixteen profiles through a real installation, an event written through the panel included
- docs/NODE.md describes the extension event, the lifetime, «Author's materials» and the import of an older file
Benefits:
- A calendar of events and classifieds that expire come with the installation, without a module of their own
- The order of a list stays in the query: an extension gives one trusted column, never a sort key of the visitor
Technical notes:
- New table _node_event: a stand that has the code runs its CREATE TABLE from table.sql with its prefix, and rebuilds config/local.php for the new key of config/node.php
- A profile file without workflow.lifetime imports with 0 and stays format version 1
- Breaking: the interface NodeExtension changes getNodeScope() and gains getNodeOrder(); the factory is closed, so only the extensions of the core implement it
The run of the panel and the site in a real browser found small classes, sizes and lists that each had a look of their own. This change moves them onto the shared parts of the two themes: one height for a field, one copy button for code, one file door, one list canon, one segmented switch and one drawer. The type form of Node becomes tabs of one form, and config/points.php ships in the shape the panel writes.
Core changes:
- The changelog in the panel (modules/changelog/admin/index.php, modules/changelog/common.php, modules/changelog/index.php, modules/changelog/admin/lang/*.php, modules/changelog/admin/info/ru.md):
- changelog() draws a table of getTplAdminList(), one row per commit: hash, title, author, changes and files (with
statistics on a local Git), and date as a moment
- the "i" of a row holds the body through $prs->filterDoc(), the e-mail and the per-file +/- lines; the search word is marked by filterTextHighlight() in the title and the body
- the filter is fragments/list-filter.html in the tab head (word, author, file except for GitHub, date from/to); the pager reads num and the per-page choice through getListLimit(), with perpage as the base
- an error of loading or an empty result goes to none_text (_NONE_FOUND under a filter, else _NO_INFO)
- rendpage() is gone; the panel no longer groups by date and no longer makes the canonical redirect
- chlogCanonicalRedirect() drops its $base argument and serves only the site
- The help describes the table, its columns, the filter, the pager and "Группировать по дате" acting on the site alone
- The forums in the panel (modules/forum/admin/index.php, modules/forum/admin/info/ru.md):
- forum() builds its list with getTplAdminList() instead of a bare table in a box: the description sits in the "i" of the row, the status is a lamp, every column sorts, and the view switch and key tip of the canon stand above it
- Node in the panel (modules/node/admin/index.php, modules/node/admin/lang/*.php, modules/node/admin/info/ru.md):
- add() opens the form of the first type the user moderates right away; a select "Тип" (list-filter, is_now) in the tab head switches the type, which replaces the page of links that listed the types
- getNodeTypeRows() becomes getNodeTypeTabs(): the type form is six runtime sub-tabs of one form (_GENPREF, _LIST,
_NODE_FEATURES, _NODE_FLOW, _NODE_ROLES, _NODE_INTEG), and the form carries is_valid
- each on/off setting (alphabet, every feature and integration, guestok, report, canlink, active of a role) is a Yes/No radio group of getTplRadioGroup() with its own caption, in place of a checkbox labelled "Да"
- the sort direction has its own row (_NODE_ORDDIR new); minimum and maximum of a role are two labelled rows
- each resource role is a partials/toggle-form-block.html block with its key in the cap, in place of the repeat fragment; _NODE_ROLEOPTS is gone
- The profile of a new type is a select "Профиль" in the tab head with an empty choice, in place of a row of links
- import() takes the file through getFormFileField()
- The help describes the "Добавить" type selector, the tabs of the type form, the profile selector and the role blocks
- The file door of an ordinary form (core/helpers.php, public/templates/{admin,lite}/fragments/file-field.html, public/plugins/system/slaed.js, admin/modules/update.php, lang/*.php):
- getFormFileField() prints fragments/file-field.html with is_form: a button names the action by _FILEPICK, a chip
shows the name and size of the picked file, a cross clears it
- the form door carries data-sl-file-form and no data-editor, prints no path or address carrier, and keeps a required file field out of sight (sl-visually-hidden, tabindex -1), so the browser refusal points at the door
- setFileDoors() and setFileDoor() of slaed.js open the browser picker, show the name and size, clear the field and return the focus to the button
- "Загрузить архив" of update is now a pane of its own (fragment pane, icon cloud-upload) and takes the door
- getTplUserSearchInput() no longer marks its input as required
- The drawer menu of the site on a phone (public/plugins/system/slaed.js, public/templates/admin/assets/js/admin-ui.js, public/templates/lite/partials/site-header.html, public/templates/lite/assets/css/theme.css, lang/*.php):
- setDrawer() moves from admin-ui.js into slaed.js and finds its menu by the aria-controls of the toggle, so one
drawer serves #sl-menu of the panel and #hmenu of lite
- the page goes inert while the drawer is open; Esc, the scrim, the cross or a window opened from the menu close it, and it closes once the toggle is hidden; the focus returns to the toggle
- The header of lite gets the toggle sl-menu-btn with _MENU, the scrim sl-menu-scrim, and #hmenu as a nav with the cross sl-menu-close; below 900 px the menu and the search form one column of the drawer, above it the bar is unchanged
- Sizes and small classes of both themes (public/templates/{admin,lite}/assets/css/{base,theme}.css, public/templates/{admin,lite}/fragments/code-block.html, public/templates/admin/partials/{layout-screen,statistic-cockpit,toggle-form-block}.html, admin/modules/statistic.php, admin/info/fields/ru.md):
- base.css of admin gives every one-line input and single select the height --sl-size-control
- The copy button of a code head and of the link window of the builder is .sl-code-copy in both themes, a quiet square of --sl-size-chip, larger on a touch screen, in place of the borrowed .sl-modal-act
- "Сейчас" and "Вчера" of the statistic cockpit are a plain sl-seg with its knob (rail stat-day); the server sets the active cell from is_now and admin-ui.js toggles sl-is-active with aria-pressed; .sl-cockpit-set is gone
- partials/toggle-form-block.html puts the name with the + toggle in a cap and its rows under it at the full width of the box; the side panel of the builder keeps them stacked; the help of the additional fields follows
- A list filter whose only fields are is_now selects shows its OK button only without scripts (@media (scripting: enabled))
- The chips of the private messages take --sl-size-chip, their bulk bar field --sl-size-control
- The changelog CSS of the admin theme is removed
- Points config (config/points.php, docs/POINTS.md):
- config/points.php ships in the shape setConfigFile() writes: actions before active, one key per line, so an unchanged save of the panel leaves the file as it is
- Tests (tests/Support/config_probe.php, tests/Support/route_probe.php, tests/Unit/{ConfigFileTest,EditorWindowTest,PointTest,AdminSecretTest,ListPickTest}.php):
- getProbeShipped() finds every setConfigFile('<name>.php') of admin, modules and core, saves each shipped file unchanged and reports any file that moves, ignoring the header year; anUnchangedSaveKeepsEveryShippedFile checks it
- anOrdinaryFormSendsItsFileThroughTheSameDoor checks the form door in both themes, getFormFileField(), _FILEPICK in six locales, update and Node import on the builder, and setFileDoors() started by slaed.js
- PointTest expects the key order actions, active
- route_probe and aDelegatedAdministratorWritesNoSecret: a changelog post of a delegated administrator that names the GitHub fields is refused whole, and the same form without them saves perpage and keeps the token and the source
- ListPickTest: a comment names the clear form by its op
- UI tools (tools/ui-contract.php, tools/ui-shots.json, tools/ui-contrast.json, tools/ui-audit-baseline.json):
- ui-contract drops pmf-filter, the exceptions of the admin changelog classes and of fragments/changelog-commit.html, and adds the theme differences of .sl-menu-btn, .sl-menu-btn .bi and the 900 px .sl-menu-close
- ui-shots captures drawer-open on the front page (optional step)
- The contrast list and the audit baseline are regenerated
- Docs (docs/TEMPLATES.md, docs/WINDOW.md, docs/UPDATE.md, docs/VERSIONS.md):
- TEMPLATES: the toggle form block, the is_now select of a list filter, the drawer of a phone, the screen switch as the plain sl-seg
- WINDOW: .sl-modal-act only for window actions and .sl-code-copy for code; the form door of getFormFileField()
- UPDATE: the changelog*.html templates and the changelog CSS stay only in lite
- VERSIONS: the sections of 2026-11-05 and 2026-11-06, including this change under "The look is one system"
Benefits:
- One field height, one copy button, one file door, one list canon and one drawer across both themes, so a fix lands in one place
- The changelog and the forums of the panel sort, switch views and page like every other list
- A shipped config the panel writes no longer changes on its first unchanged save
Technical notes:
- No database change
- Removed templates: admin partials/changelog.html, partials/changelog-body.html and fragments/changelog-commit.html, changelog-date-header.html, changelog-stats.html
- Breaking for a custom theme: the tokens --sl-pmf-filter-height (lite) and --sl-changelog-width, --sl-changelog-body-border, --sl-changelog-files-height, --sl-changelog-stat-width (admin) and the class .sl-cockpit-set are gone; the admin theme has no .sl-changelog-* rules
- The admin changelog list pages by num instead of page, and its filter no longer redirects to a canonical address
- New constants: _FILEPICK and _MENU in lang/.php, _NODE_ORDDIR in modules/node/admin/lang/.php
- Removed constants: _NODE_ROLEOPTS; _CHLOG_FILTER, _CHLOG_SEARCH_PH, _CHLOG_AUTHOR_PH, _CHLOG_FILE_PH, _CHLOG_FILTER_BTN, _CHLOG_RESET_BTN, _CHLOG_TOTAL, _CHLOG_FILTERED, _CHLOG_PAGE, _CHLOG_COMMITS, _CHLOG_COMMITS_REPO, _CHLOG_ERR_NO_COMMITS of the changelog admin
- The on/off settings of the Node type form now post 1 or 0 from a radio group under the same names
- getNodeTypeRows() is replaced by getNodeTypeTabs(); setDrawer() leaves admin-ui.js for slaed.js
- config/points.php changes only its layout and key order; its values are the same
php tools/node-profile.php stopped at "Invalid node input: name" before measuring anything, and two of its limits had fallen behind the code. The full run of 100000 materials in fourteen types now passes without a failure.
Core changes:
- The scratch configuration (tools/node-profile.php):
- setProfileConfig() drops the types the committed config/node.php names, with their fields, upload rules and rating
rules, as deleteSetupTypes() of setup.php drops them before the profiles are imported
- the committed config names the types of the stand, so every profile met its name taken in checkNewName()
- The budgets (tools/node-profile.php, docs/NODE.md, tests/Unit/NodeServiceTest.php):
- A physical delete costs six Node statements: getNodeHead() reads the head with the name of the type and then the type, before the locks of type, material and resources and the DELETE; PBUDGET 'delete' and the table of docs/NODE.md say 6, as the description of the delete there already listed
- A row of a list page passes six reads, not four: the two paths of NodeQuery::getPathSql() joined the page on 2026-10-14, after the bound was written; getProfileBound() and the formula in docs/NODE.md take 6 x page size
- The comment of NodeServiceTest names six Node statements of a delete
Benefits:
- The load profile of Node can be run again before the release
- Its limits fail on a real regression, not on the code that was accepted
Technical notes:
- No change of the core, the database or a URL; the tool runs by hand only
- Result: list docs 488 of 550 rows, list media 1100 of 1178, delete news 6 of 6 statements, no budget overrun and no full scan
An audit of the shipped types against the modules sites use most found four types a profile alone can make. A clean installation now creates and activates fourteen types; the events, vacancies and a lifetime of a classified ad need the core and wait in their own plan.
Core changes:
- The profiles (modules/node/profiles/album.json, portfolio.json, board.json, reviews.json):
- album: view default with tiles and cards, a cover and a gallery of up to 100 images, every feature of pages
- portfolio: view article, a cover and a gallery of up to 30 images, the fields client, year, site and services; no submit, no comments, no rating
- board: view default, a cover and a gallery of up to 10 images, the fields price (decimal, scale 2), city, phone and e-mail; submit by users with moderation and schedule, so a moderator may set an end of the ad
- reviews: view default, the required field stars (int 1-5), workflow access all, so a guest may post, with moderation
- the field is stars, not score: score is a column of _nodes and no field may take its name
- Sorts 110 to 140 after the ten profiles; uploads and rating empty, as every shipped profile
- Language (lang/*.php):
- _BOARD, _PORTFOLIO, _REVIEWS as titles of the types; _NODE_CITY, _NODE_CLIENT, _NODE_PHONE, _NODE_PRICE, _NODE_SCORE, _NODE_SERVICES as labels of the fields, in six languages; the gallery takes the existing _ALBUM
- Tests (tests/Unit/NodeProfileTest.php, tests/Support/install_probe.php):
- NodeProfileTest checks fourteen profiles: names, the order of sort, settings, roles, fields, the guest access of reviews; the clean installation, the exports, the panel offer and the write and view of a material of every type
- The probe keeps IPROFS as the ten profiles that stand for modules of 6.2, which the update rehearsal reads, and adds ISHIP, every shipped profile, for the installation checks; it writes the fields of the new types
- Docs and help (docs/NODE.md, docs/ARCHITECTURE.md, modules/node/admin/info/ru.md, tools/node-profile.php):
- NODE.md lists the four in "Replaced modules" and in the tables of "Shipped profiles" with their fields
- The help of Node names fourteen types; the comments of the load profile no longer count ten types
Benefits:
- A new site has a gallery, a portfolio, classifieds and reviews without building a type
- The four take one click in "Новый тип" on an existing site
Technical notes:
- No database or URL change; a clean installation creates four more types and their routes
- The board shows the phone and the e-mail of an ad publicly, as a classified ad needs them
The modules news, faq, files and the others are Node types since 8.0, and the panel showed only the card "Материалы": a newcomer did not find "Новости". Every type an administrator moderates is now a section of the panel, shown apart from the modules, since a type is no module.
Core changes:
- The sections of the panel (core/system.php, core/admin.php):
- getAdminSectionList() adds the Node types after the site modules: keyed by the name, opening name=node&type=<name>,
the icon of getIconName(), the type itself under 'node'
- a super administrator sees every type, a module administrator the types of their rights node-<name>, as the list of materials does
- getModuleLabel() answers the title of a type, so a pin and "Все разделы" no longer show the raw key
- getAdminQueues() gives a queue of a type the type as its module, so its count stands on the card of the type
- getAdminSectionData() answers the types as a third group "Типы материалов", always last
- The home and the menu of the panel (admin/index.php):
- getAdminMenu() takes the row of the section; a type opens its list, carries its key as subtitle and the dial of the
type: edit, categories when on, site when active, help of Node, pin
- a type is turned on and off in the types of Node only, the card carries no switch
- getAdminPanel() draws the panel "Типы материалов сайта, созданные в модуле «Материалы»" after the site modules
- getAdminPanelBlocks() draws the block "Типы материалов" after "Модули" and marks the open type, not Node, as current
- setAdminOption() takes an order of the menu as long as the modules and the types together
- Language and help (admin/lang/*.php, admin/info/panel/ru.md, docs/ARCHITECTURE.md):
- _ADM_TYPES (menu, all sections) and _ADM_TYPESPANEL (home) in six languages
- The help of the panel describes the block, its queue and its dial; ARCHITECTURE names the types in the one list of the sections
- Tests (tests/Unit/AdminGuardTest.php):
- The guard of the menu order checks the limit of modules plus types
Benefits:
- A newcomer finds "Новости", "Каталог файлов" and their own types on the home of the panel
- The home, the menu, the pins, Ctrl+K and the site menu read one list and agree
- The queue of a type stands on the type that waits
Technical notes:
- No database, URL or configuration change
- The card "Материалы" keeps no queue chip; the queues of the types stand on their cards
The form of a forum post without a category answered 500: add() read no row of _categories and handed null to is_acess(). A category that is not there, or one of another module, now counts as none in the form and in the three writers of the forum, as the list of forum() already does.
Core changes:
- The forum (modules/forum/index.php):
- add() answers setError(404) for no category, a missing one, a missing post and a post whose category is gone or belongs to another module, as view() does for a missing topic
- send(), move() and delete() take such a category as none and go back to the forum writing nothing
- a category of another module took the form of a new topic (rights 0|0) and its post, move and delete ended in an SQL error of IN (), as catids() names no category of another module
- The four reads of a category ask modul = :mod
- Tests (tests/Support/route_probe.php, tests/Unit/FileAccessTest.php):
- The run files of route_probe.php asks the form with no category, a missing category, post and topic, a post whose category is gone and one of a category of news, and posts send, move and delete naming them
- aForumOfNoCategoryAnswersNotFound: six forms 404 and the writers 303 with the rows unchanged; red on the old code with nine answers 500, two forms 200 and three SQL errors
Benefits:
- No 500 and no SQL error from an address of the forum a visitor can type or a stale form can post
- The forum reads only its own categories, in the list and in every handler alike
Technical notes:
- No database, URL or language change
- A forum address with a category of another module now answers 404 instead of a form