Commit Graph
1613 Commits
Author SHA1 Message Date
Nick Shirokov 7dc2e6e443 feat(form-validate): проверки необъявленного префикса и версии формата
Обе проверки — про ошибки, которые делает не платформа, а тот, кто пишет
XML руками. Обе вскрыты на наших же фикстурах, обе платформенно-фатальны:
файл не читается вовсе, а прежний валидатор говорил OK.

Check 13 — префикс в значении типа обязан резолвиться. `cfg:CatalogRef.X`
в <v8:Type> при незадекларированном xmlns:cfg даёт «Исключение XDTO при
чтении файла». Область видимости считается по узлу, а не по корню:
локальная xmlns на элементе законна и в типовых встречается (d4p1, mxl).

Check 14 — версия формата формы против версии конфигурации. В пределах
одной выгрузки версия едина; форма из более новой выгрузки даёт
«Неизвестная версия формата N загружаемого файла».

Ложных срабатываний нет: корпус УТ/БП/ERP, 21 097 форм — те же 8 форм с
ошибками, что и до правки. Регресс 15/15 на обоих рантаймах, паритет
портов сверен построчно.
2026-08-12 18:59:55 +03:00
Nick Shirokov 1b40dc5b03 test(form-validate): фикстуры доведены до загружаемых конфигураций
verify-snapshots считает фикстуру готовой конфигурацией и грузит её в базу,
а фикстуры form-validate состояли из одного Form.xml — поэтому по этому
навыку верификация падала целиком и по факту не выполнялась никогда.

Что вскрылось при доведении, по нарастающей:

1. Нет Configuration.xml и объекта — «Файл объекта не существует».
   Дописаны cf-init + meta-compile + form-add, рукописный Form.xml сохранён.
2. Префикс `cfg:` в значении <v8:Type> при НЕобъявленном xmlns:cfg —
   «Исключение XDTO произошло при чтении файла». Тот же класс, что ишью #38,
   только в наших собственных тестовых данных: четыре фикстуры платформа не
   читала вовсе, а кейсы на них считались зелёными.
3. Привязка Объект.Наименование у обработки — у неё нет такого стандартного
   реквизита. Добавлен обычный реквизит.
4. Динамический список без источника — «Неверный путь к данным: Список.Ссылка».
   Добавлен справочник-источник и MainTable.

Кейс с формой расширения помечен skipPlatformVerify: верификатор грузит
каталог кейса как конфигурацию, фрагмент расширения так не проверить. Ключ
существовал в verify-snapshots, но не был описан — добавлен в README.

form-validate: 14/14 на обоих рантаймах, платформенная верификация 13/14
(один пропуск с причиной, было 10/14 с четырьмя падениями).
2026-08-12 18:36:09 +03:00
Nick Shirokov e1d5d1f903 fix(form-validate): команда без Action — предупреждение, а не ошибка
Проверка объявляла ошибкой любую команду формы без <Action>. Корпусный
прогон (УТ 8.3.27, БП 8.3.27, ERP 8.3.24 — 21 097 форм) показал 406 таких
команд на 275 формах, и все они произведены самой платформой: формат это
допускает, конфигурации грузятся.

Приём типовых: действие назначается в рантайме, в ПриСозданииНаСервере —
`Команда.Действие = "Подключаемый_" + Имя + "Локализация"`. Назначать может
и чужой модуль (переопределяемый слой, подключаемые команды), поэтому по
одному Form.xml вердикт не вынести — отсюда предупреждение, а не ошибка, и
никакого подглядывания в соседний модуль.

Корпус после правки: форм с ошибками 283 → 8, новых срабатываний нет.
Оставшиеся 8 — четыре других класса, разбираются отдельно.

Кейс с фикстурой: команда без Action на кнопке → предупреждение, exit 0.
2026-08-12 17:24:34 +03:00
Nick Shirokov c9b64f3a0d fix(form-compile): внятный отказ на группу additionalColumns без ключа columns
PS-порт падал с «Не удается индексировать в массив NULL»: @($null).Count в
PowerShell равен единице, поэтому ветка самозакрывающегося тега была
недостижима, и код шёл эмитить несуществующую колонку. PY на том же входе
молча писал пустую группу — портируемого поведения не было вовсе.

Разведены два случая, которые до сих пор путались:
- `"columns": []` — явно пустая группа, законная форма (платформа так
  пишет таблицу без доп. колонок). Работает как работала, self-closing;
- ключа `columns` нет вовсе — недосказанность автора: «доп. колонки есть»,
  а какие, не сказано. Теперь отказ с указанием, что делать.

Валидатор такое по-прежнему пропускает: в выгрузке платформы пустая группа
встречается, запрет на авторинг не равен запрету на существование.

Заодно кейс cfe-borrow/form-main-attr-columns вернулся к форме эталона
Конфигуратора — таблица без колонок описана явно пустым списком, как в
выгрузке, а не обходным манёвром вокруг падения.
2026-08-12 17:01:10 +03:00
Nick Shirokov a6cb656a2d fix(form-validate): паритет портов по счётчику проверок
На одной и той же форме PS сообщал 12 проверок, PY — 9. Расходился не
вердикт, а учёт: три проверки (ссылки команд, обработчики событий,
действия команд) в PS отчитываются строкой «none», когда проверять нечего,
а в PY эта ветка отсутствовала — и проверка не попадала в счётчик.

Добавлены недостающие ветки. Выборка из 40 форм корпуса (УТ, БП, ERP):
число строк отчёта совпадает у портов на всех сорока.
2026-08-12 16:55:28 +03:00
Nick Shirokov 53ee51a37a fix(cfe-borrow): не переносить NumberPeriodicity в оболочку заимствованного документа
Платформа считает это свойство модификацией настроек нумерации и тогда
требует объявить ещё и <Numerator/>: /UpdateDBCfg падает с «Для
заимствованного документа, настройки нумерации которого модифицированы,
отключать контролируемость свойства "Нумератор" недопустимо». Загрузка при
этом проходит — ошибка вылезает только на обновлении конфигурации БД,
поэтому ручной E2E её не видел, а verify-snapshots поймал.

Эталон Конфигуратора переносит NumberType/NumberLength/NumberAllowedLength
и не переносит NumberPeriodicity — приводим к тому же. Свойство попало в
список ещё при появлении -BorrowMainAttribute, набор тогда был угадан, а
не выверен по эталону.

Заодно две мои фикстуры доведены до платформенной валидности:
form-choice-param-links задавала в ИСХОДНОЙ форме ссылку на несуществующий
реквизит — такая конфигурация не грузится сама, проверять на ней вырезание
висячей связи нельзя; gentypes-full не имела регистратора для регистра
бухгалтерии и задачи для бизнес-процесса.

