Журнал изменений

Официальный сайт SLAED CMS

Журнал изменений

Фильтр и поиск

Всего: 1136 Доступных коммитов | Отфильтровано: 1136 Коммиты | Страница: 1 / 114
Сегодня (21.08.2026)
Fix: the commit hook would have been silent on a second machine, in three separate ways
Автор: Eduard Laas | Дата: 12:22 21.08.2026

A hook that is present but never runs is worse than no hook: it reads as covered. On any clone other than the one it was written in, this one was present and silent for three unrelated reasons, and on a machine without composer it would have refused every commit without saying why.

Core changes:

  1. The hook is enabled per machine, not per repository (package.json):
  2. core.hooksPath lives in .git/config, which neither a clone nor a pull carries

    • git will not let a repository enable its own hooks, which is a security property
and not an oversight, so something has to set it on each machine
  • postinstall sets it during npm install, and npm run ui:hooks sets it by hand
  • the postinstall falls through with a message outside a git checkout instead of
failing the install
  1. The hook file was committed without its executable bit (tools/hooks/pre-commit):
  2. core.fileMode is false in this checkout, so chmod +x never reached the index and the file went in as 100644

  3. Windows does not care - git runs hooks through sh either way - but git on Linux and macOS skips a non-executable hook without a word

  4. git update-index --chmod=+x puts it in as 100755
  5. The hook assumed two tools that a fresh machine does not have (tools/hooks/pre-commit):
  6. php has to be on the PATH of the shell git runs hooks in
  7. vendor/ is gitignored, so phpunit is absent until composer install
  8. both are checked before anything else and each stops with its own reason and the remedy, rather than failing on a confusing error or stepping aside quietly

  9. verified in all four directions: no php stops with the PATH message, no vendor stops with the composer message, SLAED_SKIP_GATES=1 passes silently, and a normal tree prints [gates] ok

  10. Documentation (CONTRIBUTING.md, docs/TESTS.md):
  11. both said "enable it once per clone", which reads as something the clone does
  12. both now say it is per machine, name core.hooksPath and .git/config as the reason a clone and a pull leave it behind, and record what the hook refuses to run without

Benefits:

  • the hook either runs or says why it did not, on any machine
  • npm install is enough to arm it after a clone

Technical notes:

  • this commit carries no file the gates watch, so the hook exits early on it by design; the fast set was run by hand instead and is green

  • a gate that steps aside when its tools are missing is the failure this repository has now closed five times, which is why both new checks refuse rather than skip

Chore: the theme gates start running themselves, and four of them stop answering when their input is gone
Автор: Eduard Laas | Дата: 12:11 21.08.2026

The etalon plan is finished, so its prose moves into the reference documentation and the plan file goes. What replaces the checklist nobody would remember is a pre-commit hook for the cheap gates and a two-word pair for the visual one. Four gates that read zero while checking nothing are closed, and the committed screenshot baseline is dropped because it cannot be stable on a stand whose own content moves.

Core changes:

  1. Gates that no longer answer when their input vanishes:
  2. tools/ui-contrast.json could be deleted and the contrast count still read zero

    • getContract() folds the registry in only if (is_file(...)), so a missing file
left nothing to check and ThemeContractTest stayed green
  • ThemeContractTest now asserts the file exists and carries pairs for every theme
  • node tools/ui-shots.mjs --check wrote a missing baseline image instead of failing

    • a check whose baseline is absent re-baselined itself against the tree it was
asked to judge and exited zero; it reports MISSING and exits non-zero now
  • php tools/ui-audit.php --markup had no limit beside the count

    • markup.limit is 0, so the scan fails on the next hardcoded class, style or tag
  • the screenshot rig captured a degraded set without credentials

    • --before and --after refuse to start without SLAED_UI_USER and SLAED_UI_PASS,
because a run without them skips every state that needs a session
  1. Automation (tools/hooks/pre-commit, package.json):
  2. the pre-commit hook runs the audit, the markup scan and the theme, template and parser tests on every commit carrying a file able to move a count

    • about twenty seconds, and nothing at all on a documentation commit
    • enable once per clone with npm run ui:hooks; SLAED_SKIP_GATES=1 skips one
    • when a commit touches theme CSS or a canon template it prints the two visual
commands, because no count can see a moved pixel
  • npm run ui:before and npm run ui:after are the visual gate

    • ui:after empties the page and template caches, compares against the capture
`ui:before` took, regenerates the contrast registry when a base.css moved, reads
the counts and prints one verdict
  • verified in both directions: no change gives PASS, a padding shift on a class the
