Официальный сайт SLAED CMS
Журнал изменений
Roadmap steps 52 and 53, RANKS batches 2 and 3: the groups screen wears the face of the stand grp-10 - the point groups as a ladder by threshold and the special groups by members, a form that composes a rank from presets, icons, counts and colours beside a preview and two contrast tiles, and opens a new group as the next step of the ladder. The pictures of the site theme are deleted, and the plan is deleted with its lasting part in the reference and the help.
Core changes:
- Ladder (admin/modules/groups.php, partials/group-ladder.html, fragments/group-step.html):
groups() counts the members of every group in one query with GROUP BY: a special group the users whose
grpit is, a point group every user at or above its thresholdtwo lists of one step: the threshold or the mark "Специальная", the node on the rail, the rank before the name in the group colour, the description, "Участников: N" over the share bar, the dial; the special list has a dashed rail
the name links to the form; the members link goes to the user list filtered by group or by threshold, the address
op=users_showthe old dial carried reached no handler- each list reverses in place by its sort button; nothing is stored
- Composer (admin/modules/groups.php, partials/group-form.html, assets/js/admin-ui.js):
add() builds three parts: eighteen presets (getRankPresets()), the console with forty icons, the icon window, the counts and the rank swatches (getRankTones()), and the group fields with fourteen swatches (getGroupTones())
getGroupNext() opens a new group filled as the next step: threshold half again above the top, rounded up, the top rank with one more lit mark, the next group colour; an alert names the top step
setRankForm() runs the preview at three sizes, the light and dark contrast tiles against their own surface, the hint where the threshold will stand, the toolbars with one Tab stop and arrow keys, and the leave guard through setConfirmTask() and beforeunload
save() writes
icon:lit:total:#rrggbb, clamps the counts, honoursrank_sync, refuses a bad icon or colour and a threshold another point group holds- the icon window announces its pick with an
inputevent, and the icon picker takes an accessible label Theme (admin base.css and theme.css, lite base.css and theme.css, tools/ui-contract.php, tools/ui-audit-baseline.json, tools/ui-contrast.json):
sl-ladder-*andsl-rank-*of the panel; a painted group name reads--sl-group-text, which clamps the lightness ofcurrentColorinlight-dark()so any stored colour holds 4.5:1new admin tokens
--sl-group-text,--sl-ladder-width,--sl-ladder-ring,--sl-ladder-bar-height,--sl-ladder-bar-mix,--sl-wheel-bg,--sl-wheel-eye-width;--sl-d-group,--sl-d-shareand--sl-d-swatchregistered underdata.sl-profile-rank-imgand the site token--sl-rank-heightgone, the third recorded exception to the frozen API- the contract records the chosen icon of the console and the open tab of the icon window as an allowed duplicate
- Pictures (public/templates/lite/images/ranks/):
- the fourteen rank pictures and the folder deleted
- Strings (admin/lang/*.php):
- the captions of both screens, the eighteen preset names and the colour names in six locales
_POINTSNEEDEDreads "Порог пунктов",_CANCELis new,_GROUPSIand_COLORgone with their only reader- Tests (tests/Unit/GroupFormTest.php, tests/Support/group_probe.php):
save() refuses a taken threshold and a bad icon or colour, ignores the threshold of a special group, clamps the counts and binds the rank to the group colour
- getGroupNext() on the stand ladder, on a low ladder and on an empty one
Reference and plans (admin/info/groups/ru.md, docs/TEMPLATES.md, docs/VERSIONS.md, docs/ROADMAP-2026.md, docs/8-RANKS-2026.md, core/system.php):
- the help describes the ladder and the composer as they stand; it stays Russian only, as every help of the panel
- "Rank" and "Group Ladder And Rank Composer" in the template reference; the ranks in VERSIONS
- the comment and the reference no longer say the panel reads ranks from the site theme
- the plan file deleted, steps 52 and 53 ticked
Benefits:
- an administrator sees the ladder of the site at a glance and composes a rank with its contrast measured
- a new group arrives as the next step instead of an empty form
- the rank is described as it stands, with no plan left to drift from the code
Technical notes:
- Breaking:
templates/lite/images/ranks/is gone; a site's own pictures are mapped byupdate.php - Breaking: the site API loses
--sl-rank-heightand.sl-profile-rank-img - no schema, address or API change
tests: ui:gates green, phpstan and php-cs-fixer clean; the 11 failing NodeProfileTest methods fail on the clean HEAD as well (installer probe); AssetVersionTest fails on the stand until the configuration is rebuilt, its version map in config/local.php predates the edit of lite base.css
Roadmap step 51, RANKS batch 1: _groups.rank changes meaning from a picture file of the theme to the string
icon:lit:total:#rrggbb, one parser and one renderer draw it as N lit marks of M in the colour of the rank, and the
seven readers that built a path to images/ranks/ pass the raw column instead. The migration writes the darkened
tones that hold 3:1 on both surfaces.
Core changes:
- Rank helpers (core/system.php):
getRankData() splits the string and checks the icon, the colour and 0 <= lit <= total, 1 <= total <= 10; anything else, an old picture name included, gives an empty array and no rank
- getRankView() renders the
rankfragment with the group line as title and accessible name, or nothing Readers (core/user.php, modules/forum/index.php, modules/account/index.php, blocks/user_info.php, modules/users/index.php, admin/modules/groups.php):
the comment, the forum post, the profile, the user block, the group table of the users statistics and the list of the panel call getRankView() on the raw column; no reader builds a path or tests a file any more
- the group table of
stats()was a seventh reader the plan had not named groups()no longer needs$conf- Fragment and theme (fragments/rank.html of both themes, theme.css of both themes, tools/ui-contract.php):
- one markup in both themes:
sl-rankwithrole="img", the marksaria-hidden, the lit onessl-is-active the lit mark takes
--sl-d-rank, written by the fragment and registered underdata; the muted one--sl-border-strong; the marks stand at--sl-space-1and take the size of the place they are printed inTemplates (fragments/comment.html, fragments/forum-post.html, partials/account-profile.html, partials/block-user-info.html):
rank_link,rank_src,rank_url,has_rank_imageandrank_altbecome onerank_html- the dead
rank_htmlbranch of the forum post, which no caller fed, is gone, so the personal title stays escaped - Migration (storage/update/sql/table_update6_3.sql):
one UPDATE over picture names maps the fifteen pictures onto the icon strings of the plan; a site's own picture becomes no lit mark of five in its group colour; only values ending in a picture extension change, so a second run changes nothing
- run on the stand: 11 rows, then 0
Tests and plans (tests/Unit/RankDataTest.php, tools/ui-audit-baseline.json, docs/8-RANKS-2026.md, docs/ROADMAP-2026.md):
- RankDataTest: a valid string, each bound, old picture names, bad icons, bad colours, the marks of the view
- the plan records the seventh reader,
--sl-d-rankandfont-size: inherit; step 51 ticked
Benefits:
- a rank works in every theme without picture files and can be any icon, count and colour
- one parser and one renderer instead of seven ways to build a path
Technical notes:
- Breaking:
_groups.rankholdsicon:lit:total:#rrggbb; an old picture name is not drawn untilupdate.phpran Breaking: the templates take
rank_htmlin place ofrank_link,rank_src,rank_urlandhas_rank_image; a theme with its own copy of these templates shows no rank- the group form of the panel still offers pictures until batch 2; saving a group there writes a file name back
- no schema, address or API change
tests: ui:gates green, phpstan and php-cs-fixer clean; the screenshot rig moved only forum-topic and profile; the 11 failing NodeProfileTest methods fail on the clean HEAD as well (installer probe)
Roadmap step 50, PAGER batch 2: the left and right arrows follow the back and forward link of the first pager on show, an "i" beside "to the top" explains the keys in the system tip, and the pager plan is deleted with its lasting part in the template reference and the change log.
Core changes:
- Pager keys (public/plugins/system/slaed.js, core/helpers.php):
setPagerKeys() clicks the
rel="prev"orrel="next"link of the first visible pager outside a dialog, so an htmx pager swaps its part as it does for the pointer- a key already handled, a key with a modifier, a field, an editable, a tab list, an open window and the file desk
keep their arrows
- getTplPagerView() hands each arrow its
key_name,ArrowLeftorArrowRight - Pager fragments (fragments/pager.html, fragments/pager-link.html of both themes):
- the arrows carry
aria-keyshortcuts the side group gains the key tip
sl-pager-keys: a quiet round trigger with a tooltip panel listing the two keys askbdand the note that a field keeps them- the pair stays byte for byte the same in lite and admin
- Pager theme (theme.css of both themes, tools/ui-contract.php, tools/ui-audit-baseline.json):
sl-pager-tip,sl-pager-tip-head,sl-pager-tip-keys,sl-pager-tip-noteand the key caps on existing tokens- the open or hovered trigger wears the tint of a pressed control; the tip is hidden under
pointer: coarse - the contract records the key list and the panel login list as an allowed duplicate group
- Strings (lang/*.php):
_PAGEKEYS,_PAGEPREV,_PAGENEXT,_PAGEKEYNOTE,_KEYLEFT,_KEYRIGHTin six locales- the French
_PAGETOPreads "Haut de page" - Tests (tests/Unit/PagerKeysTest.php, tests/Unit/PagerViewTest.php):
PagerKeysTest: the listener stands aside where the arrows belong to someone else, follows the first pager on show and clicks its link; the tip in both themes and its strings in six locales
- PagerViewTest: the arrows carry their key
- Reference and plans (docs/TEMPLATES.md, docs/VERSIONS.md, docs/ROADMAP-2026.md, docs/6-PAGER-2026.md):
- "Segmented Switch" and "Pager" in the template reference; the switch and the pager in VERSIONS
- the plan file deleted, step 50 ticked
Benefits:
- a reader turns the pages of a list from the keyboard without hunting for the arrow buttons
- the pager and the switch are described as they stand, with no plan left to drift from the code
Technical notes:
Breaking: a theme with its own pager.html or pager-link.html has no key tip and no
aria-keyshortcuts; the keys still work as long as its arrows sit in.sl-pager-navwithrel- no schema, address or API change
Roadmap steps 47 to 49: the search field of a Node list offers up to five titles of its type as the reader types, the header plan is deleted with its lasting part in the reference, and the pager of both themes becomes the approved face of pg-09 and pg-14 - the arrows and the numbers on the segmented rail with its travelling plate, "to the top" beside them, the page line under them, narrowing by its own column.
Core changes:
- Search hints (core/user.php, public/index.php, modules/node/index.php, partials/node/head.html, partials/node/hints.html):
getNodeHint() answers the light route
go=1&op=getNodeHintwith up to five links to what the search of the type finds under the read rights of the visitor; a word under the search minimum or a type outside the search answers nothing- the field is a combobox that asks after 300 ms through htmx; the list
#sl-node-hintsis a listbox - Hint keys and face (public/plugins/system/slaed.js, lite theme.css):
setHintKeys(): the arrows walk the hints round by
aria-activedescendant, Enter opens the chosen one, Esc closes the list before the lens hears it; the list opens only with options and closes when the focus leavessl-node-hintsandsl-node-hintin the lite theme- Node header reference (docs/NODE.md, docs/TEMPLATES.md, docs/VERSIONS.md, docs/5-NODE-HEAD-2026.md):
- the header, the views, the live list, the search and the feed described as they stand; the change in VERSIONS
- the plan file deleted
- Pager view (core/helpers.php):
getTplPagerView() marks each number of the window
is_faroris_nearthe current page, never an anchor, and handsrange_from,range_toandrange_textto the pager when the caller names the page size and the total- the arrows carry
rel="prev"andrel="next"; the dots are a pager-link cell instead of an inline badge - Pager fragments (fragments/pager.html, fragments/pager-link.html, fragments/inline-badge.html of both themes):
one pair of files, byte for byte the same in lite and admin: a
navlabelled_PAGENAV, the arrows assl-but-mini, the numbers assl-seg-cellinsl-seg sl-seg-pagerwithdata-sl-knob="pager", the side group with "Наверх" on#top, the line "Страница 3 из 10 · Показаны 21–30 · Всего 97"- the admin
sl-pnumrow and theis_pager_dotsbranch of both inline badges are gone - Pager theme (theme.css and base.css of both themes):
.sl-pageris the container its steps measure: under 520px of its column the far numbers and the range go, under 320px the neighbours too; the site centres the row, the panel starts it at the left and pushes the line right- the digits of the rail read at
--sl-textfor AA, a number in hand takes--sl-primary-strong - the forum foot gives the pager a width and wraps it under the buttons; the panel list foot wraps under 560px
--sl-pnum-width,--sl-pnum-height,--sl-pnum-border,--sl-pnum-arrow-width,--sl-pnum-arrow-heightand the admin--sl-card-shadowstruck from the frozen API by the owner's decision: their one reader was the drawn arrow of the old panel pager- Strings (lang/*.php):
_PAGENAVand_PAGERANGEin six localesTests, gates and plans (tests/Unit/PagerViewTest.php, tests/Unit/NodeRouteTest.php, tests/Support/route_probe.php, tools/ui-contract.php, tools/ui-audit-baseline.json, tools/ui-contrast.json, tools/ui-shots.mjs, docs/6-PAGER-2026.md, docs/ROADMAP-2026.md):
- PagerViewTest: the anchors, the dots, the marks at the first, a middle and the last page, the arrows, the range
- NodeRouteTest::theSearchHintsFollowTheSearchOfTheType over real requests
- the contrast crawler measures the chosen cell of a rail against the plate under it, not against the track
- the contract drops the pager fragment forks and the stale duplicate group, the freeze exception is recorded
- steps 47 to 49 ticked, the decisions of PAGER batch 1 recorded
Benefits:
- a reader reaches a material from the search field without leaving the list
- one pager markup for both themes, readable on a phone and in a 329px column, with the plate travelling between pages
Technical notes:
Breaking: the pager fragments take new keys and print new classes; a theme with its own copy of pager.html or pager-link.html keeps its old face and loses the marks;
.sl-pager-nextprev,.sl-pager-prev,.sl-pager-next,.sl-pnum-nav,.sl-pnum-curand.sl-pnum-dotsare gone- Breaking: six admin API tokens removed, listed above
- no schema, address or API change; the light route
getNodeHintis a new public read tests: ui:gates green, phpstan and php-cs-fixer clean; the screenshot rig moved only the five pages with a pager; the 11 failing NodeProfileTest methods fail on the clean HEAD as well (installer probe)
Roadmap step 46, NODE-HEAD batch 6: the header and the list of a Node type are one part that htmx swaps in place, with the address pushed and the title a reload shows, and "Show more" appends the next page under the cards. Two faults of batch 4 found by the browser check are repaired: the stuck bar flipping on a phone and a header window closed by any swap of the page.
Core changes:
- Part answer (modules/node/index.php, core/system.php):
setNodeList() answers an
HX-Requestwith the part alone headed by its<title>, every answer withVary: HX-Request- getHeadTitle() is the one rule of the title of a site page; setHead() and the part both call it
more_hreflinks the next page under every view but the rail;_NODE_MOREin six locales- Live list template (partials/node/list.html, partials/node/head.html, fragments/mode-switch.html):
#sl-node-listis the target of its swaps and the history element of htmx without a snapshot cache, so back and forward ask the part againthe filter toolbar, the three windows and the foot
#sl-node-footare boosted; "Добавить" carrieshx-boost="false"; the view switch posts throughhx-post"Показать ещё"
#sl-node-moreappends the cards of the next page to#sl-node-itemsbyhx-selectand redraws the foot byhx-select-oob- Script and theme (public/plugins/system/slaed.js, lite theme.css):
data-sl-swap-revealbrings a swapped part back when its head went above the screensetToggleBlocks() gives a block its default once, marked by
data-sl-toggle-ready; a later run keeps the state it stands in, read by isToggleOpen() as setToggleBlock() reads it.sl-node-listtakesoverflow-anchor: none, so scroll anchoring no longer unsticks the barTests and plans (tests/Unit/NodeRouteTest.php, tests/Support/route_probe.php, docs/5-NODE-HEAD-2026.md, docs/ROADMAP-2026.md):
NodeRouteTest::anHtmxRequestGetsTheListPartAlone over real requests: the part, the whole page, Vary, the title, "Show more" and the boosted regions
- the decisions of batch 6 recorded, step 46 ticked
Benefits:
- a reader narrows, sorts and pages a list without a full reload, and every state keeps its own address
- one title rule for a page and its swapped part
Technical notes:
- no schema, address or API change; a list answers the same URL with the part to htmx and the page otherwise
- a theme with its own copy of partials/node/list.html works as before without the live part
Roadmap steps 44 and 45: the draft header of a Node list becomes the approved face of nh-13 - a sticky bar with the windows of the categories, the letters and the sorts, month heads and counts in the plural form of the locale - and a type now offers views of its list: cards, compact, list, accordion, tiles and a rail, the choice of the visitor kept in a cookie. The stand pages of the panel UX plan for 2027 follow the owner's choices of the menu and gain the rail.
Core changes:
- Header face (partials/node/head.html, theme.css, base.css, modules/node/index.php):
sl-node-headsticks withdata-sl-sticky; the windows of the categories, letters and sorts open one at a time throughdata-sl-toggle-group; the lens opens the search over the stuck bar- the stuck bar takes the lens below 900 px of its own width, measured by
@containeron the bar - token
--sl-node-find-widthwith the componentnode-findin the contract - Windows and counts (core/system.php, core/classes/node/query.php, category-row.html):
- setCategories() draws "all materials" first and the top level with its subcategory chips, the chosen one marked
getLetterNavi() marks the chosen letter and tells each count; NodeQuery::getNodeLetters() counts the letters of a category live, the stored counts serve the list without one
- getPluralForm() picks the form a constant carries by the plural rule of the locale (three forms ru, uk, pl)
- Month heads (fragments/date-head.html, partials/node/list.html, privat-list.html, modules/account/index.php):
- a list by publication in cards or compact is headed month by month with the count of its cards on the page
- one date head for the mailbox and the Node list:
sl-pmf-daybecamesl-date-head, its token keeps its name - Views of a list (core/classes/node/query.php, config/node.php, modules/node/profiles/*.json):
list.viewschecked against NodeQuery::VIEWS, its first view the default- an empty list, the default, offers the one view of the display mode by NodeQuery::MODEVIEW
- news offers cards, compact and tiles; docs list and rail; faq accordion and list
- the type form of the panel gains the allowed views and the default view
- View switch and cards (mode-switch.html, modules/node/index.php, docs/card.html, media/card.html, theme.css):
the list answers a POST of the switch: its token of scope
viewsets the cookieview-<type>and answers 303 to the same list; a view outside the type or a foreign token changes nothing, a stale cookie falls backfragments/mode-switch.htmldraws the colour mode and the view switch, cells carryaria-pressedlist is
node/docs/cardwith the thumbnail and the date, accordionnode/faq/card, tilesnode/media/cardwith the play mark only for media; containerssl-node-compact,sl-node-row,sl-node-tiles,sl-node-rail- the rail is the tabs of slaed.js on two panes;
sl-pmf-split/aside/viewrenamed tosl-duo/-aside/-main partials/node/media/list.htmldeleted, the common list draws the tiles- Strings (lang/, admin/lang/, modules/node/lang/, modules/node/admin/lang/):
_PRSORTand the panel_SORTbecame one_SORTin lang/;_CATVORHremoved with the category toggle- the views, the windows and the counted materials in six locales
- Panel UX stand (public/demo/ux-01 to ux-09, demo.js, demo.css, docs/2027-ADMIN-UX.md):
- the menu keeps the two panel blocks with a filter above them in place of four groups
- the list view icons, saved views that keep the filter, the view and the sort, the icon pair of the top bar
- ux-09-rail.html: the menu folds into a rail of icons with the same fan as the full menu
Tests and gates (NodeRouteTest, NodeQueryTest, NodeProfileTest, SegKnobTest, route_probe.php, node_probe.php, tools/ui-contract.php, tools/ui-audit-baseline.json, tools/ui-contrast.json):
- a list answers only the views of its type and falls back to its first; POST answers 303, PUT 405
- header links, counts, month heads and letters of a category over real requests
list.viewsrefused when repeated or outside the registry
Benefits:
- one face for every Node list, its controls links the server answers
- a type chooses how its list reads without a template of its own
Technical notes:
- Breaking:
config/node.phpneedsnode.defaults.list.views([]); without it every type is refused atlist - Breaking: POST to a Node list answers 303 instead of 405
- docs/5-NODE-HEAD-2026.md records the decisions of batches 4 and 5
Roadmap steps 38 to 43: the editor plan closes into its reference, the segmented switch with its travelling knob becomes a part of both themes, and the Node list header is built in its first three batches - the decisions, a feed that names and dates its list and revalidates, the script of the bar, and the data of the header with a draft template. The rank plan and the panel UX plan for 2027 join the docs with their stand pages.
Core changes:
- Editor reference (docs/EDITORS.md, docs/TEMPLATES.md, docs/VERSIONS.md, docs/4-EDITOR-2026.md):
- the shell, its runtime, the manifests with a list type and what a page may tell an editor described as they stand
- the plan file deleted, its lasting part in the reference
- Segmented switch and the top link (slaed.js, base.css and theme.css of both themes, mode-switch.html, foot-controls.html):
sl-segwithdata-sl-knob: a knob that travels to the chosen cell, the header mode switch stands on it- one "Наверх" on
#topwith smooth scrolling in CSS; getFootControls() and the mode switch lose their dead keys - dead flags of link.html, span.html and toggle-form-block.html cleared; tests/Unit/SegKnobTest.php
- Node feed (core/user.php, tests/Support/route_probe.php):
- the channel names the site, the type and a readable category; links its list; lastBuildDate is the newest item
- a closed or unknown category answers 404; the tag is computed before rendering and answers 304
- the copyright names the site
- Header script (slaed.js, public/demo/nh-08 to nh-13, demo.js, demo.css):
data-sl-toggle-group,data-sl-stickywithdata-sl-stuck,data-sl-lens; the stand pages stand on them- Header data (modules/node/index.php, partials/node/head.html, list.html and media/list.html of lite):
getNodeHead() replaces getNodeNavi(): count with "N of M" for a narrowed list, the sorts of the type, the other direction, reset, the feed with the category, the form and the category switch; every link keeps the filters
- getNodeSortWay() is the one default direction of a sort for the list and the header
personal filters
new=1andfav=1for a signed-in visitor, any other value 400,favon a type without favorites 400; a guest's one narrows nothing and is answered noindex- Counts and their cache (core/classes/node/query.php, core/system.php, core/classes/node/service.php):
NodeQuery::getNodeTally() counts the total, the main and extra categories, the first signs and the next publication or expiry; getNodeReadSet() names the readable set; setNodeFavorite() and getNodeOwnCount() serve the filters
getNodeCountMap() keeps one file per type, an entry per readable set, until a write, a type version or the next publication; deleteNodeCountMap() is called after every material write and by deleteCategoryMap()
- category tiles carry their count, a closed tile none; a letter without materials is not linked; the total shows
- Previous visit (core/system.php, modules/account/index.php):
getUserLastVisit() keeps
lastvisper account in the session; updateSessionTrack() and setUserLogin() ask it before they move the column- Constants (lang/.php, admin/lang/.php, modules/node/lang/.php, modules/node/admin/lang/.php):
_ASCand_DESCmoved from the panel language to the site language,_NODE_UPDATEDto the site language of Node_NODE_SINCEand_NODE_INFAVin six localesPlans and stands (docs/ROADMAP-2026.md, docs/5-NODE-HEAD-2026.md, docs/6-PAGER-2026.md, docs/8-RANKS-2026.md, docs/2027-ADMIN-UX.md, docs/NODE.md, public/demo/grp-01 to grp-10, public/demo/ux-01 to ux-08):
- the decisions of NODE-HEAD batches 0 and 3 recorded, steps 38 to 43 ticked
- the rank plan joins the roadmap as steps 51 to 53, the schema steps move to 54 to 61
- the panel UX plan for 2027 with its stand pages, outside the roadmap
- Panel settings (admin/modules/config.php):
- the CSS file field of the settings is no longer required
- Theme gates (tools/ui-contract.php, tools/ui-audit-baseline.json, tools/ui-contrast.json):
- the contract and the registries follow the switch and the new rules
Benefits:
- a reader sees how many materials a list holds and narrows or sorts it with plain links that keep each other
- the counts cost no query on a cached request and change at the moment a material does
- the feed is a list of its own for readers and search engines, with revalidation instead of no-store
Technical notes:
breaking: partials/node/list.html receives
head_htmlinstead ofnavi_html; getNodeNavi() is gone; a theme with its own copy of the list template draws no header until it follows- breaking: getFootControls() lost its label and debug parameters
- new data cache file per Node type: nodecount; no schema change
tests: NodeRouteTest gains the feed and two header tests, NodeQueryTest the new public methods, SegKnobTest; ui:gates green, phpstan and php-cs-fixer clean; the screenshot rig moved only node-list as intended
- the 11 failing NodeProfileTest methods fail on the clean HEAD as well (installer probe)
Step 37 of the roadmap, EDITOR batch 10: every frame of the shell can show what the reader will see, a file template filled with sample files of the panel theme and a text rendered by the parser through a POST route, both in a sandboxed frame. The screenshot rig that guards such changes now walks the manifest in parallel, shoots only the states a change can reach, and no longer reports the live figures of the stand as theme changes.
Core changes:
- Preview data and route (core/classes/editor.php, core/helpers.php, public/index.php):
previewis a key of the page likevars: 'text' or the sample values of a template with an address to open- getPageData() validates it, getViewData() hands the frame its route, token, module and store, or the values
- getTplTextarea() passes 'text' for every field
getEditorPreview() answers go=1 on the site and go=5 in the panel, POST only (405 otherwise)
- the save filter of the format and checkEditorTextRoom() run first, as a save runs them
- an attachment of a closed owner is drawn in the context of an unsaved text, [getUploadOwner($mod), 0]
- the answer ends there, so the debug block of the door stays out of the frame
- File template samples (admin/modules/uploads.php, core/classes/filemanager.php, public/templates/admin/assets/samples/):
- getTemplateSample() fills the variables as the parser does for one file: picture, sound, video, PDF, archive
- four bundled samples, 24 KB together; a PDF falls back to its link in the sandbox and the pane links the sample
- FileManager::getFileKind() becomes public and static for it
- Shell and runtime (public/plugins/system/editor.js, editor-frame.html and editor-kit.html of both themes):
- the eye in the capsule and a palette row; the pane under the text, beside it on the full screen
- the page is written by srcdoc with the stylesheets and colour mode of the page, sandbox="allow-same-origin" without scripts
- an older answer is dropped, the frame height follows the page through a ResizeObserver
- Theme (theme.css of both themes, tools/ui-contract.php, lang/*.php):
- pane, sheet and side-by-side full screen rules on existing tokens; --sl-d-view-height registered
- _EDITOR_SAMPLE in six locales
- Screenshot rig (tools/ui-shots.mjs, tools/ui-shots.json, package.json):
- the viewport is stretched to the document once instead of a full-page shot per try, which fired a resize every time
- a pool of four slots, each with its own PHP session, saved between runs
a DOM snapshot beside every picture; a check shoots only a state whose DOM moved, which loaded a public file changed since the capture, or which a changed CSS rule still matches; a DIFF names the elements that moved
- PNG comparison in Node through pngjs with the former metric; noise floors kept between runs and measured from a fresh load
- masks after a step that navigates, no focus in a base state, list boxes scrolled alike
- the footer timing of the panel and the live rings, bars and charts of the presentation are masked
Benefits:
- an author sees the rendered text or template before saving, in every engine of the shell
- a full before takes about 3.5 to 4 minutes and an after about 3, against 10 to 15 for each before
- the false differences of the front page, the presentation and the file block code state are gone
Technical notes:
- new route op getEditorPreview in go=1 and go=5; no schema change
- new dev dependency pngjs 7.0.0
- tests: EditorFormatTest::previewFollowsItsCaller; ui:gates green, phpstan and php-cs-fixer clean
- the 11 failing NodeProfileTest methods fail on the clean HEAD as well (installer probe)
Plain Plus and CodeMirror now take the variables of a template from their caller with a list, a hint at the caret and marks, check an html template with a badge, a list and fixes of one click, and compare the present text with the loaded one in a window of the canon. Roadmap steps 34 to 36 (editor plan batches 7 to 9); the schema plan for 8.0 joins the roadmap as steps 51 to 58.
Core changes:
- Variables and the hint (core/classes/editor.php, public/plugins/system/editor.js, fragments editor-kit.html and editor-frame.html):
vars is a map of a word in brackets to its description; Editor::getPageData() reads it for getCode() and getContent() and getFrame() empties it, so the interfaces keep their signatures and the next frame starts clean
The frame hands the pairs over as data-sl-editor-vars; the capsule gains the list and the hint (Ctrl+Space), the palette a group of its own with one row a variable
One list .sl-editor-hint serves Plain Plus at the caret and every engine under the capsule button: arrows choose, Enter, Tab or a click insert as one step of undo, a used variable is ticked
- CodeMirror marks the known variables and completes them after a bracket through one language data source
- The status line counts the variables used
- Template check and format (core/classes/editor.php, public/plugins/system/editor.js, fragments):
lint is a key like vars, kept only for html; getIssues() is one check for every engine: unknown variable with the nearest known one, a tag left open or closed twice, img without alt, _blank without noopener, a value without quotes, a template without [src]
A textarea checks after a pause, CodeMirror through linter() with its gutter; both write the badge, the rail and one list from the kit, a row selects the place, its button applies the fix found again in the present text
"Tag on a line" and "join into one line" need the check, in the capsule and in the palette under Format; Ctrl+Shift+M opens the findings, F8 steps through them in CodeMirror
- Comparison (fragments editor-kit.html and editor-frame.html, public/plugins/system/editor.js, theme.css):
A dialog.sl-modal.sl-modal-lg in the kit, cloned once per page: the views "together" and "side by side", the count of units gone and new, "restore before" and "keep after"; it opens from the capsule, the palette and the dirty mark of the status line, which became a button
Computed only when opened and written as text nodes with del and ins; the shared head and tail are cut first, the middle is aligned by its longest common subsequence up to four million cells, else by lines and the changed lines by units; an 80 KB text with three edits opens in about 100 ms
- "Restore before" writes the loaded text after the window has closed, one step of undo
- Callers and texts (admin/modules/uploads.php, admin/lang/, lang/):
- tplconfig is the first caller: it passes its variables and asks for the check
_TPINFO and the markup it carried leave the admin locales; ten UPLOADS* constants describe the variables and the alert above the form lists them, which fixes the French and Polish lines naming the wrong variables
- Thirty EDITOR* constants in six locales for the variables, the check, its fixes and the comparison
- Themes (templates admin and lite, theme.css, tools/ui-contract.php, tools/ui-audit-baseline.json):
Rules of the hint, the findings, the badge and the comparison in both themes on their own tokens; the fix button and the selected row of list and CodeMirror share one rule
The editor tab gives way to the capsule and cuts its words instead of wrapping, so a capsule unseen at rest never makes an editor taller
- The resets of the palette and the comparison bodies outrank the canon spacing of the window body, which overrode them
- --sl-d-hint-x and --sl-d-hint-y registered; the audit baseline is stored
- Tests and plans (tests/Unit/EditorFormatTest.php, docs/*):
- EditorFormatTest checks the variables, the check and the comparison through both doors, the kit and the runtime
- The editor plan records batches 7 to 9 and decision 13; WINDOW.md lists the editor comparison
docs/7-SCHEMA-2026.md plans one set of column domains, the time rule, the connection contract, the collation, unique email and keys that serve queries; the roadmap ticks steps 34 to 36 and adds the schema steps
Benefits:
- A template is written with its variables at hand and checked before it is saved
- Every change can be seen against the loaded text before the form is sent, at any length of text
Technical notes:
- Stored content and the editor interfaces are unchanged; no schema, address or API change
- Rows of editors with a long tab are one line now, 29 px lower where the capsule used to wrap above the tab
- Gates: ui:gates and the editor and file format tests pass; the audit baseline is stored
Plain Plus and CodeMirror as text now insert files and emoji as Toast UI does, through one adapter of the shell runtime, and both engines carry the capsule of the final face with only the commands each one serves, a command palette that is a window of the canon, the Markdown commands with a list continued by Enter, and a counter of the room the text column leaves. Roadmap steps 32 and 33 (editor plan batches 5 and 6).
Core changes:
- File window for every text engine (core/helpers.php, core/classes/editor.php, public/plugins/system/editor.js, filemanager.js):
getEditorFileKit() works out the options and draws the file window, the gallery and the insert options once for Plain Plus, CodeMirror and Toast UI; the window ids are <id>_fm* for every engine
- A text whose field names an upload place carries the options as data-sl-editor-files; code takes none
The adapter getPort() answers focus(), insertText(), exec('addImage'), getMarkdown() and addHook('addImageBlobHook'); SlaedFileManager.addUpload() binds it on first use and api.addPanel opens the window
- A picture is written in the format of the field: Markdown for markdown, the [img] tag otherwise
- A pasted or dropped image goes to the window as Toast UI's hook sends it; a paste carrying text/rtf stays text
- The forum's insert of a name reaches a text under CodeMirror through SlaedEditor.getEditor()
- Emoji in the shared runtime (public/plugins/system/emoji.js, emoji/<locale>.js, partials emoji-panel.html):
- Script, six word files, templates and rules leave the Toast UI plugin and skins; SlaedEmoji replaces SlaedToastUi
Editor::getEmojiPanel() prints the panel once per answer; Plain Plus and CodeMirror load it on the first press, Toast UI with its page; every text offers it, code never
- Capsule and palette (fragments editor-kit.html and editor-frame.html, public/plugins/system/editor.js):
The capsule stands in groups, each command marked with what it needs (cm, code, text, markdown, files); the runtime drops what the frame cannot serve and an empty group, the theme draws the line between groups
The capsule keeps to one line within --sl-capsule-max-width and scrolls the rest, so a capsule unseen at rest never makes an editor taller; under 900 points the folds leave it, under 560 copy and reset
The palette is a modal dialog.sl-modal inside the kit, cloned once per page: groups Edit, Format, Insert, View, a filter by letters in their order with the matches marked, the last command raised with the word of the locale, the keys of the engine under the list, Esc closing at once over a typed search
- A command runs once the palette has closed and given the focus back, once per choice however often it is pressed
- Ctrl+K is heard only inside a frame; Ctrl+B and Ctrl+I only in the text of a Markdown field
- Markdown commands and counter (public/plugins/system/editor.js, core/classes/editor.php):
Bold, italic, code, link, list and quote put their mark around the selection or take it away; code over several lines becomes a fence; a link selects url to be typed over, a selected address becomes the target
A textarea continues a bullet, numbered, task and quote list by Enter and ends it on an empty item; CodeMirror does that through its Markdown grammar
- The commands follow the format of the field: CodeMirror as text has them, Plain Plus only on a markdown field
data-sl-editor-room carries the room of a text column; the status line says what is left in bytes and turns to _ETEXTLONG once the text no longer fits; mediumtext columns and code show no counter
- Themes (templates admin and lite, base.css, theme.css, editor skins):
- Palette, capsule groups, counter and emoji panel rules byte-identical in both themes; Toast UI skins lose the panel
- --sl-capsule-max-width declared in both API blocks; component capsule declared in tools/ui-contract.php
- .sl-editor-sep gone, the line between groups is a pseudo-element
- Texts, tests and plans (lang/, tests/Unit/, docs/*):
- Thirty-one EDITOR* constants in six locales; _CODE, _URL, _LIST, _QUOTE, _EFULLSCREEN and _EDITOR_NONE reused
EditorFormatTest checks the capsule, the palette and the counter of the column; the file manager tests follow the resolver, which opens md as Markdown and ini as INI since the batch that added those grammars
Editor plan records batches 5 and 6 and decisions 8 to 12; WINDOW.md lists the editor palette; roadmap steps 32 and 33 done
Benefits:
- One shell gives every text engine the same file window, emoji, commands and keys
- Every command stays reachable from the palette, whatever the width of the row
Technical notes:
- Stored content is unchanged; Plain Plus still stores plain, so its Markdown commands wait for a markdown field
- The counter measures the raw text; the server measures the filtered value, which a plain text makes longer
Gates: ui:gates, the editor, language, file manager and asset version tests pass; the audit baseline is stored; NodeProfileTest stays red as on the previous commit (the install probe)