verify-snapshots по cfe-borrow: 11 из 12 (container-types падал и раньше,
дефект его фикстуры). Регресс 12/12 на обоих рантаймах, все гарды зелёные.
2026-08-12 16:52:42 +03:00
Nick Shirokov 9650085fff docs(cfe-init): каталог расширения в примерах повторяет его имя
В примерах имя расширения задано явно (-Name Расш1), а рядом стоял
плейсхолдер -OutputDir src\cfe\extname — связь между именем и каталогом
из такого примера не читается. Теперь каталог называется по имени, и
конвенция «подкаталог = имя расширения» видна из самих примеров.

Базовая команда дополнена -OutputDir и -ConfigPath: без них расширение
уезжает в src (тот самый неоднозначный каталог), а совместимость и UUID
языка берутся по умолчанию вместо базовой конфигурации.
2026-08-12 16:29:16 +03:00
Nick Shirokov c0c37532a7 docs(cfe-*): единая конвенция путей в примерах и передача ConfigPath валидатору
В группе cfe-* примеры расходились: cfe-patch-method уже использовал
src\cfe\<расширение> и src\cf, остальные три навыка — голый src и
абсолютный C:\cfsrc\erp. Голый src особенно вреден: в репозитории с
конфигурацией и расширением он неоднозначен, и модель подставляет в
-ExtensionPath конфигурацию.

Везде одна пара: src\cfe\extname и src\cf.

Заодно по итогам появления -ConfigPath у cfe-validate: блоки «Верификация»
теперь показывают его передачу, а описание параметра в самом валидаторе
переписано с устройства проверки на повод её включить — с последствием
(расширение пройдёт валидацию и будет отвергнуто платформой) и с
алгоритмом поиска пути прямо на месте, без отсылки к соседнему навыку.

У cfe-init отмечено, что CompatibilityMode не нужен при заданном
ConfigPath, и что расширение стоит класть в отдельный подкаталог.
2026-08-12 16:24:57 +03:00
Nick Shirokov 9b1c3de642 feat(cfe-validate): полнота GeneratedType, ТЧ из AdditionalColumns, сверка путей с -ConfigPath
Три вещи, на которых платформа отвергала расширение, а валидатор молчал.

Check 9 — полнота набора GeneratedType у заимствованной оболочки: неполный
набор платформа не читает («отсутствует один или более типов объекта»).
Карта категорий взята из той же таблицы спецификации (§2.5) и заведена в
реестр check-type-maps.mjs, чтобы копия не разошлась с остальными.

Check 12 — <AdditionalColumns table="Объект.X"> при незаимствованной
табличной части. В отличие от соседних проверок блока, здесь сигнал точный
(имя из атрибута), а последствие жёсткое, поэтому ошибка, а не warning.

Check 14 — пути Объект.* заимствованных форм против конфигурации-источника,
по новому опциональному -ConfigPath. Без него проверка пропускается с явной
строкой в отчёте. Отличить живой путь от висячего иначе нельзя: Объект.Партнер
валиден и без заимствования (наследуется от базы), а Объект.Товары.Артикул не
разрешится нигде. Итоги колонок (Total<Колонка>) и стандартные реквизиты
пропускаются — иначе ложные срабатывания на типовых формах.

Проверено на пяти расширениях: наши (оба режима) и оба эталона Конфигуратора
проходят чисто, расширение с дефектом ловится. Регресс cfe-* 51/51 на обоих
рантаймах, гарды дрейфа зелёные.
2026-08-12 16:17:56 +03:00
Nick Shirokov 9a28fbfacb feat(form-validate): путь на необъявленный основной реквизит заимствованной формы
Форма, которую платформа отвергала с «Неверный путь к полю - Объект.Партнер»,
проходила валидацию с вердиктом OK. Check 5 такое не видит по двум причинам:
у формы с BaseForm он пропускает базовые элементы (id < 1000000), а привязка
внутри <ChoiceParameterLinks> лежит в <xr:DataPath> и в его список тегов не
входит вовсе.

Проверка 11d: если форма не объявляет основной реквизит, любой путь с корнем
«Объект» не разрешится — ошибка. Непрозрачные формы пути (1/0:uuid), которыми
как раз и заменяет такие ссылки Конфигуратор, ошибкой не считаются.

Две фикстуры: висячая ссылка ловится, та же форма в uuid-форме проходит.
Регресс 13/13 на обоих рантаймах.
2026-08-12 15:58:45 +03:00
Nick Shirokov 1bd0c7724e feat(cfe-borrow): ссылки параметров выбора по uuid реквизита
Заимствование формы без основного реквизита копировало
<ChoiceParameterLinks>/<xr:Link> как есть, с текстовым путём
«Объект.Партнер». В расширении такой путь не разрешается — платформа
отвергала загрузку: «Неверный путь к полю - Объект.Партнер». Привязка
лежит в <xr:DataPath> внутри xr:Link, и общий стриппинг её не видел.

Конфигуратор ссылку не выбрасывает, а переводит путь в непрозрачную форму
«1/0:<uuid реквизита объекта>» — связь остаётся рабочей. Делаем так же;
реквизит, которого в источнике нет, недоступен и по uuid — такую связь
вырезаем вместе с опустевшим контейнером. Путь односегментный во всём
корпусе УТ (285 из 285), глубже не бывает.

Результат совпал с эталоном Конфигуратора вплоть до uuid, E2E на UT_DEMO:
«Load completed successfully». Регресс 12/12 на обоих рантаймах.
2026-08-12 15:53:59 +03:00
Nick Shirokov 3fafa099b2 feat(cfe-borrow): перенос UseAlways/Columns основного реквизита формы
Заимствование формы с -BorrowMainAttribute синтезировало <Attribute
name="Объект"> с нуля — Type/MainAttribute/SavedData и всё. Секции
<UseAlways> и <Columns> исходной формы терялись, а вместе с ними и
дополнительные колонки табличных частей, объявленные прямо в форме:
платформа отвергала загрузку («Неверный путь к данным: Объект.Товары.Артикул»).

Три связанных следствия:
- секции переносятся из исходной формы (helper Get-MainAttributeExtraXml
  на оба порта, отступ в BaseForm — тем же приёмом, что у ChildItems);
- табличная часть, упомянутая только в <AdditionalColumns table="…">,
  теперь попадает в сбор путей и заимствуется — иначе «Колонки не могут
  быть добавлены к реквизиту»;
- типы колонок дозаимствуются через collect_reference_types, как это
  делает Конфигуратор (DefinedTypes/Артикул в эталоне).

Попутно снят PS↔PY-дрейф: lxml включал хвостовой пробельный узел в
tostring, из-за чего .py вставлял пустые строки, которых нет у .ps1
(with_tail=False в четырёх местах).