page really carries gives eight DIFFs and FAIL
  1. The screenshot baseline is no longer committed (tools/ui-baseline, .gitignore):
  2. measured: a check against the committed set went red hours after capture on a tree whose frontend was byte-identical - 100% size changed at sm, md and lg on the front page and the forum list, 1.6% to 3.9% even at xl

  3. the page height follows the content of the stand, and a mask cannot save a box whose height is what moves, so the reference is captured per change into a temp directory

  4. 168 images and about 80 MB leave the working tree; the path is gitignored so a stray capture cannot put them back

  5. A scratch theme stopped leaking its compiled templates (tests/Support/theme_scratch.php):
  6. deleteScratchTheme() removed templates/scratch-* and left storage/cache/templates/scratch-* behind, so every phpunit run and every --newtheme leaked about half a megabyte under a name nothing can read again

  7. Documentation (docs/, root):
  8. docs/THEME-ETALON-2026.md is deleted; its contract, its gate table and its measured findings move into docs/TEMPLATES.md as three reference sections, without the batch narrative and without any superseded figure

  9. docs/PARSER.md records that the parser owns no markup and needs a global $tpl
  10. docs/ARCHITECTURE.md records that $tpl is created before $prs because the parser depends on it, not by coincidence

  11. docs/EDITORS.md records that every driver mounts through fragments/editor-mount.html
  12. CONTRIBUTING.md, UPGRADING.md and docs/TEMPLATES.md stop describing HTML leaving PHP as work in progress; it is done and a gate holds it

  13. five files that pointed at the deleted plan now point at docs/TEMPLATES.md

Benefits:

  • the cheap gates need nothing remembered: they run on commit or refuse it
  • a gate whose input is missing now fails instead of reporting a clean zero
  • the working tree drops from about 145 MB to 65 MB

Technical notes:

  • .git is unchanged in size: the baseline blobs stay in history and any image can be recovered with git show b115eaa0:tools/ui-baseline/<name>.png

  • the hook needs core.hooksPath pointed at tools/hooks, which npm run ui:hooks does
  • the full 168-image walk has not been run under the new --before/--after flags; the cycle was verified with --only=voting, and the contrast branch of ui:after has not executed because no palette moved in that test

  • no breaking change: 915 tests pass, phpstan is clean, every ratcheted count holds
Refactor: the last markup leaves PHP, and a panel painting with names no theme has is found
Автор: Eduard Laas | Дата: 10:03 21.08.2026

A theme cannot restyle what PHP hardcodes, so the closing batch of the etalon plan moves every class attribute, inline style and HTML tag out of PHP and into the theme layer. The scan that measures it falls from 143 occurrences in 21 files to zero and gains a limit, so the next one fails the build instead of printing a number.

Core changes:

  1. Extended statistics panel (admin/modules/statistic.php):
  2. The inline <style> block it printed painted with --line, --bg, --soft, --muted, --blue2, --green, --purple and --orange

    • None of those eight names exists in either theme, so every border, background,
shadow and accent in the panel resolved to nothing and it had never been styled
  • No count could see it: the audit reads CSS files and this was a string in PHP
  • The markup is partials/statistic-extended.html and the CSS is 24 rules in theme.css

    • Every value lands on a ladder; the four accents are primary, success, warning
and accent; five component tokens are declared under statx
  • These eight images are the only ones of 168 that moved, and they moved on purpose
  1. Monitor traffic chart (admin/modules/monitor.php):
  2. The 900x285 SVG becomes partials/monitor-chart.html taking four series, six hover slots and two axis lists

  3. Four attribute kinds are dropped as dead

    • stroke and filter on lines and areas are SVG presentation attributes, which lose
to any CSS rule, and .sl-chart-line-up { stroke: ... } had been winning already
  • .sl-chart-tip .muted set fill: var(--sl-text), which .sl-chart-tip text two rules
above already set, so the rule and the class are both gone
  1. Markdown and BBCode renderer (core/classes/parser.php):
  2. 55 tags, 7 inline styles and 4 classes move into four new fragments plus the existing list, list-item, code-highlight, blockquote, div and parser-image

  3. A fragment can own a regular expression's replacement string once it renders the placeholder

    • parser-inline.html rendered with content_html set to the literal $1 gives the
template a whole element and PHP a replacement string
  • A pair that wraps text the parser has not finished reading renders the same whole
element with \x01 as its content and is split there, so a theme never spells half
a tag
  • The blockquote and attachment fallbacks are gone: the parser renders through the theme or not at all

  • One decision, one address, across eighteen smaller files:
  • class strings inside form_attr become semantic flags in form-add.html and form-wrap.html

  • style="display:none" becomes the hidden attribute, which the reset gives !important so nothing a component sets can put back what the page took off screen; the tab script writes .hidden instead of style.display

  • Eight sites that joined text with a tag now call getTplLines() over fragments/lines.html
  • add_voting() built class="sl-field sl-form-control" into select_attr, where select.html had already written class="sl-select..."; an HTML parser keeps the first attribute of a name, so the string had never reached a pixel and is deleted with its parameter

  • The captcha directory guard copies storage/index.html instead of building a document
  • Gates (tools/ui-contract.php, tests/Unit/ParserFixturesTest.php):
  • markup.limit is 0, so php tools/ui-audit.php --markup exits non-zero on the next hardcoded class, style or tag

  • ParserFixturesTest asserts the exact HTML of 86 cases and ran with no engine, so every expected string was what PHP concatenated; it builds a Template now, because the bytes it asserts are a theme's answer

  • tools/ui-contrast.json is regenerated beside the manifest that feeds it: 438 pairs, 118 admin and 320 lite, contrast at zero in both

