Официальный сайт SLAED CMS
Журнал изменений
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
Batch 7 of the schema plan moves what lasts into the permanent reference and deletes the plan. The human-readable addresses leave the 2027 core plan and become a plan of their own before the release: one address per page without an id, held in a registry of routes, so the index of a search engine moves once.
Core changes:
- Schema reference (docs/NODE.md, docs/ARCHITECTURE.md, docs/VERSIONS.md, docs/TESTS.md, README.md, UPGRADING.md):
docs/NODE.md gets "Schema conventions": the column domains, the time rule, the connection contract, the collation as one source, writes that fit their columns, unique addresses with NULL for none, keys that serve the queries
- docs/ARCHITECTURE.md names the connection contract of Database and points at the conventions and their test
- docs/VERSIONS.md records the schema of 2026-10-13 with its breaking changes for code outside the tree
README.md and UPGRADING.md recommend MySQL 8.4 or MariaDB 10.11 and newer and state the collation; UPGRADING.md lists what code outside the tree must change
- docs/TESTS.md lists SchemaConventionTest.php
- Schema plan closed (docs/7-SCHEMA-2026.md, tests/Support/route_probe.php):
- the plan file is deleted; the comment of the route probe no longer names it
- Plan of readable addresses (docs/10-URL-2026.md, docs/2027-CORE.md, docs/ROADMAP-2026.md):
docs/10-URL-2026.md: addresses without id (/files/adaptivnyi-shablon-pure, /files/moduli, /forum/modul-poiska, /users/eduard), one registry _routes with the current and every former path, an address never reused, 410 for a deleted entity, the type prefix and a fixed /, page in place of num
- the design: the registry with one current row per entity, the name in six locales, when an entity gets and
changes its path, the migration order, parsing in two stages around the database, building from passed paths,
the query rules, one final redirect after the access check
- seven batches with their tests and the estimate
- docs/2027-CORE.md drops its item 7 and the steps and questions of it, pointing at the new plan
- docs/ROADMAP-2026.md: step 66 ticked, steps 68-74 for the new plan, the close moves to step 75
Benefits:
- The schema conventions live in the permanent reference and are held by a test
- The addresses change once, before the release, in their final form
Technical notes:
- Documentation only; no code, schema or configuration changes
- The address plan is not started; its first step is 68 of the roadmap
The Phoenix codex leaves the demo folder: public/phoenix carries the presentation as one package that depends on no template or demo file and can be embedded on any page of the same origin by one script. The old demo page now only forwards to the package, so its address keeps working.
Core changes:
- The package (public/phoenix/):
index.html is the player, embed.js the integration that opens it over the current page in an isolated iframe and reveals that page at the end without a reload
- window.SlaedPhoenix with setPlay(), setStop() and isPlaying(), events slaed:phoenix:ready, reveal, end, error
- data-sl-phoenix attributes for a button, with preset, sound, volume and autoplay
player/ holds the scene, audio, particles, timeline and bridge scripts and the styles; vendor/ the copies of base.css and the icon font it needs, logos/ and images/ its pictures
- presets/ holds dark and light and the production presets of the audio edit, the generations and the export
- audio/theme.mp3 is the 83.352 s theme, audio/reserve.mp3 the reserve; exports/ the dark and light 720p videos
- archive/ keeps the source takes, the WAV master, the pronunciation samples and the earlier assets
- manifest.json and checksums.json describe the package, README.md documents the integration
- examples/ shows the embed on a page and on a nested page
- The demo page (public/demo/phoenix-codex.html, public/demo/assets/phoenix-codex*):
- phoenix-codex.html forwards to the package with its query and hash, through phoenix/redirect.js
- the old scripts, styles, pictures and sounds of public/demo/assets/phoenix-codex* are removed
Benefits:
- The presentation can be moved or embedded as one folder, without the template or demo files
- The old demo address keeps leading to the presentation
Technical notes:
- The package adds 66 files, 117 MB; the largest are archive/master.wav (14 MB) and the two exports (13-14 MB)
- The player needs HTTP(S) and the same origin as the page that embeds it
- No code of the CMS changes
Batch 6 of the schema plan rehearses update.php on a clean 6.2 site, which the owner chose over a production dump: the 6.2 path renames the seven source tables of Node again, so any 6.2 site reaches Node, while update.php stays the internal tool of the maintainer. The rehearsal pinned the root cause of the fresh installation that had failed in NodeProfileTest since 2026-10-04: setup.php opened its session a second time.
Core changes:
- The source tables of Node on the 6.2 path (storage/update/sql/table_update6_3.sql):
Batch X renames the 6.2 columns of _news, _pages, _faq, _help, _links, _files and _content to the 6.3 names the migration reads
- sid, pid, fid and lid become id, catid becomes cid, hometext and bodytext become intro or body
- ip_sender, date, totalvotes, totalcomments, associated and homepage take their 6.3 names
- rencol leaves a table already in the 6.3 shape untouched, so the production path is unchanged
- The installer session (public/setup.php):
session_start() runs only when no session is open, as core/security.php does
- an open session (session.auto_start, a router) printed a notice before the JSON of every part of the run
- the run then stalled and its first part read a config/db.php that did not exist (2002)
- Tests (tests/Support/install_probe.php, tests/Unit/UpdateSiteTest.php, tests/SchemaUpdateValidationTest.php):
- the update probe runs the second stage as the main administrator and reads the materials it made
- UpdateSiteTest checks that the two news and the file of the 6.2 fixture become materials with intro and body
- the seven source tables join _modules as legacy tables the schema file may name
- Plan and reference (docs/7-SCHEMA-2026.md, docs/ROADMAP-2026.md, docs/NODE.md):
the decisions "Any 6.2 site migrates" and "The installer starts its session once", the result of batch 6, step 65 ticked
- NODE.md no longer says that a clean 6.2 stops at the counter step
Benefits:
- A clean 6.2 site of the installer of 6.2 carries all seven old modules with comments, replies and resources
- The fresh installation passes NodeProfileTest again, on MariaDB 11.8, MySQL 8.4 and MariaDB 10.11
- The second stage of update.php is guarded by a test for the first time
Technical notes:
Rehearsed with tests/Support/install_probe.php over table.sql and insert.sql of 6.2 on MariaDB 11.8, MySQL 8.4.6 and MariaDB 10.11.14: no failed statement in three runs, the schema equal to a clean installation
- UpdateSiteTest stays MariaDB only: MySQL 8.4 refuses the zero-date defaults of its fixture dump
- update.php is still not shipped; UPGRADING.md still says the release updates no 6.2 site