E2E на UT_DEMO (8.3.27, формат 2.20, форма заказа поставщику): режим с
основным реквизитом грузится «Load completed successfully» без ручных
правок. Регресс 11/11 на обоих рантаймах.
2026-08-12 15:49:34 +03:00
Nick Shirokov 902b643a3e feat(cfe-borrow): полный набор GeneratedType у заимствованных оболочек
Платформа отвергала заимствованный план видов характеристик: «отсутствует
один или более типов объекта ChartOfCharacteristicTypes». В карте
$script:generatedTypes не хватало категории Characteristic — а вместе с ней
ещё пяти категорий у пяти типов (планы счетов и видов расчёта, регистры
бухгалтерии и расчёта, бизнес-процессы).

Дефект был чистым дрейфом копий: карта живёт в трёх навыках, и в
meta-compile с meta-validate она верна. Чтобы расхождение больше не
копилось молча, каноническая таблица наборов GeneratedType заведена в
спецификации (§2.5), а check-type-maps.mjs получил виды gentypes/gencats и
сверяет по ней все три карты.

Регресс cfe-* 47/47 на обоих рантаймах, оба гарда дрейфа зелёные.
2026-08-12 15:38:52 +03:00
Nick ShirokovandClaude Opus 5 37108b15c4 test(skills): свести verify-snapshots с runner по разбору кейса
Верификатор читает тот же DSL, что и функциональный раннер, но своей
реализацией, и отставал на пять ключей: preRun[].cwd, inputFrom, cwd на уровне
кейса, раскрытие {workDir} в args_extra и маппинг from: outputPath. Кейсы,
опирающиеся на них, до платформы не доезжали вовсе — падали на подготовке
фикстуры, причём шаг preRun с относительным путём писал её в корень репозитория.

Незнакомый from теперь роняет кейс с внятным сообщением: молчаливый default
маскировал расхождение под дефект навыка (флаг уходил без значения).

Платформенная верификация: mxl-compile 28/36 -> 36/36, mxl-decompile 0/7 -> 7/7,
mxl-info 2/7 -> 7/7, mxl-validate 0/4 -> 4/4, skd-decompile 0/17 -> 17/17.

Оба ключа, которых не было в документации DSL, дописаны в README.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 14:40:18 +03:00
Nick ShirokovandClaude Opus 5 241a56a29f docs(mxl): актуализировать спецификации после серии находок на стенде
Спецификация XML — дописано то, что вскрыли контролируемые макеты и замеры
по корпусу:

- шрифт-ссылка на СИСТЕМНЫЙ шрифт: префикс sys в корне не объявлен, поэтому
  объявление xmlns дописывается прямо на узел. Плюс правило вывода kind
  из префикса и то, что неиспользуемый шрифт в палитру не попадает;
- новый раздел «Устройство палитр»: порядок документный и НЕ зависит от
  последовательности действий автора (проверено опытом с оформлением снизу
  вверх), формат по умолчанию последний, палитра дедуплицирована по содержимому;
- у текста ячейки ТРИ состояния: тега нет, тег с элементами, пустой <tl/>.
  Третье — 57% макетов корпуса;
- языковые настройки: набор языков не выводится из языков текста, description
  бывает самозакрывающимся, currentLanguage бывает отсутствующим и бывает
  указывающим на необъявленный язык.

Спецификация DSL — из ограничений убрано объявление языков макета: оно больше
не теряется. Добавлено пояснение, почему побайтовое совпадение достижимо не на
любом макете: в долго правленных макетах остаются следы прежних состояний,
которые из итогового документа не выводятся.

В инструкции навыка отражена только форма шрифта-ссылки — остальное из этой
серии либо уже там, либо для авторинга не нужно.

Примеры из справочника скомпилированы и проверены валидатором.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 12:53:10 +03:00
Nick ShirokovandClaude Opus 5 98bcf79b41 feat(mxl-compile,mxl-decompile): шрифт ссылкой на стиль или системный
Шрифт бывает не собственным описанием, а ссылкой: на элемент стиля конфигурации
(style:) или на системный шрифт (sys:). Своих атрибутов у такого шрифта нет, и мы
превращали его в пустую запись — faceName="" height="0". ps1 при сборке подставлял
туда Arial 10, то есть подменял данные молча.

В корпусе таких шрифтов 272 в 213 макетах из 10 924: StyleItem 209, WindowsFont 63.

Запись — та же, что у шрифта в описании формы: { "ref": "style:TextFont" }.
kind выводится из префикса, ключом быть не обязан.

Синтетический стенд показал деталь, которую по корпусу было не разглядеть: префикс
sys в корне документа не объявлен, поэтому платформа дописывает объявление xmlns
прямо на узел шрифта — тот же приём, что с цветами из web-палитры.

Порты после этого сошлись ПОЛНОСТЬЮ: на пилоте из 40 макетов совпадают и JSON
декомпиляторов, и собранный XML. Макет со шрифтами добавлен в побайтовую
регрессию, стенд 8 из 13.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 12:47:24 +03:00
Nick ShirokovandClaude Opus 5 0a8671a166 feat(mxl-compile,mxl-decompile): пустой тег текста ячейки
У текста ячейки три состояния, а выражались два: тега нет вовсе, тег с пустым
текстом на каждый язык — и третье, <tl/>, которое мы теряли. Это не редкость:
38 075 ячеек на 1200 макетов, встречается в 57% макетов корпуса.

Выражается пустым объектом: "text": {}. Не новый ключ и не новое понятие, а
вырожденный случай уже принятой записи «язык → текст» — языков нет вовсе.
В позиционной строке двусмысленности не создаёт: {} не несёт ключей ячейки,
значит по общему правилу читается как текст, а null там по-прежнему «пропустить
колонку».

В описании DSL записи нет намеренно. Для авторинга она бесполезна — визуально
это тот же пустой текст, что и "", — а документировать две пустые формы рядом
значило бы завести развилку, у которой нет правильного ответа.

Платформа пишет такой тег самозакрывающимся, поэтому эмиссия отдельной веткой.

На пилоте потери в категории row[].cell[].tl упали с 12 488 до 2, заодно
row[].cell[].fmt с 41 645 до 30 088; совпадение строк документа 17.6% → 18.5%.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 12:21:02 +03:00
Nick ShirokovandClaude Opus 5 6619a4e7ad fix(mxl-compile,mxl-decompile): висячие ссылки на стиль и культурная сортировка
Два дефекта, найденных при сведении портов.

Стиль, на который ссылалась только дополнительная колоночная раскладка,
отсекался как неиспользуемый: проверка смотрела в result, а columnSets
попадает туда ПОЗЖЕ неё. Ссылка оставалась висячей — 5 макетов пилота
из 40 указывали на стиль, которого в styles нет. Берём стили из самих
раскладок.

Именованные элементы платформа хранит отсортированными по имени ординально.
ps1 сортировал их Sort-Object -CaseSensitive, а он всё равно сравнивает по
текущей культуре — комментарий рядом сам об этом предупреждал. На кириллице
порядок расходился с py-портом (4800 строк разницы на одном макете).
Сортируем по ключу из кодов символов: в нём только 0-9A-F, культура его
переупорядочить не может.