Benefits:

  • A theme can restyle every element the CMS emits, which is the goal of the plan expressed as a measurement: markup 0, and every count either theme controls at zero

  • The statistics panel is readable for the first time, in both light and dark
  • Three gates that were reporting stale answers now report current ones

Technical notes:

  • Six fragments are byte-identical in both themes: lines, editor-mount, parser-block, parser-inline, parser-link, parser-table; two partials are the panel's alone

  • The parser now hard-depends on the theme layer; there is no plain-HTML fallback left
  • The parser disk cache already carries getTheme() and the class mtime in its key, so theme-owned markup cannot be served across themes or across this change

  • Masking an element for the screenshot gate removes it from the contrast gate, because the collector skips visibility:hidden and display:none; the registry shrank from 460 pairs to 438 for that reason and not from a regression, which the retired span.sl-but.sl-hidden still sitting in the committed file independently proves

  • No breaking change: 86 byte-exact parser fixtures pass unchanged, and 160 of 168 baseline images are identical to the pristine tree

Feature: the API freezes, and the last of canon answers for itself
Автор: Eduard Laas | Дата: 01:24 21.08.2026

Batch 8 of docs/THEME-ETALON-2026.md closes the remainder of canon and freezes the theme API. Every shared name the two etalons spell differently is now either one file or an entry with a written reason, and the freeze is held by a gate rather than by a sentence: the baseline carries a roster of the 297 and 404 names each API block declares, the audit compares it every run and --store refuses to write while a name has gone missing.

Core changes:

  1. The 28 same-named templates of canon (tools/ui-contract.php, tools/ui-audit.php):
  2. --cross reads a shared template the way it already read a shared selector

    • templates carries a written reason per divergence, canon names the two
directories the demand applies to, and unanswered ones join the global `cross`
  • layouts and pages are reported and demand nothing: the page shells differ by nature
  • two of the 28 were one contract with two spellings and are now one file

    • radio.html: admin's is lite's plus four optional keys no lite caller passes
    • changelog-body.html: the admin h3 skipped a level under the h1 of module-head,
so both are h2 and the admin rule moved with it at the same --sl-font-h4
  • the other 26 are two contracts under one name, which the key sets show rather than the line counts: link maps twelve names in the panel against thirty-three on the site with not one in both, inline-badge fourteen against twenty, span one against 22

  • One name per intent for hiding and dimming (both themes, core/user.php, modules/forum/index.php):

  • .sl-hidden meant display: none in admin and an opacity step in lite

    • read by producer rather than by class: six lite call sites mean gone, four mean
closed but still readable - a comment awaiting approval, an unpublished forum
post, an opened message, an action the reader may not take
  • those four take .sl-dimmed, which both themes already carried
  • .sl-none and .sl-hidden were two spellings of display: none, so the name that says the role keeps the intent and .sl-none is retired from both themes

    • the admin is_hidden branch of span.html had no producer at all, and the
!important that served it goes with it
  1. The focus ring of a link (both base.css):
  2. a { outline: none } was in both themes, not one: in admin it sits in the group input, textarea, select, a, which is why the tool reported only lite

  3. neither theme drew a:focus-visible in its place, so both take the ring the rest of each theme already draws, spelled identically

  4. The !important flags, read once (both theme.css):
  5. three in lite were fighting nothing: a hover rule that already outscored its target, an id selector nothing in the file can reach, and a dead rule whose class no template or script writes

  6. every one that remains carries the reason beside it - the glyph face beating an element font, the editor engine writing its height inline, a print rule reaching past the inline display the tab script writes

  7. The HTTP half of the theme-creation gate (tools/ui-shots.mjs, tests/Support/theme_scratch.php, tests/Support/theme_probe.php):

  8. node tools/ui-shots.mjs --newtheme builds a scratch copy of an etalon, points the account the rig signs in as at it, renders every frontend page of the manifest and restores the column and removes the tree in finally

  9. both halves build that copy through one lifecycle, theme_scratch.php, so a lifecycle spelled twice cannot drift into two

  10. the three site logs are read before and after the walk: a theme that renders a page and writes a notice behind it has still failed

  11. The freeze (tools/ui-contract.php, tools/ui-audit.php, tests/Unit/ThemeContractTest.php):

  12. frozen is true, and the roster under api in the baseline is what holds it
  13. an addition wrongs no copy; a rename in place is refused by the run, by --store and by phpunit alike

  14. The rig learns two pages it could not hold still (tools/ui-shots.json):
  15. admin-monitor grew 6954 to 7092 to 7557 pixels in the seven seconds after load: two panels arrive on load and are then replaced out of band by a four-second poller, and what moved was their height, so masking could never have held them

  16. the bar of the statistic page was masked and the count and per cent printed beside it were not, so the page flapped around the threshold on a walk long enough for the rig's own visits to move the figures

