Официальный сайт SLAED CMS
Журнал изменений
The Phoenix stand had fifteen separate faces of the logo; three of them are merged into one screensaver of sixty seconds that starts after a rest of the hand, runs in a circle and fades out when the hand comes back. A second stand page lets the owner hear and choose the sounds of messages, mentions, alerts, wins and moderation before a standard of sound is written.
Core changes:
- Screensaver (public/demo/assets/phoenix.js):
One scene on its own clock in acts: the page dims to 75% while the code of the system rains in, sparks rise from the code into a giant A, the A falls to ash, the ash takes fire into the big logo whose letters grow around the A, the logo falls to ash and flies pixel by pixel into the header, falls again and becomes the real logo
- one particle for the whole scene, a square of smouldering ash; colours change by ramps, dots sit on whole
screen pixels, the rain is made of lines of core/system.php
- the quiet: ash rises from the foot of the page and every six seconds the beam of the installer crosses the
letters of the logo, sparks leave the A and the version badge flashes
- Chance comes from a seed and the ash moves in fixed steps, so any second can be reached by seeking
Screensaver rules: it starts after the chosen rest on the page (60 s by default), loops while nobody wakes it, a move, a key, a wheel or a scroll fades it out in 0.8 s; the quiet stays on as the ground of the page
- the measures of a round are taken when it starts, the beam follows the logo where it is now
- prefers-reduced-motion and the Motion option skip the scene and show the end state
- The fourteen other faces and their machinery are removed
- Stand player (public/demo/phoenix.html):
The dock is a player: play and pause, a stopwatch, a scrubber, chapters per act, speed 1/4, 1/2 and 1, the rest before the screensaver (10 s, 60 s, 5 min, off) and Launch (R)
- its clicks and keys neither wake nor count as the hand on the page; a paused player is left alone
- Styles (public/demo/assets/phoenix-fx.css):
Only what the screensaver uses: the ground over and under the page, the dimming veil next to it, the badge that springs out and shines with every beam
- Sound lab (public/demo/snd-01-lab.html):
Zones with variants: personal messages in and out, mentions and replies, alerts in their five theme tones (error, warn, success, info, accent, each with a silent choice), wins, the moderation queue
- synthesised voices in D minor, the key of the Phoenix theme, and three cuts of its strongest moments
Rules shown live: a sound always with a counter and the number in the tab title, one sound per category in 2.5 s, quiet on an open tab for what comes from others, full for the answer to one's own action, only alerts that appear sound, the silent switch of the phone where Safari allows it
- A master switch in sl.sound (1 or 0) next to sl.motion; zones, variants and volume in sl.sound.set
Benefits:
- One screensaver that tells the return of the system and gets out of the way the moment it is not wanted
- The sounds are chosen by ear on a working page before any of them goes into the theme
Technical notes:
- Stand pages only (public/demo/); no theme, core or schema change
- Music in the screensaver was weighed and left out: it is the default effect of the system
Version 1.0.4 of the presentation package. The headings, the texts and the captions take the brand face Magistral instead of Segoe UI and the monospace face, which stays only where code is shown, and the six videos are rendered again from the browser frames with the new face.
Core changes:
- Face (public/phoenix/vendor/base.css, public/phoenix/player/styles.css, public/phoenix/index.html):
- base.css gets the token --sl-face-body, Magistral with Arial and sans-serif behind it
The stage, the header, the kicker, the archive captions, the continuity row, the editor head, the build bar, the modules, the technology row, the HUD, the history and the controls use --sl-face-body
- the code of the editors and the continuity listings keep --sl-face-mono
- index.html stamps base.css ?v=2 and styles.css ?v=4
- Technology row (public/phoenix/index.html):
- Every item of the final row carries its Bootstrap icon: release, PHP, MariaDB, core, light and dark
- Videos (public/phoenix/exports/*.mp4, public/phoenix/presets/production/video-fullhd.json):
- The dark and light videos in 1080x1920, 1920x1080 and 720p are rendered again with the new face
- The production preset records the faces and their WOFF2 files
- Package (public/phoenix/manifest.json, public/phoenix/checksums.json, public/phoenix/README.md):
- Version 1.0.4 of 2026-10-14; checksums of the changed files
README says the texts are set in Magistral, the marks use Bootstrap Icons, and both WOFF2 files match the originals of the lite theme byte for byte
Benefits:
- The presentation speaks in the face of the brand, in the player and in every video
Technical notes:
- The WOFF2 files were already in the package; nothing new is loaded
- No change outside public/phoenix/
Readable addresses are the only form of a page address since 2026-10-14. The README and the help of the settings now state the one thing a server must do for them, so an installation on a server without the rule does not end with a start page that opens and every other page answering 404.
Core changes:
- Requirements (README.md):
Web Server names Apache or LiteSpeed with mod_rewrite and nginx with the server block of nginx.conf.example
- another PHP-capable server (IIS, Caddy) needs its own rule: every path that is no file or folder goes to index.php
The server section adds what happens without that rule: the start page opens, every other page gets the 404 of the server
- Help of the settings (admin/info/config/ru.md):
Page addresses and canonical: readable addresses are the only form and cannot be turned off; the separators / and - are fixed
- the server rule, public/.htaccess for Apache and LiteSpeed, try_files of nginx.conf.example for nginx
Benefits:
- An administrator learns the server requirement before the installation, not from a site that answers 404
Technical notes:
- Text only; no code, template or schema change
Batch 8 of docs/10-URL-2026.md (roadmap step 75): what lasts of the plan moves into the reference, the help of the panel and of the modules, and the plan is deleted. A live check of every address and every printed link on Apache (root on public/ and on the project) and on nginx found a redirect loop, a served dot file and a hard-coded link; all three are fixed.
Core changes:
Reference (docs/ARCHITECTURE.md, docs/NODE.md, docs/TEMPLATES.md, docs/VERSIONS.md, docs/TESTS.md, README.md, UPGRADING.md):
docs/ARCHITECTURE.md gets "Address Boundary": the address table, the registry _routes and the class Route, the name, when an entity gets and changes its path, migration, parsing, building and the one redirect
- the bootstrap, the routing keys, the front controller and "What Not To Assume" follow it
docs/NODE.md: the permanent type name, Route::checkTypeName(), "Addresses" of a type, the paths of the writers, page in place of num, the 301 of setSeoMove(), the canonical and href on paths
- the operation attach, which getNodeOps() no longer has, leaves "Public routes"
- docs/TEMPLATES.md: {{ root }}, the key route of setHead() and "Links In Templates"
docs/VERSIONS.md records the addresses of 2026-10-14 with their breaking changes; UPGRADING.md gets "Readable Addresses"; README.md the routing and the cut of the path; docs/TESTS.md the five Route tests
- Help of the panel and the modules (admin/info//ru.md, modules//admin/info/ru.md):
- config: page addresses and canonical, 301 and 410, where forcessl and forcehost act
- security: dot files refused, DirectorySlash Off in the project mode, the front controller required
- editor, scheduler: the front-controller rule must stay, the sitemap task writes the Sitemap line
- privat, search: /account/privat/123 and /search; template: {{ root }}
categories, node, voting: the field Address; account: a new login moves the profile address, the OAuth redirect URI keeps its query form on purpose
- Servers (.htaccess, public/.htaccess, nginx.conf.example):
The project .htaccess turns off DirectorySlash, which sent /docs to the folder docs/ of the project and back to /docs in an endless loop when the document root is the project
public/.htaccess and nginx.conf.example refuse every dot file or folder but .well-known/; nginx served /.htaccess with 200
- Theme (public/templates/lite/partials/site-footer.html):
- The ideas button links the path of its forum section instead of forum?cat=95, which answered 301
Plan closed (docs/10-URL-2026.md, docs/ROADMAP-2026.md, tests/Unit/Route*Test.php, tests/Support/registry_probe.php, docs/2027-CONNECTOR.md, docs/2027-VINE-MONITOR.md):
- The plan file is deleted, step 75 ticked; the tests and the 2027 plans point at "Address Boundary"
- Connector plan (docs/2027-CONNECTOR.md):
Full, safe administrator access for AI agents over MCP (owner, 2026-10-14): off by default, a risk class per action with an owner-set policy, short-lived keys, a fixed list of actions no agent gets, an audit trail
- api_client gains aid and policy, the table api_confirm holds confirmations, config/api.php the key risk
- a confirm policy answers input_required; a user key stays draft-only
Benefits:
- The reference and the help describe the addresses as they work, with nothing left in a plan
- Apache with either document root and nginx give the same answer to every address
Technical notes:
Live check: route matrix 57/57 and about 890 printed links on all three configurations, no relative address, no link answering 301, no missing-route warning in the log
- The stand's nginx file .osp/nginx/slaed.loc.conf (outside the repository) got the same dot-file refusal
Breaking: a dot file in public/ is no longer served; a server whose document root is the project needs DirectorySlash Off, which the shipped .htaccess carries
- No code or schema change besides the server files and one template link
Batches 5, 7 and 6 of docs/10-URL-2026.md (roadmap steps 72-74): the builder writes the stored path of an entity, every link, endpoint, form and picture the site prints starts at the root of the site, and a page asked by any other address - a query, a former path, case, a slash, num, the wrong host - lands on its address in a single hop.
Core changes:
- Building (core/system.php, core/classes/node/*, modules, blocks):
getSeoUrl() takes a passed route path, puts operation and id into the path and sorts the query
- the category map answers the path of a category; a missing route of an entity link is logged with its caller
- page replaces num on the public lists, the canonical keys and the title; the panel keeps num
The readers join the registry: Node lists and views, targets, sitemap, legacy map, forum, users, polls, comments
- the writer answers the path a publication gives, so the redirect after a submission and the approval mail link it
- Addresses from the root (templates, core, modules, blocks, lang/, modules/account/lang/):
- Templates take {{ root }} from Template::getView(); theme pictures getThemeImageUrl()
- Page links go through getSeoUrl(), mails through getPublicUrl(), endpoints, forms and htmx from HOME_PATH
user_info() takes the path of the user; the language constants of the account link carry href="%s"
- the broken Polish links and the links to op=reg, op=passfor and op=profil are gone
- One redirect (core/system.php, core/security.php, public/index.php):
setSeoMove() answers one absolute 301 for every reason at once, on the forced scheme and host
- GET and HEAD only; never POST, htmx, an endpoint, a module file, a refusal or another script
- the material, the topic, the profile and the poll call it right after their read and access check
- the three callers of getNodeLegacyUrl() answer through it, so a 6.2 address is one hop
- index.php keeps no 301 of its own: a path that names only the script is the start page
- The early forcessl/forcehost block keeps the endpoints, the panel and the scripts; the host keeps its port
- The forum counts a view after the access check; a missing profile answers 404, a members-only one 403
- robots.txt (public/robots.txt, core/system.php):
- The Disallow lines of folders outside public/ and the relative Sitemap line are gone
- addSitemapTask() writes Sitemap: <homeurl>/sitemap.xml after every map, other lines untouched
- Tests (tests/Unit, tests/Support):
- RouteBuildTest: the address table built from its entities, list query counts, no relative address at a path
- RouteMoveTest: each reason alone and mixed in one hop, the kept query, refusals, methods, no view counted
- The probes follow one 301 on their site; the install router sends every path to index.php
Benefits:
- A search engine moves once, from the query addresses of 6.2 straight to the final paths
- One place decides the address of a page, the redirect and the canonical
Technical notes:
- No schema change; the registry _routes of batch 1 is read only
- Breaking: every GET of a page at index.php?name=... now answers 301 to its path
- RouteRegistryTest::twoWritersRaceForOnePath fails on the MariaDB 11.7 of the work machine, before and after
Batches 3 and 4 of the plan of readable addresses before 8.0 (steps 70 and 71 of docs/ROADMAP-2026.md). The migration and the installer give every entity due an address its first path, a request at a path is cut before the database and resolved after it, and the builders of the core print addresses from the root of the site, so a page opened two segments deep finds its styles, scripts and links. The links still read index.php?...; building, the literals and the redirect follow.
Core changes:
- Migration (core/system.php, public/update.php, public/setup.php):
addRouteBacklog() gives types, categories before materials per type, forum sections, topics, users and polls without a module their first path, oldest id first, 500 per transaction; a second call adds nothing
- A material is due when Published, Disabled or Deleted with published <= NOW() in a type whose ext is not support
- The second stage of update.php runs it as setMigrateRoutes() on every run and keeps the counts in the manifest
- setup.php runs it after the Node profiles, so the seed of insert.sql gets its paths
- Parsing (core/security.php, public/index.php, core/classes/route.php):
The block of retired addresses keeps up to five segments of [A-Za-z0-9_-] of a path of index.php in $urlroute, with the flag bent for upper case, a trailing slash or /index.php/ in front; anything else answers 404 before the database, and a path after the script naming only the script keeps its 301
public/index.php resolves the segments by one grammar: a type or module is its home, a reserved operation its op, digits after it the id, any other path the registry; a former path opens its entity marked to move, a gone path answers 410, an unknown one 404; the keys replace the same keys of the query in $_GET
- Route::isRouteReserved() gives a module without a switch no operation instead of those of a Node type
- getVar() reads a scalar from $_POST and $_GET, so the keys of a path reach every reader
Addresses from the root (core/system.php, core/stream.php, core/classes/template.php, core/classes/access.php, core/classes/parser.php, core/classes/editor.php, core/user.php, modules/account/index.php, modules/forum/index.php, modules/node/index.php, public/plugins/editors/toastui/driver.php):
HOME_PATH is the path of homeurl with both slashes; the installer and the first stage of update.php take the folder of their script
getSeoUrl(), Template::getAssetUrl(), getUploadUrl(), FileAccess::getFileUrl() and the image check of Parser answer from the root; setRedirect() puts the root before a relative target
getPublicUrl() also takes an address of the site as a string and answers an absolute one as it is; the canonical address, the feed item and the images of a profile, a topic and a material go through it
- getImgText(), the image check of Parser and the feed take the root off an address before they use it
- Three checks of an existing asset ask for ?v= instead of comparing the address with its file
Tests (tests/Unit/RouteParseTest.php, tests/Support/route_probe.php, tests/Support/install_probe.php, tests/Unit/UpdateSiteTest.php, tests/Unit/NodeProfileTest.php, tests/Unit/InputVarContractTest.php and others):
route_probe.php parse: every row of the decided address table as a path, the grammar, the cut, the keys of a path over those of the query, and a page two segments deep linking its assets from the root
The rehearsal of update.php on the clean 6.2 holds the whole registry of the fixture; a clean installation holds the paths of its seed; getVar() scalars are driven from CLI
The expectations of assets, uploads, the file route and Node links read from the root; the bootstrap defines HOME_PATH
- Plan (docs/10-URL-2026.md, docs/ROADMAP-2026.md, docs/PERFORMANCE.md):
Batches 3 and 4 recorded; addresses from the root decided by the owner; the batch of the literals runs before the redirect
Benefits:
- A path such as /news/internet opens its page with styles and scripts while every 6.2 address keeps working
- The migrated production base and a fresh installation start with one current path per entity
Technical notes:
- No schema change; the stand's registry was filled once by a direct call of addRouteBacklog()
Breaking: getSeoUrl(), Template::getAssetUrl(), getUploadUrl() and FileAccess::getFileUrl() answer addresses from the root of the site (/index.php?..., /templates/..., /uploads/...) instead of relative ones
- Breaking: getPublicUrl() accepts array|string; setRedirect() roots a relative target
Breaking: getVar() no longer reads scalars through filter_input(), so a value the core writes into $_GET or $_POST is read
Literals of templates, scripts and PHP stay relative until the batch of the links; a page at a path still misses the theme images and the pseudo-cron below the path
A review of three 2027 plans brings them in line with what the tree now is: the schema conventions, the reserved names of the readable addresses, the scheduler as it registers jobs today and the settings that are already taken. Nothing is implemented; the plans only stop promising what the code would contradict.
Core changes:
- Admin UX (docs/2027-ADMIN-UX.md):
The status says that the decisions are taken and nothing is built; the dated remarks and the finished fix of the configuration form leave the plan
Eight ideas instead of nine, the dark theme of the panel no longer an open check, the dial of the list options left to its own roadmap step
- The keyboard state of table sorting and the action journal of an undo are described as they are
- Connector (docs/2027-CONNECTOR.md):
The rewrite rule of /api/v1/ stands before the fallback to index.php, and the name api is reserved in docs/10-URL-2026.md
Own-site calls of the scheduler keep working over http and loopback; jobs register in the three places the core uses today, or in the module list once docs/2027-CORE.md delivers it
- Every column takes its domain from the schema conventions of docs/NODE.md
The update check keeps upcheck and channel in config/api.php, because config/update.php holds the marks of the 6.3 data updates; the compared version is the leading MAJOR.MINOR.PATCH of $conf['version']
- The update package accepts only the tracked top-level entries of the release tag
- Vine monitor (docs/2027-VINE-MONITOR.md):
- The module stands on the floor of the release it ships with: PHP 8.4, MariaDB 10.5.2+ or MySQL 8.0.16+
Every column takes its domain from the schema conventions; moments are DATETIME in the site zone the connection runs on, timers unix seconds
An admin action is recorded by the connector audit and by the journal of checkAdminPost(); the public page is /vine; an installed site gets the tables from the step of update.php of its version
Benefits:
- The 2027 plans start from the current tree instead of an older one
Technical notes:
- Documentation only; no code, schema or configuration changes
The Phoenix package in public/phoenix gets four Full HD exports for sending, captured from the browser at full resolution, a portrait layout of its scenes for the phone format, and a pass over its copy. Version 1.0.3.
Core changes:
- Exports and their build (public/phoenix/exports/, public/phoenix/presets/production/video-fullhd.json):
Light and dark at 1080 x 1920 (9:16) and 1920 x 1080 (16:9), about 83 seconds, H.264 Main yuv420p, 30 fps, the approved AAC track copied without re-encoding
- The preset names the viewport, scale and encoder settings of both profiles
- The two 720p exports are encoded again
Scenes and player (public/phoenix/index.html, public/phoenix/player/scene.js, public/phoenix/player/timeline.js, public/phoenix/player/styles.css):
- Headings and kickers lose their trailing full stops; ДНК replaces DNA; several lines are reworded
- The typed lines of the work scene move on their own phases, and the assembly cue starts at 42.5 s
- Text sizes of the scenes read from the theme tokens, with a portrait layout for the phone format
- The cache marks of the player files are raised
- Package files (public/phoenix/manifest.json, public/phoenix/README.md, public/phoenix/checksums.json):
The manifest lists all six exports at version 1.0.3, the README describes the Full HD files, the checksums follow
Benefits:
- The presentation can be sent as a file in both phone and screen format without upscaling the 720p videos
Technical notes:
- About 139 MB of new video; every file stays below the 100 MB limit of GitHub
- No code of the site changes
Batches 0 to 2 of the plan of readable addresses before 8.0 (steps 67 to 69 of docs/ROADMAP-2026.md). The address switch that could never work leaves the panel and the configuration, one table _routes with the class Route holds every path an entity was ever given, and each writer of a material, type, category, topic, account or poll writes its paths inside its own transaction. Links still read index.php?...; parsing, building and the redirect follow.
Core changes:
- The switch out and the inventory (admin/modules/config.php, config/global.php, core/system.php, public/update.php):
rewrite, sep, tsep, title and ctitle leave the panel, the shipped configuration, the 6.2 configuration update.php writes, the language constants of the fields and the help of the settings
getSeoUrl() always answers the query address and drops the title keys; the rewrite branches of getSeoRoute(), the sitemap, the panel monitor, NodeView and blocks/center_forum.php are gone
The inventory of literals, public operations, num readers, writers and the query baseline stands in docs/10-URL-2026.md; the plan left docs/2027-CORE.md, and no 6.2 path form is parsed
- Registry and name (core/classes/route.php, storage/update/sql/table.sql, storage/update/sql/table_update6_3.sql):
_routes: path, kind, oid, state (current, former, gone) and cur generated from state; unique path and one current path per entity
Route finds a path, gives the first one, moves it without a chain, marks every path of a gone entity gone, answers a set in one query and keeps the reserved operations and type names; a write inside an open transaction runs behind a savepoint, a racing writer takes the next suffix
Route::setRouteName() is the one rule of the field Address: the given name, else the current name under a new prefix, else the first path from the title
- filterSlug() transliterates the letters of all six locales from one table, decodes stored entities and cuts to 80
Node writers (core/classes/node/service.php, core/classes/node/query.php, core/classes/node/entity.php, core/classes/node/input.php, modules/node/index.php, modules/node/admin/index.php):
A material gets its first path where its publication comes, by hand or by the scheduled delivery, never for a type with ext support; the panel form moves it by the field Address once it has one; a deletion marks it gone
A type takes its name as its path and loses it on deletion; a new name is refused by Route::checkTypeName() besides RESERVED, so the name of a deleted type is never registered again
Categories of a type get, move and lose their paths, the subtree on deletion; Node and the writer read carry the current path as Node::$path
- A refused path is a storage failure and rolls the whole write back
Writers outside Node (admin/modules/categories.php, modules/forum/index.php, modules/account/index.php, modules/account/admin/index.php, admin/index.php, public/setup.php, modules/voting/admin/index.php):
Forum categories, new topics, accounts (activation, OAuth sign-up, the panel, the first administrator) and polls write the row and its path in one transaction; deletions mark every path gone
- A reply the recycle section turns into a topic gets its path; the recycle section keeps the path of a topic
- The forms of category and poll carry the field Address with the whole path as hint, label _ADDRESS
- getCategoryMap() joins the registry and carries path under the new cache key catmap/path
- The reads of the topic, the profile and the poll join the registry for their current path
Tests (tests/Unit/RouteRegistryTest.php, tests/Unit/RouteWriterTest.php, tests/Support/registry_probe.php, tests/Support/route_probe.php, tests/Support/guard_probe.php):
The registry: transliteration per locale, both cuts, suffixes after current, former and gone paths, no chain, the keys, cur bound to state, a race of two writers, the reserved words held against the switches and the tree
The writers over real requests, and a trigger refusing a path leaves neither the entity nor a route row in each of the seven writers
- The contracts of Node, NodeInput, NodeService, the statement budgets and the reuse of a type name follow
Benefits:
- An address once given stays bound to its entity, so 301 and 410 can be answered from one table
- The index of a search engine moves once, from the 6.2 query addresses straight to the final form
Technical notes:
- New table _routes in table.sql and table_update6_3.sql; the stand got it empty, batch 3 fills it on migration
- Breaking: rewrite, sep, tsep, title and ctitle are no settings any more; filterSlug() lost its separator argument
- Breaking: the name of a deleted Node type cannot be registered again
Breaking: NodeService::addNodeCategory() and updateNodeCategory() take the field Address as an optional argument, Node and NodeInput end in an optional path
A check of the schema reference against the code found five statements wider than the code. The reference now names exactly what the code does; nothing else changes.
Core changes:
- Schema conventions (docs/NODE.md):
Every connection of the site, the panel, setup.php, update.php, the tools and the probes that load the code runs the contract of Database; a probe that only loads a fixture opens PDO itself
- SQL compares a timer with UNIX_TIMESTAMP() or a bound time(), not only the first
- An OAuth return address over 512 falls back to index.php instead of being refused
- Every writer answers a taken address with _ERROR_EMAIL; only those that also write a name answer a taken name
- Connection and history (docs/ARCHITECTURE.md, docs/VERSIONS.md):
- "Database Boundary" and the entry of 2026-10-13 name the same set of connections
- The forum line says that a topic is new only when it changed after the last visit
Benefits:
- The reference promises nothing the code does not do
Technical notes:
- Documentation only; no code, schema or configuration changes