Порты сошлись: JSON декомпиляторов совпадает на всех 40 макетах пилота
(было 26 расхождений), собранный XML — на 39 из 40 (было 20 расхождений).
Остаток — один макет, где расходятся компиляторы: ps1 подставляет
faceName="Arial" height="10" там, где py пишет пустой шрифт.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 12:03:53 +03:00
Nick ShirokovandClaude Opus 5 c11f4837fd fix(mxl-decompile): свести порты — пробелы, порядок и свойства значения ячейки
Расхождение JSON между портами было 26 макетов из 40, стало 5. Расхождение
собранного XML — 20 из 40, стало 7. Причин оказалось четыре, и три из них
дефекты, а не просто разнобой.

1. ps1 грузил XML с PreserveWhitespace = $false, и текст ячейки из одних
   пробелов схлопывался в пустой. Молчаливая потеря данных в каноничном
   порте, 25 макетов пилота.

2. Стили обнаруживались обходом хэш-таблицы строк, а её порядок в PowerShell
   НЕ определён. Платформа же кладёт записи палитры в порядке документа, так
   что порядок обхода — часть верности вывода, а не деталь. Обход теперь по
   возрастанию номера строки. ([ordered] тут не годится: с целочисленными
   ключами он индексируется по позиции, а не по ключу.)

3. Именованные области сортировались нестабильным Sort-Object (-Stable
   появился только в PowerShell 6.2), поэтому области с одинаковыми границами
   получали произвольный порядок. Добавлен явный ключ исходного порядка —
   в оба порта, чтобы совпадение было по построению, а не по совпадению.

4. containsValue / valueType / controlType протекали в styles сквозным
   пробросом неизвестных тегов. Это свойства ЗНАЧЕНИЯ ячейки, а не оформления,
   и компилятор таких ключей не знает — в DSL они были чистым шумом. Заодно
   вложенный элемент больше не читается как скаляр: ps1 брал InnerText и
   получал склейку поддерева, py брал .text и получал пустоту.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 21:40:44 +03:00
Nick ShirokovandClaude Opus 5 e46db618e3 docs(mxl): уточнить, как читается объект в позиционной записи строки
Прежняя формулировка противопоставляла «ячейку» и «многоязычный текст», хотя
текст тоже даёт ячейку. Разница не в этом: объект либо описывает СВОЙСТВА
ячейки (среди ключей есть ключ её схемы), либо является её ЗНАЧЕНИЕМ, и тогда
ключи — идентификаторы языков.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 21:16:17 +03:00
Nick ShirokovandClaude Opus 5 b5fef09844 feat(mxl-compile,mxl-decompile): многоязычный текст элементом строки-массива
В позиционной записи строки элемент — это значение содержимого ячейки, а значение
текста по общей конвенции бывает строкой либо объектом «язык → текст». Значит
объект {ru, en} там законен так же, как строка, и новой формы это не заводит.

Раньше ячейка с многоязычным текстом не считалась «простой», поэтому строка
двуязычного макета оставалась объектной:

  { "cells": [{ "text": { "ru": "Пусто", "en": "Empty" } }, { … }] }
  [{ "ru": "Пусто", "en": "Empty" }, { … }]

Объект-элемент читается как ячейка, если несёт хоть один её ключ, и как текст
в противном случае: идентификаторы языков с ключами ячейки не пересекаются
(в корпусе это ru, en, ru1, Русский).

Попутно исправлен признак «список позиционный»: он искал элемент-строку, поэтому
строка из одних многоязычных текстов позиционной не признавалась и ключ cells
у неё оставался.

Правка не должна менять скомпилированный XML — и не меняет: пилот из 40 макетов
собрался побайтово так же, JSON при этом изменился у 13. Стенд 7 из 10.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 21:13:32 +03:00
Nick ShirokovandClaude Opus 5 f926acd4f4 fix(mxl-decompile): убрать из вывода бессмысленный style и лишние объектные строки
Две правки читаемости DSL, обе замечены на живом выводе.

Ячейка получала "style": "default" — ссылку на стиль, которого в styles нет
вовсе. Условие сверялось с обнаруженным стилем пустых колонок, а он бывает
равен "default": строка такой стиль не пишет, а ячейка получала ключ на
пустое место. Теперь сверяемся со стилем, который РЕАЛЬНО раздаётся ячейкам.

Позиционная запись строки отбрасывалась, как только первая ячейка стояла не
в первой колонке. Но один-два null впереди обычно короче объектной записи
с col: строка вида { "cells": [{ "col": 2, "text": "…" }] } сворачивается
в [null, "…"]. Выбираем ту форму, которая короче.

Правка не должна менять скомпилированный XML — и не меняет: пилот из 40
макетов собрался побайтово так же, как до неё, при том что JSON изменился
у 31 макета. Стенд по-прежнему 7 из 10 байт в байт.

Попутно блок JSON-сериализатора в ps1 перенесён выше первого использования:
в PowerShell функция должна быть объявлена до вызова.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 20:59:54 +03:00
Nick ShirokovandClaude Opus 5 96e8616bb4 test(mxl): побайтовый раундтрип макетов, произведённых платформой
До сих пор строгая дорожка кампании жила только в отладочных прогонах, и её
некому было защищать: снэпшот сравнивает наш вывод с нашим же прежним выводом,
поэтому дрейф от платформы он не ловит.

Семь макетов, собранных вручную в Конфигураторе, положены в фикстуры. Кейс
декомпилирует такой макет и компилирует обратно, а результат сверяется с
исходником побайтово. Проверено, что сеть кусается: подмена одного тега в
фикстуре роняет кейс.

Раннеру добавлены две ручки:
- expect.filesEqual — сравнение двух файлов побайтово. Снэпшот его не заменяет:
  --update-snapshots молча принял бы расхождение с платформой;
- inputFrom — брать вход из файла в рабочем каталоге, а не из case.input.
  Нужно, когда вход производит preRun: case.input пишется ПОСЛЕ preRun и затёр
  бы его.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 20:27:58 +03:00
Nick ShirokovandClaude Opus 5 fc792bfee6 feat(mxl-compile,mxl-decompile): языки макета и шрифт как у платформы
Контролируемый стенд (upload/epf/МакетТабличныйДокумент) вскрыл четыре
системных расхождения подряд. После правок 7 макетов стенда из 10 проходят
раундтрип БАЙТ В БАЙТ обоими портами — до этого ни один.

1. Объявление языков макета жёстко писалось как «только русский». Это не то же
   самое, что textLanguages: почти во всех макетах ERP объявлен один ru, а текст
   лежит и под ru, и под en, поэтому выводить одно из другого нельзя. Заведены
   недокументированные ключи languages / currentLanguage / defaultLanguage —
   автору они не нужны, нужны раундтрипу. defaultLanguage несём, а не выводим:
   «всегда ru» — наблюдение на четырёх русских типовых, а не правило формата.

2. <font> писался в каждом формате. Платформа пишет его только когда шрифт
   задан: треть форматов корпуса (23 003 из 69 581) обходится без него.