Benefits:

  • every shared name across the two etalons is now answered for, by one file or by one written reason, which is what hundreds of copies will inherit

  • one name for gone and one for dimmed, in both themes, with no name left holding two intents at the moment the API is frozen

  • a link that is focused says so on both sides of the CMS
  • creating a theme is checked as a file and as a served page, through one lifecycle
  • important falls 23 to 22 in admin and 17 to 13 in lite, and what is left says why

Technical notes:

  • --sl-font-hero reads 38px: the top step of the font-size ladder holds a different value, with the step count and the role names unchanged

  • probe_boot.php answers 404 and exits unless PHP_SAPI is cli. The probes are served by a stand, register_argc_argv turns a query string into $argv, and theme_probe.php stopped being read-only in this commit

  • tools/ui-baseline is re-captured whole and checked against itself: 168 of 168 states match and every noise floor sits below 0.2%

  • no breaking change to any runtime contract: the template keys that moved are is_hidden to is_dimmed at four call sites, all inside this repository

Вчера (20.08.2026)
Refactor: the two themes start answering to each other, and three gates learn what they were counting
Автор: Eduard Laas | Дата: 22:36 20.08.2026

Batch 8 of docs/THEME-ETALON-2026.md. Canon reconciliation is what makes the two shipped themes etalons rather than two codebases with matching names, and the checks that were meant to drive it were adding unlike populations together: one figure of 424 duplicates and one of 135 divergences is answered by grinding rather than by thinking. Split into what they actually measure, they left 50 duplicate groups and 46 divergences - sets small enough to decide one by one.

Core changes:

  1. Cross-theme divergence becomes a gate (tools/ui-audit.php, tools/ui-contract.php):
  2. checkCrossTheme() reads a shared selector two ways

    • same property set, different values: legal, one canon carrying many skins
    • different property sets: needs a written reason under divergent
  3. 135 divergences are now 117 = 71 in value alone and 46 in structure, all explained
  4. the element reset was settled structurally instead of registered: h1..h5, hr, ol, fieldset, a:hover and the two check inputs hold one property set per theme

  5. The duplicate check stops counting need as debt (tools/ui-audit.php):
  6. a body of one declaration is need, the way display: flex under 122 containers is
  7. a body met in two files cannot be merged: CSS has no cross-file selector list
  8. of the 50 groups left, 7 merged, 1 redundant rule deleted, 34 given a reason
  9. dup 178 and 246 -> 0 and 0
  10. A theme is created and checked by a test (tests/Unit/ThemeCreationTest.php):
  11. copies an etalon, repaints only the API block, asks the whole contract of the copy
  12. contrast is measured on the real pair registry put through the same repaint
  13. checkThemeAssets() answered by tests/Support/theme_probe.php on the live core
  14. the skeleton is checked against both gates that demand it, never against itself
  15. The contrast walk composites instead of passing through (tools/ui-shots.mjs):
  16. every sheer layer is composited back down onto the ground that really paints
  17. background-image is walked layer by layer, back to front, not stop by stop
  18. it found the admin footer copyright at 4.26:1 under its own decorative hatch; the hatch alpha is now what the ratio allows and the pair reads 4.60:1

  19. registry 442 -> 460 pairs, both halves at zero below AA
  20. Dark is guarded by pixels (tools/ui-shots.json, tools/ui-baseline):
  21. modes gains dark: 168 committed images, every noise floor under the threshold
  22. --out= sends a capture outside the repository, so a batch can compare its own two captures instead of a committed baseline the stand's own data has moved under

  23. the site counters the profile and the statistic page print are masked
  24. The two names nothing wrote are gone (plugins/system/slaed.js, both themes):
  25. placeFloat() writes --sl-d-float-top and --sl-d-float-left; the theme owns the rule
  26. six inline layout properties and a hardcoded z-index leave the script
  27. unmet 2 -> 0 in both themes
  28. Three zoos in admin take the shape lite had already settled (templates/admin):
  29. the alert reads one --sl-alert-tint and its glyph the base tone; the last raw hex goes
  30. the loading dots read the API block and the tone directly
  31. the switch spells its four aliased tokens and draws the theme's one focus ring
  32. scoped 84 -> 54, --sl-danger-subtle deleted, --sl-knob-dur folded onto the ladder
  33. error.html on the settled names, and four spellings of one intent closed:
  34. 60 tokens declared and 60 read, closed on itself
  35. ::before/::after at 57 sites, "bootstrap-icons" at seven, --sl-radius-circle at 28

Benefits:

  • every count either theme controls reads zero, and cross reads zero beside them
  • a new theme is now provably one copy and one repaint, asserted rather than claimed
  • the dark half of both themes is guarded by images and not only by ratios
  • a contrast failure nobody could see is fixed, and the walk that finds its kind is honest