3. Декомпилятор считал шрифт с индексом 0 «обычным» и не писал его в стиль.
   Нулевой шрифт вовсе не обязан быть обычным — в макетах стенда он курсивный
   и жирный, и начертание терялось.

4. Формат, несущий ТОЛЬКО шрифт, выглядел пустым: у ячейки он получал
   зарезервированное имя default и не записывался, у строки терялся целиком.

Остаток на стенде — три макета, и все три про одно: ссылка ячейки на запись
ширины, оставшуюся от прежнего состояния документа. Это не воспроизводится
принципиально, разобрано в docs/1c-spreadsheet-spec.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 20:08:28 +03:00
Nick ShirokovandClaude Opus 5 f72dd544dd fix(mxl-compile): не класть в палитру шрифт, на который никто не ссылается
Мы всегда заводили шрифт Arial 10 по умолчанию, даже когда его никто не
использует. Платформа так не делает: у макета без оформления элемента <font>
нет вовсе. Неиспользуемые шрифты теперь отбрасываются, ссылки перенумеровываются
(индексы шрифтов позиционные).

Проверено на контролируемом стенде: после правки простейший макет расходится
с платформенным ровно одной строкой — объявлением языков в шапке.

Кейс font-fractional-size дополнен: стиль, задающий ТОЛЬКО шрифт по умолчанию,
равнозначен отсутствию оформления (ячейка получает <f>0</f>), поэтому шрифт
остаётся неиспользованным. Чтобы кейс продолжал проверять целый размер, стилю
добавлено второе свойство.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 19:40:39 +03:00
Nick ShirokovandClaude Opus 5 3f80f936ae fix(mxl-compile): формат по умолчанию — последняя запись палитры
Мы регистрировали формат по умолчанию первым, из-за чего вся палитра шла
со сдвигом относительно платформенной. На корпусе он последний в 8285 макетах
из 10 863, первым — в 25.

Подтверждено контролируемым стендом: в макетах, где ширины заданы на уровне
документа, палитра идёт «ширины колонок в порядке колонок, умолчание последним».

Порядок палитры не виден в семантическом диффе (правило palette-index разрешает
ссылки и тем самым его прячет), поэтому мерялся отдельно: на пилоте совпавших
с оригиналом записей с начала палитры стало 467 из 2274 против 23, палитр,
совпавших целиком, — 4 против 2.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 19:23:18 +03:00
Nick ShirokovandClaude Opus 5 250ec9ef0d docs(mxl): актуализировать спецификацию XML табличного документа
За кампанию XML-уровень изучен заметно глубже, чем был описан. Внесено то,
что проверено на корпусе ERP (10 924 макета) и на контролируемом стенде:

- <i> платформа пишет только при разрыве последовательности;
- <indexTo> схлопывает только ПУСТЫЕ строки — прежняя формулировка «строки
  с одинаковым содержимым» неверна: одинаковых непустых схлопнутых нет ни одной
  при 98 153 несхлопнутых;
- <f>0</f> — у ячейки формата нет вовсе, это не индекс записи;
- канонический порядок тегов внутри <format> (height раньше width);
- единица ширины — 1/8 символа;
- свёртка четырёх одинаковых сторон рамки в <border>;
- цвет — значение с префиксом пространства имён, web/win объявляются прямо
  на узле; в Form.xml те же цвета выглядят как web:/win:;
- формат строки несёт не только высоту, а формат колонки не только ширину;
  оформление строки материализуется и в ячейки, кроме hidden;
- columnsItem с formatIndex 0 и с индексом за пределами size;
- полный список стилей линии вместо Solid/None.

Отдельно описана ловушка: width в формате ЯЧЕЙКИ — устаревшая ссылка, она не
описывает итоговое состояние документа и воспроизведению не подлежит.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 19:08:52 +03:00
Nick ShirokovandClaude Opus 5 ba25aa4a00 feat(mxl-compile,mxl-decompile): формат самой строки
У строки есть собственный формат: платформа хранит в нём скрытие (17 423 вхождения
в корпусе), шрифт (9 216), фон, выравнивания, защиту. Мы писали туда только высоту,
всё остальное теряли.

Разбор на контролируемом стенде показал, что это два разных случая, а не один.
Оформление, применённое к строке целиком, платформа пишет И строке, И каждой
ячейке (backColor: 13 899 ячеек повторяют против 96). Скрытие — только строке
(24 026 против 62 856). Поэтому одного правила «rowStyle красит ячейки» мало.

Теперь rowStyle — стиль строки: по умолчанию ложится и на строку, и на ячейки,
как это делает платформа. Объектная форма { style, apply } задаёт исключения:
"row" — только строке, "cells" — только ячейкам. Модификатор нужен раундтрипу,
в описании DSL его нет.

Скрытие и высота — собственные свойства строки, ключами рядом: у ячейки таких
свойств не бывает, и в её оформление они не попадают.

На пилоте категория row[].formatIndex упала с 1964 до 201 потерянного факта.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 18:36:41 +03:00
Nick ShirokovandClaude Opus 5 ccbd488d63 fix(mxl-compile): у неоформленной ячейки формата нет вовсе
Ячейка без собственного оформления ссылалась на формат по умолчанию. Платформа
в этом случае пишет <f>0</f>, где ноль — не индекс записи, а «формата нет»:
на корпусе так у 170 710 ячеек против 50 635, ссылающихся на умолчание, и
<f>0</f> встречается в 71% макетов.

Подтверждено контролируемым стендом: в макете, где колонкам заданы ширины, а
ячейки ничем не оформлены, платформа пишет ячейкам ноль.

На пилоте верхняя категория row[].cell[].fmt упала с 63 900 до 55 855 фактов,
совпадение строк документа выросло с 16.7% до 17.5%.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 17:32:56 +03:00
Nick ShirokovandClaude Opus 5 08503c72e9 docs(mxl): разделить справочник DSL и описать новые ключи
Справочник был одним файлом на 273 строки, а полная таблица свойств стиля его
бы удвоила. Разделён по частоте обращения, как в meta-compile: в инструкции
маршрутная таблица «что нужно → какой файл».

  reference/dsl-spec.md          — верхний уровень, области, строки, ячейки
  reference/styles.md            — шрифты, стили, цвет, рамка, колонки
  reference/format-properties.md — полный перечень свойств стиля

Описаны columnStyles и новая запись стиля: ключ = имя свойства как в выгрузке,
рамка пятью ключами, цвет в четырёх формах. Прежние align/valign/wrap работают
и дальше, но в описании их нет — иначе у модели появляется развилка.

Ограничения переписаны по факту: цвета, скрытие, отступ, посторонние рамки и
стили линий больше не теряются; зато честно названо то, что теряется до сих пор —
оформление строки помимо высоты.

Пример из спеки скомпилирован и проверен валидатором.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 17:14:31 +03:00
Nick ShirokovandClaude Opus 5 3c7f7345bb fix(mxl-compile,mxl-decompile): не терять колонку, объявленную без формата
Платформа иногда перечисляет колонку в раскладке с <formatIndex>0</formatIndex> —
колонка объявлена, формата у неё нет (4% макетов корпуса). Компилятор такие
опускал, декомпилятор их не видел, и элемент терялся целиком.

Выражается пустым значением в columnStyles. Для авторинга это бесполезно —
ни одна задача не звучит как «объяви колонку без свойств», — поэтому в описании
DSL записи нет: она нужна только чтобы раундтрип не терял байты.

На пилоте потери в категории colset[].col[].formatIndex упали с 39 до 35.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 17:08:03 +03:00
Nick ShirokovandClaude Opus 5 4e389d0ef1 feat(mxl-compile,mxl-decompile): columnStyles — оформление колонки
У ячейки есть style, у строки rowStyle, у колонки не было ничего — при том что
колонка ссылается в ту же палитру <format> и несёт те же свойства. В корпусе
ERP колонки используют шрифт (3006 форматов), выравнивание, рамки, скрытие и
прочее; всё это терялось при раундтрипе.

Колонка получает такой же именованный стиль: columnStyles рядом с columnWidths,
ключи той же грамматики диапазонов ("1", "2-8", "5,7,9"), значение — имя из
styles. Внутри columnSets тот же ключ. Формат колонки собирается из ширины и
свойств стиля: запись в палитре одна.

Шрифт по умолчанию колонке не навязываем — формат колонки без оформления это
ровно <width>, как пишет платформа.

Попутно два дефекта декомпилятора: стиль колонки не учитывался при отсечении
неиспользуемых стилей и пропадал целиком, а набор из одних неприметных свойств
(отступ, защита) получал зарезервированное имя default и тоже терялся.

На пилоте потери в категории colset[].col[].formatIndex упали с 86 до 39.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 16:57:52 +03:00
Nick ShirokovandClaude Opus 5 c9e490bd25 feat(mxl-decompile): читать формат целиком, а не девять полей
Декомпилятор снимал из <format> только шрифт, четыре рамки, выравнивания,
перенос и форматную строку — остальные тридцать с лишним свойств терялись
молча. Компилятор их уже умеет, но до раундтрипа они не доезжали.

Теперь формат читается целиком по общей таблице типов — той же, что у
компилятора: рамки разворачиваются в описание линии из палитры, булевы и
целые приводятся по типу, цвет из web/win-палитры возвращается в привычную
запись (префикс узла разрешается в URI пространства имён).

Имя стиля осталось читаемой меткой по заметным свойствам: различает стили
ключ, имя лишь помогает автору ориентироваться.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 16:15:16 +03:00
Nick ShirokovandClaude Opus 5 63f7c7a99d test(mxl): переснять снэпшоты соседей после свёртки рамки
Снэпшоты mxl-decompile/mxl-info/mxl-validate строятся preRun-прогоном
mxl-compile. Дрейф одного вида: четыре одинаковые стороны рамки стали
одним <border>.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 15:34:55 +03:00
Nick ShirokovandClaude Opus 5 d3c16f56c5 feat(mxl-compile): стиль умеет все свойства формата, а не семь
Стиль описывал 7 свойств из 47, которые платформа хранит в <format>. Не было
самого частого тега корпуса — backColor (1.7 млн вхождений), а также textColor,
textPlacement, protection, hidden, indent, borderColor, textOrientation и хвоста.

Теперь ключ стиля — имя тега платформы, без исключений: помнить, какие ключи
названы по-своему, больше не нужно. Прежние align/valign/wrap продолжают
работать молча, как синонимы; там же CSS-имена (background, color,
border-bottom) и русские имена свойств. Таблицы типов значений и допустимых
значений перечислений сняты с корпуса, а не выписаны на глаз.

Рамка: пять плоских ключей (border и четыре стороны) со значением
{ style, width } либо строкой стиля; линия регистрируется в палитре <line>,
которая раньше знала только «тонкую» и «толстую» Solid. Четыре одинаковые
стороны сворачиваются в один <border> — правило проверено на корпусе:
70 265 свёрнутых форматов против 36 783 записанных по сторонам, и среди
вторых нет ни одного с четырьмя совпадающими значениями.

Цвет — нотация самой платформы: #RRGGBB, style:Имя, web:Имя, win:Имя. Первые
две пишутся дословно (префикс style объявлен в корне документа), для web и win
платформа дописывает объявление xmlns прямо на узел — делаем так же.

containsValue / valueType / controlType намеренно не заведены: это свойства
конкретной ячейки, а стиль — сущность общая, один на многие ячейки.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 15:34:54 +03:00
Nick ShirokovandClaude Opus 5 c559696c5a refactor(mxl-compile): формат — таблица тегов вместо двенадцати полей
Формат был фиксированной записью из 12 полей, выписанной руками в четырёх
местах: ключ дедупликации, набор свойств, резолв стиля и эмиссия палитры.
Добавление свойства стоило восьми правок в двух портах, а свойств у формата
в выгрузке 47.

Теперь формат — набор «тег платформы → значение», а порядок эмиссии задаёт
один канонический список тегов. Список снят с корпуса ERP: 766 960 форматов,
ни один не нарушает эту последовательность.

Вывод не меняется: пилот из 40 макетов скомпилировался побайтово так же, как
до правки, обоими портами.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 14:45:30 +03:00
Nick ShirokovandClaude Opus 5 c5a65ef620 test(mxl): переснять снэпшоты соседних навыков после правки эмиссии
Снэпшоты mxl-decompile/mxl-info/mxl-validate строятся preRun-прогоном
mxl-compile, поэтому дрейфуют вместе с ним. Дрейф ровно двух видов:
убран избыточный <i> и схлопнуты пустые строки в indexTo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 13:04:59 +03:00
Nick ShirokovandClaude Opus 5 cd9a800f8b fix(mxl-compile): писать номер колонки и пустые строки как платформа
Две правки эмиссии, обе выведены из корпуса erp_8.3.24 (10924 макета,
15.2 млн ячеек) и проверены на нём же без единого контрпримера.

Номер колонки <i> платформа пишет только при разрыве последовательности:
подряд идущая ячейка его не несёт, читатель ведёт счётчик сам. Мы писали
всегда — из 1.2 млн записанных платформой номеров ни один не избыточен.

Подряд идущие одинаковые ПУСТЫЕ строки платформа хранит одним rowsItem
с indexTo; непустые не схлопывает, даже когда они совпадают (98153 таких
случая в корпусе). Мы не эмитили indexTo вовсе.