Technical notes:

  • ui-audit gains isClassUsed(), whose boundary is the name alphabet: a class emitted after a template tag was invisible to it, which read as five dead classes in lite

  • nine classes really were dead and are deleted, with --sl-move-height beside them
  • admin ul keeps its longhands on purpose: a shorthand resets every longhand it omits, which flattened 82 nested list marks when it was written as one

  • ten lite templates carried CRLF in the working tree, which .gitattributes normalises on the way into the index: four template divergences the tool reported were local drift and never in the repository, which is why this commit shows none of them

  • fixtures cover every classifier change: classuse.html, dup-need.css
  • no theme file below the marker names a mode; frozen stays false while canon is open
Docs: the two questions batch 7 left about the rig are written down where they will be found
Автор: Eduard Laas | Дата: 18:11 20.08.2026

Both are about what the gates can see rather than about either theme, and neither blocks the freeze. They are recorded in the risk that names them, in the description of the walk that would change, and as a step of batch 8 that has to answer them.

Core changes:

  1. The dark-mode risk carries what batch 7 measured (docs/THEME-ETALON-2026.md):
  2. Two dark breaks shipped green through every gate and were found by looking

    • The headline over the photo band flipped from white to near-black, because the band
is a photograph with no dark variant while --sl-on-solid turns over
  • The footer wordmark vanished when the band under it inverted, because the wordmark
is an image with white baked into it
  • Both pass AA in both modes, so no contrast pair could report either
  • Until modes gains dark, a batch that touches colour opens the pages in dark, looks at them, and says in its report that it did

  • The walk's sheer-fill reading is named as a half answer (docs/THEME-ETALON-2026.md):
  • Passing through a fill under 0.5 alpha removed exactly two false readings in batch 7
  • It also leaves the honest ones unmeasured: white text on a twenty per cent veil over a photograph now reports nothing rather than something wrong

  • Compositing is the whole answer, about twenty lines in getContrastPairs(), and needs both halves of the registry re-generated in one run

  • Batch 8 gains the step that decides them (docs/THEME-ETALON-2026.md):
  • The cost of each is stated: 168 images and about 72 MB against the only gate that would have caught either break; twenty lines against a registry that may surface new pairs

Benefits:

  • A question asked once is not asked again from scratch, and its cost is already priced
  • The next colour batch knows it owes a look in dark until the manifest covers it

Technical notes:

  • Documentation only; no code, no theme and no gate changes
Feature: the second etalon closes, and the site learns to turn its own lights
Автор: Eduard Laas | Дата: 17:59 20.08.2026

Batch 7 of docs/THEME-ETALON-2026.md. Lite's last 386 untokenised decisions reach a token, its whole palette gains a second half through light-dark(), and the frontend gains the colour-mode switch on the plumbing the panel already had. Both themes now report zero on every count they control, in both modes.