На пилоте (40 макетов) доля строк документа, совпавших с оригиналом
байт в байт, выросла с 0.3% до 16.7%; все 9 диапазонов indexTo оригинала
воспроизведены точно.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 13:04:59 +03:00
Nick ShirokovandClaude Opus 5 3e7e105c43 fix(mxl-compile): писать ошибки в stderr напрямую, а не через Write-Error
Write-Error оборачивает сообщение в ErrorRecord: приписывает имя скрипта,
добавляет хвост CategoryInfo и ломает текст по ширине окна консоли. Длинные
сообщения приезжали разорванными посреди слова, и по ним нельзя было
ни искать подстроку, ни читать их глазами.

Остальные четырнадцать мест в файле уже писали через Console.Error —
приведены оставшиеся шесть. Тексты сообщений не менялись.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 12:16:42 +03:00
Nick ShirokovandClaude Opus 5 3bb717b2cc docs(mxl-compile): убрать из описания textLanguages совет и скрытую статистику
«Внутри конфигурации набор обычно одинаков» — тот же вывод из замеров, только
без числа: проверить его по месту нельзя. Совет смотреть соседние макеты толкает
на лишний обход при авторинге с нуля, а упоминание, что платформа разрешает
необъявленные языки, читается как приглашение их указывать.

Осталось свойство самого ключа: он ни на что в конфигурации не смотрит.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 12:09:20 +03:00
Nick ShirokovandClaude Opus 5 7b78485f42 docs(mxl-compile): убрать из спеки статистику по корпусу и внутренности компилятора
Проценты по типовым конфигурациям модель может принять за правило, а объяснение,
почему набор языков нельзя вывести из текстов, ей для применения не нужно.
Осталось то, что помогает выбрать значение: языки текстов и языки конфигурации
совпадать не обязаны, ориентир — соседние макеты той же конфигурации.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 12:03:49 +03:00
Nick ShirokovandClaude Opus 5 683ed8e08d feat(mxl-compile,mxl-decompile): textLanguages — текст строкой на всех языках макета
Строка в text/template разворачивается по одному элементу на каждый язык из
документного ключа textLanguages (по умолчанию только ru). Декомпилятор собирает
набор языков по всем текстам макета и пишет строкой текст, одинаковый на всём
наборе; различающийся остаётся объектом.

Языки текстов и языки, объявленные в конфигурации, — разные вещи: типовые ERP
объявляют один ru, а тексты хранят под ru и en. Набор постоянен внутри
конфигурации, поэтому он документный, а не вычисляемый: к моменту компиляции
тексты уже свёрнуты в строки.

Заодно: эмиссия tl проверяет НАЛИЧИЕ ключа text/template, а не истинность —
пустая строка это текст, платформа такие ячейки пишет с пустым tl, а по
истинности он терялся.

Проверено на пилоте (40 макетов ERP): скомпилированный XML совпадает с прошлым
прогоном байт в байт обоими рантаймами, объём JSON −47%.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 12:00:20 +03:00
Nick ShirokovandClaude Opus 5 4cceb03269 feat(mxl-compile,mxl-decompile): позиционный список ячеек в выводе декомпилятора
Компилятор давно принимал короткую форму, а декомпилятор всегда писал самую
развёрнутую. Теперь он выбирает самую короткую из применимых, как это делает
skd-decompile.

Короткая запись стала свойством СПИСКА ЯЧЕЕК, а не строки: cells принимает
позиционный список, поэтому строка с height или rowStyle тоже может им пользоваться.
Раньше наличие свойств строки лишало её короткой записи — искусственная связка.
Если же своих свойств у строки нет, она пишется просто массивом, без ключа cells.

Позиционный список опознаётся по наличию элемента-строки или null; список из одних
объектов читается как обычный — для простой строки обе трактовки дают один результат.

Три дефекта, вскрытых проверкой на потери (каждый раз XML переставал совпадать с
прежним, и это приводило к причине):

- компилятор двигал позицию на единицу за элемент, поэтому объектный элемент со
  span: 3 занимал одну позицию вместо трёх и всё правее него уезжало влево. У маркеров
  ">" этого не было — каждый съедает свою позицию;
- проход «удалить неиспользуемые стили» не заглядывал в строки-массивы: у массива нет
  свойства cells, и стиль, использованный только внутри такой строки, вырезался как
  неиспользуемый;
- в py тот же проход падал на позиционном списке — "style" in c для None бросает
  TypeError, а для строки делает подстрочный поиск.

Плюс ловушка PowerShell: += разворачивает вложенный массив, и строка-массив
расплющивалась в список строк — нужна запятая.

Проверено: XML на всех 40 макетах пилота совпал с прежним БАЙТ В БАЙТ, то есть
сокращение без потерь. Объём декомпилированного JSON меньше на 11%. Тесты 54/54 на
обоих рантаймах, гарды зелёные.

В WORKFLOW уточнена находка про паритет портов: декомпиляторы расходятся по JSON на
33 макетах из 40, а компиляторы на одном и том же входе — всего на 2.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 22:35:58 +03:00
Nick ShirokovandClaude Opus 5 a9697b613d fix(mxl-decompile): не писать style по умолчанию, чинить потерю стиля при rowStyle
Одно условие делает две вещи.

Сокращение: у 56% ячеек (19 737 из 35 190) стоял "style": "default", хотя компилятор
и так подставляет default, когда ключа нет и у строки нет rowStyle. Чистый шум;
объём декомпилированного JSON на пилоте упал на 7%.

Починка: при ЗАДАННОМ rowStyle ячейка со стилем default, которая его не наследует,
теряла стиль вовсе — условие содержало -not rowStyleName, — и при обратной сборке
такая ячейка наследовала rowStyle вместо своего умолчания.

Проверка на потери: из 40 макетов пилота 23 собрались байт в байт как прежде. У 17
XML изменился, и это разобрано поячеечно: во всех 181 разошедшейся ячейке НИ ОДНА
версия не совпадает с оригиналом — там backColor, фон ячейки, который DSL не
поддерживает вовсе (числится в ограничениях). То есть правка переключает между двумя
одинаково неверными отображениями неподдерживаемой конструкции, не улучшая и не
ухудшая совпадение: фактов, совпавших с оригиналом, было 8183 и осталось 8183.

Проверено: тесты 54/54 на обоих рантаймах. Дрейф снэпшота один и ожидаемый — из
back.json ушёл "style": "default".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 21:36:07 +03:00
Nick ShirokovandClaude Opus 5 1e06e43e92 feat(mxl-compile,mxl-decompile): многоязычный текст ячейки
Платформа хранит текст ячейки по элементу на язык. Декомпилятор брал ПЕРВЫЙ и терял
остальное, компилятор писал всегда ru захардкоженно. В корпусе ERP текст лежит и под
ru, и под en у 10 730 макетов из 10 924 — то есть терялось у 98%.

Форма взята готовая — конвенция ML-значений из docs/meta-dsl-spec.md §4.4, по которой
уже живут synonym, tooltip и title в метаданных и формах: строка означает русский
текст, объект даёт по надписи на язык в порядке ключей. Новых понятий не вводили,
ключи те же (text и template).