Core changes:

  1. Every decision left in lite reaches one address (templates/lite/assets/css/*.css):
  2. Size, animation, shadow, background, border and outline take a component token

    • A figure sizing a control, an icon or a page region has one name, as admin has
    • An animation carries its own dur and ease; a shadow is one whole value at one name
  3. Four laws replaced four zoos rather than renaming them

    • The tinted pill: six tone classes and eight state marks read one --sl-chip-bg
    • The focus ring: outline and box-shadow draw one --sl-ring-width in one --sl-ring-bg,
and a status ring mixes its own colour at --sl-ring-mix
  • The tile: --sl-ico-bg and --sl-ico-border read currentColor, and only the ratio is
hoisted where the tone is itself scoped
  • The arrow: every CSS triangle in the theme reads one --sl-arrow-width
  • The colour tail folded onto the roles it had been spelling by hand

    • --sl-color-brand and -banner onto --sl-primary-subtle, -strong onto --sl-primary-strong
    • --sl-color-text-ink onto --sl-text, -link and --sl-login-link-hover-color onto -strong
    • The eight --sl-changelog-* and the two service tones of the social icons onto roles
  • Lite carries both modes (templates/lite/assets/css/base.css):
  • color-scheme on :root, light-dark() through the palette, two data-theme selectors under the marker; no rule outside the API block names the mode

  • A tint step is a ground and nothing else: every mark that read one takes the base tone of its family, which turns over by itself

  • What a token cannot repaint stays fixed: the footer band carries the wordmark and the flag as images with white baked in, the page head carries a photograph, and the text standing on them reads --sl-band-text

  • Contrast fell 53 to 0 in light and dark together, each value derived from the luminance the ratio allows against the worst ground it really meets

  • The colour mode switch reaches the site (core/system.php, index.php, templates/lite):
  • getAdminModeSwitch() becomes getThemeModeSwitch($action) for the panel and the site alike, and one setThemeMode() writes the cookie for both handlers

  • index.php answers op=mode on the token scope the panel uses; lite's four document templates carry data-theme and the site header carries the toggle

  • A visitor who picked a mode is not cacheable: the page-cache key is the route alone, so a stored copy would hand the next visitor someone else's mode

  • Two measuring tools were wrong and are fixed (tools/ui-audit.php, tools/ui-shots.mjs):
  • getValueKind() could not read a shadow whose colour or length arrives through a token, so one name held two kinds across the themes for no visible reason; isShadowValue() reads it now, with tests/Fixtures/ui/shadow.css under it

  • The contrast walk read a mostly transparent fill as a solid surface and reported a failure against a colour nobody can see; it passes through to the ancestor that paints, at the threshold its own gradient rule already used

Benefits:

  • A new theme is one copied directory and one repainted API block, in two modes
  • Every visual decision in both shipped themes has exactly one address
  • The whole site meets AA in light and dark, measured on pairs that really meet on screen
  • dup falls 266 to 246 and scoped 103 to 92 as the folds land

Technical notes:

  • tools/ui-audit-baseline.json re-stored at the new lows; the ratchet holds them
  • tools/ui-contrast.json regenerated with credentials: 442 pairs, admin's 124 unchanged
  • 84 baseline images re-captured in one pass and verified stable on a second run
  • The mode cookie is written only through setCookies(); the toggle posts with a CSRF token that rides the dynamic-region contract on cacheable pages

  • Backward compatible: no schema, route or template contract changes
Refactor: every scale in lite lands on the ladder, and the type ladder grows the step a hero was standing on
Автор: Eduard Laas | Дата: 10:37 20.08.2026

Batch 6 of docs/THEME-ETALON-2026.md. Lite's untokenised visual decisions fall from 1209 to 386 and its bare numbers from 107 to zero: font size, line height, weight, tracking, opacity, radius and every rhythm step now reach their value through a token, and the last of the legacy scale names is retired.

Core changes:

  1. The scales of lite (templates/lite/assets/css/base.css, theme.css):
  2. 823 decisions snapped onto the shared ladders and named

    • font-weight, line-height, opacity and letter-spacing leave the bare check at zero
    • h1 to h5 take the same token per element as admin, so the heading ladder is
strictly descending at 24 / 20 / 18 / 16 / 14
  • --sl-space-xs..xl, --sl-radius-control, --sl-radius-card and the four
--sl-overlay-* are gone; the overlays are two --sl-scrim-* steps
  • the login geometry loses six names of four and five segments
  • every viewport breakpoint sits on 560 / 768 / 900 / 1200, and a query above a
step is spelled (width > step) so no pixel belongs to both branches
  • three placements written as a negative half-width margin become one left: calc(50% - n), which is the same pixel at one address

  • the thumbnail column of a list row and the padding of the login profile now say what they make room for instead of holding a figure

  • Two contract changes (tools/ui-contract.php):
  • --sl-font-hero at 48px above display: a slider headline and a dashboard number are the largest type a page carries, and folding them onto display would size a hero like an h1

  • --sl-space-9, -10 and -11 at 32 / 40 / 48: lite carries a second rhythm between the sections of a page that admin has not one atom of

  • Three figures the audit had been counting are not decisions (tools/ui-audit.php):
  • a descriptor block holds no rule, because var() is invalid inside @font-face
  • the middle of a clamp() is the rate between two bounds that are decisions themselves, and no ladder step in the contract can carry it

  • a percentage gap is measured against the width of the container, not against the rhythm of the page

  • each is covered by a fixture in tests/Fixtures/ui/ and a test that reads the answer off the fixture

Benefits:

  • one address per visual decision across every scale lite owns
  • the two etalons now share the same ladders, values included, which is what batch 8 reconciles the rest against

  • the audit stops reporting a figure that could never have been tokenised at all

Technical notes:

  • the top menu wrapped onto a second row at 900px when its gutter took the upward tie from 15 to 16; it takes the step below at 12 instead. A geometry probe over the screenshot manifest caught it, and no count could see it

  • tools/ui-baseline is re-captured for the frontend states; profile and private render in lite and need a run with SLAED_UI_USER and SLAED_UI_PASS to follow

  • the admin theme is untouched and every count it holds is still zero
Feature: lite gains the three axes it had no name for, and every motion in it lands on the ladder
Автор: Eduard Laas | Дата: 00:12 20.08.2026

Batch 5 of the theme etalon gives lite the motion, layer and gradient axes admin already carried. Not one transition, one layer or one gradient in the theme had an address of its own, so a theme author could not restyle any of them without forking rules. All three now read the same names admin does, the untokenised metric falls from 1396 to 1209 and the grammar violations from 99 to 44.

Core changes:

  1. Motion (templates/lite/assets/css/base.css, theme.css):
  2. 81 duration parts over 13 spellings snap onto --sl-time-fast, --sl-time-base and --sl-time-slow; 32 were already on a step, 31 move by 20ms or less and 18 move further

    • the two longest, the poll bar filling at 0.8s and the profile ring drawing
itself at 0.9s, take --sl-time-slow with the rest: an exception granted by
argument is how a zoo grows back, so the ladder has none in this theme
  • admin still spells its own ring --sl-knob-dur: 1s, and that divergence is
batch 8's to settle in the direction lite has taken
  • of the five curve spellings ease stays, linear is allowlisted, cubic-bezier(0.2, 0.8, 0.2, 1) folds onto --sl-ease-in-out and cubic-bezier(0.2, 0.7, 0.3, 1) onto --sl-ease-out

  • the overshoot cubic-bezier(0.3, 1.6, 0.5, 1) becomes --sl-vote-ease, because a curve leaving the 0-1 range is a different kind of motion and no ladder holds it

  • the speed dial's five hand-written delays become calc(var(--sl-dial-dur) * n) on a 0.05s step, which is the spelling admin already used

  • Layers (templates/lite/assets/css/base.css, theme.css):
  • twelve values over 26 sites become seven roles: --sl-z-base, -raised, -overlay, -dropdown, -popover, -modal and -toast

    • 0, 1, 30, 3000 and the image popup at 1000 were already a role's own value
    • 2 and 5 both mean "painted over the box it sits in" and merge into -overlay;
the only place the two sets meet is the user card, where the tool chip sits
at top: -38px and the online badge at top: 20px
  • 4, the session panel, goes up to -dropdown to stay above that badge; it
clears the tool chip by 12px, which is measured and not argued
  • four orders change and each is recorded in the plan: the header login form and the top-menu submenu fall to -dropdown and keep their old order by tie and document position, the tooltip's anchored panel and the image popup rise past the submenu, and the two fixed edge tabs fall to -popover where the footer's document position still resolves the tie their way

  • --sl-z-sticky stays undeclared, because nothing in lite reads it and a step nothing reads is a dead token

  • Gradients (templates/lite/assets/css/base.css, theme.css):
  • --sl-line-gradient becomes --sl-grad-line and --sl-bg-hover-gloss becomes --sl-grad-gloss, which drops the last name stacking a state on an axis

  • the five --sl-progress-fill-* triples fold into the categorical --sl-progress-1..5 and the five --sl-grad-progress-*, and the track into --sl-progress-bg, -border, -radius and -height; the fill height is now calc(var(--sl-progress-height) - 1px - 1px) and the flat fallback under each gradient, which no browser in the baseline ever painted, is gone

  • the three gradients one component owns take that component's address: --sl-changelog-date-bg, --sl-feedback-bg and --sl-idea-bg

  • the button bevel is composed: --sl-btn-bg holds the face, --sl-btn-text the label and one --sl-btn-shadow both edges and the lift under them, which retires the two scoped internals .sl-but had been setting on itself

  • --sl-grad-info-start/-end were a colour under a gradient prefix and one of them also painted the search button flat, so they take colour names --sl-info-subtle and --sl-info-muted; --sl-grad-success-start proved to be --sl-success-subtle spelled twice and collapses onto it, its partner becoming --sl-success-muted

  • The contract and the baseline (tools/ui-contract.php, tools/ui-audit-baseline.json):

  • the components changelog-date, feedback and idea are declared
  • the baseline is re-stored: count 1209, bare 107, names 44, scoped 107, tokens 213, with admin unchanged at zero

  • The plan (docs/THEME-ETALON-2026.md):
  • the batch 5 section is deleted rather than marked done, and the status moves to batch 6

  • every figure it touched is re-derived from the tool: the metric tables, the baseline table, the transition and layer ladder rows, the cross-theme selector count and the grammar violation split

  • the merge reasoning for the layers and the three colour names minted here are written down, because the batches after this one inherit them and no counter would recover them

Benefits:

  • a theme author can now restyle every transition, every stacking layer and every gradient in lite by editing the API block, which is what the etalon promises

  • the layer roles replace fourteen numbers picked to win one argument each, so a new overlay has a role to take instead of a number to outbid

  • one duration ladder across both themes means a motion change is one edit, not a search for eight spellings of the same fifth of a second

Technical notes:

  • durations move on purpose: 0.5s and 0.6s fall to 0.35s at seven fade and zoom sites, and the poll bar and profile ring fall to it from 0.8s and 0.9s

  • no screenshot sees a duration, so motion is verified by reading computed transition-duration, -delay and -timing-function off the rendered page: 843 transitioning elements over five lite pages, all on the ladder

  • layers are verified the same way, by resolving every computed z-index on those pages against the named roles

  • all 42 tokens this batch added or renamed resolve on the live page to the values they held before it

  • lite screenshots are identical to the stored baseline; the admin diffs the rig reports are the stand's own data, and admin renders from files this batch does not touch

  • the button label highlight is now spelled with color-mix() at --sl-btn-mix: 85%, which is the same paint as the rgba() it replaces

Эта неделя (19.08.2026)
Feature: the admin theme reaches zero and learns the second half of every colour it owns
Автор: Eduard Laas | Дата: 21:15 19.08.2026

Batch 4 of the theme etalon closes admin: every visual decision in its three CSS files now has exactly one address in the API block, and every colour in that block carries both modes in one declaration, so a page follows the operating system or the button in its toolbar without a second rule anywhere.

Core changes:

  1. The remaining 302 decisions (templates/admin/assets/css/base.css, theme.css, editors/toastui/skin.css):

  2. the API block grows from 135 names to 291, from --sl-form-width to the eleven --sl-col-*-width that are the track list of a fixed list table

    • a size that names a control, a panel or a column is a token; a figure that
only repeats a neighbour is a calc of it, which is why the second editor on a
form says calc(var(--sl-editor-height) * 2) and the coarse-pointer button
says calc(var(--sl-modal-act-width) + var(--sl-space-3))
  • animation duration and curve are the character of one element and become
--sl-loading-dur, --sl-drift-dur, --sl-row-dur, --sl-led-dur, --sl-alert-dur,
--sl-skel-dur, --sl-flash-dur, --sl-modal-dur and --sl-modal-ease
  • the three alert durations collapse to one, and the modal close takes the
transition ladder's fast step
  • the nine shadows and ring colours that fitted no role are settled: --sl-wrap-shadow, --sl-bar-shadow, --sl-check-ring, --sl-check-shadow, --sl-switch-ring, and one law for every focus ring -- --sl-ring-width, --sl-ring-gap, --sl-ring-mix and --sl-ring-bg

  • the two colour tails fold: --sl-color-primary-hover-soft onto --sl-primary-strong, --sl-color-text-soft onto --sl-text

  • skin.css is migrated once and copied to lite byte for byte, and lite's API block gains exactly the 41 names the copy reads

  • Light and dark (templates/admin/assets/css/base.css):
  • every colour token carries light-dark(); color-scheme is declared and the two :root[data-theme] selectors switch it

  • a shadow or a gradient stop keeps its own colour and puts the mode inside it, so the light values the screenshot baseline holds are untouched

  • --sl-on-solid turns over with the mode: in light the solid fill is the dark one and its text is white, in dark the ramp reverses and its text goes dark

  • the thirteen rules that painted text on a solid fill with var(--sl-bg) now say the role they mean; in light the two names are the same white, and only inversion tells them apart

  • The mode plumbing (core/system.php, core/security.php, core/admin.php, admin/index.php, templates/admin/layouts/*.html):

  • getThemeMode() reads the mode cookie through getCookies() and answers auto to everything it does not recognise

  • $adminvars, $sitevars and the message page core/security.php builds itself all carry the key; a render path missing it would flash the wrong mode

  • getAdminModeSwitch() renders one POST button with a CSRF token, and op=mode stores the choice through setCookies(); setCookies() marks the cookie httponly, so a script toggle could never have worked

  • both admin layouts write data-theme="{{ mode }}" on <html>
  • The contrast registry (tools/ui-shots.mjs, tools/ui-shots.json, tools/ui-contrast.json):

  • the walk drives light and dark through the cookie the CMS really reads, and files a pair once when its two colours are the same in both modes

  • a pair whose foreground equals its background is dropped: it is an element between the text and the ground the walk could not follow, not a reading

  • admin's 124 pairs replace its old 53; lite's 150 are left for its own batch
  • the seventeen failures found are closed by five darker light values and one role, and admin is at zero below AA in both modes

  • The audit tool (tools/ui-audit.php, tools/ui-contract.php):
  • color-mix() and light-dark() are walked instead of counted whole, and the mix ratio is a decision of its own kind

  • getValueKind() reads both as colours; getRgbValues() resolves light-dark() to the half asked for, or every categorical check silently measures nothing

  • the prop list gains min-width, max-width, min-height, max-height, ease and mix; the colour axis gains the tint role; vertical-align, contain-intrinsic-size and linear join the allowlist with their reasons

Benefits:

  • a new admin-like theme is made by editing one block: 291 tokens, no literal below the marker, and both modes in the same declarations

  • dark mode costs no second stylesheet and cannot drift from light, because the two values sit in one declaration and no rule outside the API block names them

  • every focus ring, tint and scrim in the theme has one address instead of nine near-identical rgba() values

Technical notes:

  • browser baseline rises to Chrome 123 / Safari 17.5 / Firefox 120 for light-dark(), above the :has() baseline already assumed

  • five light values move for contrast: --sl-primary-strong, --sl-success, --sl-success-strong, --sl-text-muted and --sl-danger are each darkened to the luminance the ratio 4.5 allows against the worst ground they really meet

  • the admin toolbar row wraps instead of squeezing, so the menu, the flags and the new toggle all keep their labels

  • the sidebar gains the 4px row gap and 20px line box its scoped set had asked for and never declared

  • screenshot baselines re-captured; only admin states moved, and the frontend is refreshed against the stand as it is today

  • lite is untouched except for the skin copy and the names it declares for it

Всего: 1136 на 114 страницах по 10 на каждой странице

1 2 3 4 5 6 7 8 9 10 114
Хотите опробовать SLAED CMS в действии?
Идеи и предложения
Обратная связь
Подтверждение

Поделиться
QR-код