Документный ключ для языков НЕ заводили, и это измерение, а не экономия: блок
languageSettings мы и так эмитим байт в байт как платформа (currentLanguage ru,
defaultLanguage ru, один languageInfo). Отклонения редки — 8 макетов ERP с
currentLanguage en, 3 без него, 157 с объявленной парой ru+en, всего около 1,6%.
Заодно это снимает развилку из разбора PR #62: список языков компилятор дописывать
не должен, платформа его не дописывает.

Эффект на пилоте: категория row[].cell[].tl упала с 47 252 до 12 488 — минус 74%,
самое крупное улучшение кампании. Остаток частично классифицирован: 348 строк — ячейки
с ПУСТЫМ <tl> у оригинала, которых мы не эмитим вовсе; полностью разобрать мешает
обрезка диффа по 400 строк на объект.

Снэпшоты не дрейфанули: кейсы пишут текст строкой, и вывод для неё прежний.

Проверено: тесты 54/54 на обоих рантаймах, verify-snapshots 24/24, вывод портов
совпадает байт в байт и в компиляции, и в декомпиляции.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 20:52:03 +03:00
Nick ShirokovandClaude Opus 5 572b75984a fix(mxl-compile): неоформленная ячейка ссылается на формат по умолчанию
Платформа держит для неоформленного макета ОДИН формат — ширину колонки, он же
defaultFormatIndex, и все ячейки указывают на него. Компилятор заводил каждой ячейке
собственный формат с <font>0</font>, где ноль означает «шрифт не задан», то есть
формат был пуст по смыслу и отличался от умолчания только своим существованием.

Теперь ячейка, у которой не задано ничего (шрифт умолчательный, нет рамок,
выравнивания, переноса, типа заполнения и формата числа), ссылается на
defaultFormatIndex. На минимальном макете палитра сократилась с двух форматов до
одного и совпала по форме с платформенной.

На корпусе правка закрывает случай неоформленной ячейки начисто: на макете
АтрибВыгрузкиXML2015Кв1 расхождения по формату исчезли полностью, блок диффа
сократился с 75 строк до 40, и всё оставшееся — многоязычный текст. В агрегате по
пилоту это 497 строк из 64 865: остальное держат макеты, где ячейки реально
оформлены, и там расходится состав самих форматов — отдельная категория.

Дрейф 18 снэпшотов разобран построчно, посторонних строк нет: 170 сменившихся
индексов <f>, 36 границ <format> (палитра стала короче), 18 удалённых <font>.

Проверено: тесты 53/53 на обоих рантаймах, verify-snapshots 23/23.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 20:20:35 +03:00
Nick ShirokovandClaude Opus 5 66c7364c37 fix(mxl-compile): выводить идентификатор раскладки как корректный UUIDv3
Идентификатор колоночной раскладки выводится из имени детерминированно — это
требование, а не оптимизация: случайный давал бы другой файл при каждой компиляции,
снэпшоты не совпадали бы сами с собой, а повторная сборка того же определения
порождала бы диф.

Но выводился он неправильно: сырой хэш форматировался как UUID, без битов версии и
варианта. В выдаваемом значении версия оказывалась 7, вариант 1 — таких не бывает,
то есть формально это был не UUID, а шестнадцатеричная строка нужной формы. Платформа
такое принимает (проверено сертификацией), но упереться в валидатор мы могли в любой
момент — ровно того же сорта риск, что сертификация уже ловила в этой ветке.

Теперь это штатный UUID версии 3 (имя + MD5, RFC 4122): для задачи «вывести
идентификатор из имени» существует именно он. Детерминированность сохранена, оба
порта дают одинаковое значение.

Проверено: тесты 53/53 на обоих рантаймах, verify-snapshots 23/23.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 20:07:02 +03:00
Nick ShirokovandClaude Opus 5 61199cfe9e test(mxl-compile): объяснить магический GUID в имени кейса
В ожиданиях column-sets стоит конкретный идентификатор раскладки, и по кейсу
не видно, откуда он взялся. Имя кейса теперь говорит, что id выводится из
имени детерминированно и что ожидаемое значение — md5 от имени раскладки.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 20:00:33 +03:00
Nick ShirokovandClaude Opus 5 cbe7abdcbb fix(mxl-compile): не эмитить fillType=Text, UUID для идентификатора раскладки
Две правки по корпусу и по 1С-сертификации.

fillType=Text платформа практически не пишет: на выборке корпуса 344 981 текстовая
ячейка из 348 023 (99,1%) ссылается на формат БЕЗ fillType — наличие <tl> и так
означает текст. Parameter и Template платформа пишет, их оставляем. Правило то же,
что и везде: эмитим то, что эмитит платформа, а не то, что верно в рантайме.

Идентификатор колоночной раскладки платформа хранит как UUID и другой не принимает —
макет с <id>узкая</id> она отвергала целиком. Имя, похожее на UUID (то есть пришедшее
декомпиляцией), проходит насквозь, иначе поехал бы раундтрип; читаемое имя автора
превращается в UUID ДЕТЕРМИНИРОВАННО, из хэша имени, чтобы повторная компиляция
давала тот же файл. Оба порта дают одинаковый хэш.

Дефект с идентификатором нашла 1С-сертификация: ни юнит-кейсы, ни раундтрип по
корпусу его не видели — там идентификаторы приходят из исходных макетов и уже
являются UUID.

Дрейф 33 снэпшотов разобран построчно, посторонних строк нет: 116 сменившихся
индексов формата, 49 удалённых fillType, 43 перестановки свойств внутри палитры,
16 границ <format> (палитра стала короче).

Проверено: тесты 53/53 на обоих рантаймах, verify-snapshots 23/23.

Замечание на будущее: на корпусе эта правка снимает лишь 170 расхождений из 64 865
в категории формата ячейки. Основная причина другая — платформа даёт неоформленной
ячейке ссылку на формат по умолчанию, а компилятор заводит ей собственный формат
с <font>. Это отдельный заход, он сдвинет снэпшоты повторно.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 19:58:35 +03:00
Nick ShirokovandClaude Opus 5 8302b17814 fix(mxl-compile): внятный отказ на объектную форму columnSet
Раскладка адресуется именем из columnSets. Инлайновой формы в этом DSL нет ни у
чего: style у ячейки и font внутри стиля тоже только имена — именованные сущности
объявляются один раз и всюду адресуются по имени. Объектная форма стала бы второй
формой одного и создала бы асимметрию «раскладку инлайнить можно, а стиль нельзя».

Объект в columnSet и раньше не портил вывод — навык падал с «Unknown 'columnSet'»,
подставляя сериализованный объект. Но сериализация у портов разная (@{columns=5}
против {'columns': 5}), то есть сообщения расходились. Теперь это отдельная проверка
с текстом, одинаковым в обоих портах и прямо называющим правило.

Кейсы: column-sets (позитивный, ссылка и объявление доезжают до XML) и
error-columnset-inline.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 19:26:01 +03:00