Commit Graph

207 Commits

Author SHA1 Message Date
Nick Shirokov 55b80fdc08 feat(skd-decompile): компактный JSON-сериализатор + расширенный shorthand totalFields
Заменил ConvertTo-Json (PS5.1) на собственный ConvertTo-CompactJson:
- 2-пробельный indent (вместо 4 + выравнивание keys по длине)
- Массивы примитивов (string/number/bool/null) — inline `[a, b, c]`
- Массивы с объектами/nested arrays — multi-line как раньше
- Кириллица в UTF-8 (без \uXXXX-escapes)
- Корректный escape строк (\\", \\, \n, \r, \t, \uXXXX для control chars)

Build-TotalField: shorthand "name: expr" для любого однострочного
expression. Раньше object form применялась когда expression не Func(arg).
Теперь — только когда есть group или expression многострочный.
Compile принимает любой shorthand вида "dataPath: expression" (Parse-
TotalShorthand делает split по первому ":").

Save-UserStyles тоже использует новый сериализатор.

Все 16 декомпиляционных snapshot'ов обновлены (косметика — JSON
структурно тот же, тесты round-trip проходят).

На реальном отчёте (целевой корпус): 405 → 264 строк (-35%).
v0.18 → v0.19.
2026-05-21 20:50:34 +03:00
Nick Shirokov e0ee927156 feat(skd-decompile): externalize multi-line queries в отдельные .sql файлы
Если query ≥3 строк и указан -OutputPath, decompile выносит SQL в
<datasetName>.sql рядом с decompiled.json. В JSON эмитится "@<name>.sql"
вместо inline-строки.

- Имя файла: dataset name (sanitized — non-word chars → _), коллизии
  разрешаются суффиксом _2/_3/...
- compile уже поддерживает синтаксис @file.sql (Resolve-QueryValue)
  — round-trip симметричен.
- Тесты не изменились: все тестовые queries по 1 строке (порог не
  срабатывает).
- На реальных отчётах (ERP/ACC main DCS — 10-50 строк query типично)
  даёт значительно более компактный JSON + читаемый .sql с подсветкой
  синтаксиса в IDE.

Новый тест dataset-query-multiline (round-trip с внешним .sql).
v0.17 → v0.18.
2026-05-21 20:37:15 +03:00
Nick Shirokov a1131965cc feat(skd): DataSetFieldFolder + GroupItemAuto + empty-field selection
Закрывает три gap'a, выявленных при полном прогоне ERP+ACC:

1. DataSetFieldFolder — поле-папка для UI-группировки полей в композиторе
   настроек. Только dataPath + title, без valueType/role.
   - DSL: object form поля с `folder: true`.
   - Compile: при folder=true → <field xsi:type="DataSetFieldFolder">.
   - Decompile: распознать xsi:type, эмитить object form.

2. GroupItemAuto — пустой <item xsi:type="GroupItemAuto"/> в groupItems
   (auto-grouping, аналогично "Auto" в selection).
   - DSL: строка "Auto" в groupFields.
   - Compile/decompile: round-trip.

3. Empty <field/> в conditionalAppearance/selection (wildcard — apply
   to all). Раньше — SelectionItem: sentinel. Теперь эмитим как "Auto"
   (семантический эквивалент через SelectedItemAuto).

Новый тест dataset-folder-and-auto-group (round-trip).
Versions: compile v1.34→v1.35, decompile v0.16→v0.17.

На предыдущем прогоне ERP+ACC: 227 sentinel'ов (218 DataSetFieldFolder
+ 7 GroupItemAuto + 2 SelectionItem). После — 0.
2026-05-21 20:28:45 +03:00
Nick Shirokov be9ebedf14 fix(skd-compile): multilang appearance value (Формат={ru,en} и др.)
Emit-AppearanceValue / emit_appearance_value: hashtable/PSCustomObject/dict
значение → LocalStringType независимо от ключа. Раньше для значения
{ru: "ДЛФ=D", en: "DLF=D"} compile эмитил xs:string "@{ru=ДЛФ=D; en=DLF=D}"
(строковое представление PS hashtable) — потеря структуры и неверный XML.

Wrapper {use: false, value: ...} распознаётся точечно (требуются оба ключа,
чтобы не путать с multilang dict без 'use').

Унификация field-level appearance: parse сохраняет значение как есть
(а не str(v)), emit использует Emit-AppearanceValue вместо дублированной
mini-логики. Side-effect: "true"/"false" в field appearance теперь эмитятся
как xs:boolean (раньше — xs:string). Корректнее для 1С; обновлён один
snapshot теста compile.

Новый тест appearance-multilang-value (поле + conditionalAppearance с
multilang Формат — round-trip bit-perfect).
Versions: compile v1.33→v1.34.

Закрывает п.2 из handoff («известный баг с multilang appearance values»).
2026-05-21 20:01:31 +03:00
Nick Shirokov 7f3a8861ad feat(skd): inline cell style override + закрытие категории C
Cell в rows теперь может быть либо string ("text"/"{param}"/"|"/">"/null),
либо объектом {value, style: "presetName"}. Object form применяется когда
стиль ячейки отличается от template default.

compile (ps1+py): helpers _get_cell_value / _get_cell_style_or_default.
Emit-AreaTemplateDSL / _emit_area_template_dsl используют per-cell style
для appearance вместо единого template style.

decompile: refactor Build-Template. Первый pass — собрать style name per
cell в cellStyleMap. Второй pass — выбрать template default как most
frequent style. Третий pass — обернуть в {value, style} ячейки, чьи
стили отличаются от default. TemplateStyleMismatch sentinel удалён —
теперь все случаи покрываются через inline override.

Дедуп при обоих pass'ах (Match-PresetByShape) работает через
effectivePresets (built-in + user + ранее аллоцированные customN), так
что одинаковые shape'ы получают одно имя.

Новый тест template-inline-cell-style (round-trip bit-perfect).
Versions: compile v1.32→v1.33, decompile v0.15→v0.16.

Метрики на момент коммита:
- ERP-сэмпл 30: 30/30 clean, 0 sentinel'ов
- Корпус из 40 отчётов целевого класса: 40/40 clean, 0 sentinel'ов

Закрывает категории A, B, C полностью на обоих корпусах.
2026-05-21 19:53:41 +03:00
Nick Shirokov 3119700c71 feat(skd-decompile): авто-генерация skd-styles.json для custom appearance
Категория C — закрыта для однородных шаблонов с custom appearance.

Refactor fingerprint → preset shape (11 полей: font/fontSize/bold/italic/
hAlign/vAlign/wrap/bgColor/textColor/borderColor/borders). vAlign теперь
учитывается в matching (раньше игнорировался).

Алгоритм:
1. При -OutputPath загружается existing skd-styles.json рядом (если есть);
   user presets накладываются на built-in по той же логике что и compile.
2. Каждая ячейка → Extract-CellPreset → Match-PresetByShape против
   effectivePresets (built-in + user).
3. Если не match — Allocate-CustomStyle: новый customN, регистрируется
   в effectivePresets и accumulator.
4. По окончании Save-UserStyles пишет skd-styles.json рядом с outputPath
   (preserved existing + новые customN).
5. Compile подхватит файл по своим search-путям (cwd/dirname/scan-up).

В SKILL.md не добавляем (custom стили — для round-trip, не для написания
модель с нуля; built-in `data/header/subheader/total/none` остаются
основным интерфейсом для модели).

- runner.mjs: новый preRun step `writeFile` для подготовки fixture-файлов
  в workDir (нужен для теста с предзаписанным skd-styles.json).
- Новый тест template-custom-style: preRun пишет myHeader preset,
  скомпилирует темплейт, decompile reverse'ит → переиспользует имя
  myHeader (не создаёт customN).
- v0.14 → v0.15.

Метрики:
- ERP-сэмпл 30: 24 → 0 sentinel'ов, clean 26 → 30/30
- Целевой корпус 40 отчётов: 39 → 25 sentinel'ов (часть закрыта), clean
  19 → 20/40. Остаточные — шаблоны с разными стилями в разных ячейках
  одного шаблона (нужно per-cell style override — отдельная задача).
2026-05-21 19:32:17 +03:00
Nick Shirokov 4bd8f27dec feat(skd): preset style=none + детект пустого fingerprint в decompile
Закрывает простую часть категории C: шаблоны где у ячеек appearance
содержит только per-cell атрибуты (МинимальнаяШирина и др.) без font/
borders/colors. Раньше такие шаблоны попадали в TemplateStyleMismatch.

- skd-compile (ps1+py): новый preset 'none' со всеми стилевыми полями
  null/false. Emit-CellAppearance / _emit_cell_appearance пропускают
  Font-элемент когда style.font=null.
- skd-decompile: пустой fingerprint (после отсева per-cell ключей) не
  считается за стиль ячейки; если все non-merge ячейки шаблона имели
  пустой fp — эмитим style="none" вместо sentinel.
- Новый тест template-no-style (round-trip bit-perfect).
- Versions: compile v1.31→v1.32, decompile v0.13→v0.14.

Метрики:
- ERP-сэмпл 30: 32 → 24 sentinel'ов, clean 24→26/30
- Корпус из 40 отчётов целевого класса: 45 → 39 sentinel'ов, 19/40 clean

Остаточные sentinel'ы — реальный custom appearance (нестандартный шрифт/
выравнивание/цвет вне built-in пресетов). Требует расширения DSL под
hashtable-style — отдельная задача.
2026-05-21 18:38:34 +03:00
Nick Shirokov a73517ee07 feat(skd): nested folder + nestedObject + groupItem object form (round-trip)
Закрывает категорию B полностью на ERP-корпусе:
- selection.folder теперь рекурсивный: внутри items могут быть string,
  {field, title}, или ещё одна {folder, items: [...]}. Compile/decompile
  обходят дерево рекурсивно (Emit-SelectionItem / Build-SelectionItem).
- structure: новая ветка type=nestedObject с {objectID, settings:
  {selection, filter, order, conditionalAppearance, outputParameters}}.
- groupFields теперь объектная форма {field, groupType?, periodAdditionType?}
  когда не дефолт (Items / None). Compile уже принимал; decompile перестаёт
  ставить warning GroupItemDetails. Try-StructureShorthand игнорирует
  object-form поля при сворачивании в строку.
- Refactor: Build-Structure для StructureItemGroup теперь использует
  общий Get-GroupFields вместо дублированного inline-кода.

В SKILL.md не добавляем (формы редкие/сложные, модель не пишет с нуля).

Новый тест structure-nested-and-folder покрывает все три случая bit-perfect.
Versions: compile v1.30→v1.31, decompile v0.12→v0.13.

На сэмпле 30 ERP-отчётов: 754 → 32 sentinel'ов (-96%), clean 4 → 24/30.
Остаточные 32 — все TemplateStyleMismatch (категория C, диагностика).
2026-05-21 18:19:49 +03:00
Nick Shirokov cbc9f0cf61 feat(skd): inputParameters — ChoiceParameters/Links + typed values (round-trip)
DSL: object-form ключ inputParameters — массив элементов, каждый типизирован
по форме value:
- choiceParameters: [{name, values: [...]}] — параметры выбора (DesignTimeValue)
- choiceParameterLinks: [{name, value, mode}] — связи параметров выбора
- value (+ optional use=false) — простое типизированное значение (bool/string/number)

Compile: Emit-InputParameters / emit_input_parameters → <r:inputParameters>...
Decompile: Read-InputParameters читает любой xsi:type, без SilentDrop warnings.
Build-Parameter — убран вызов несуществующего Check-InputParameters.

В SKILL.md не добавляем (форма сложная — модель не пишет с нуля, но при
декомпиляции из реального отчёта получает корректно и compile примет назад).

Новый тест field-input-parameters (3 типа элементов, bit-perfect round-trip).
Versions: compile v1.29→v1.30, decompile v0.11→v0.12.

На сэмпле 30 ERP-отчётов: SilentDrop:ChoiceParameters/Links 51 → 0,
clean reports 8 → 21, total sentinel'ы 109 → 58.
2026-05-21 18:07:59 +03:00
Nick Shirokov 4413a06c49 feat(skd): orderExpression — сортировка поля по выражению (round-trip)
- skd-compile (ps1+py): object-form ключ orderExpression{expression,orderType,autoOrder}
  → <r:orderExpression><dcscom:expression/><dcscom:orderType/><dcscom:autoOrder/>
- skd-decompile: читает <r:orderExpression> → object form поля, без SilentDrop warning
- SKILL.md skd-compile: одна строка в "Дополнительные ключи объектной формы"
- docs/skd-dsl-spec.md: пример в объектной форме поля
- Новый тест field-order-expression (round-trip bit-perfect)
- Versions: compile v1.28→v1.29, decompile v0.10→v0.11

На сэмпле 30 ERP-отчётов: SilentDrop:orderExpression 11 → 0.
2026-05-21 17:59:19 +03:00
Nick Shirokov 537adfd3f8 feat(skd-decompile): shorthand-render роли + extras без whitelist
- Get-RoleInfo: любой <dcscom:KEY> со строковым значением → extras; whitelist убран
- Render-Role: shorthand-строка "@flag K=V" когда все extras-значения простые
  (regex ^[\w\.\-]+$); иначе object form
- Build-Field: shorthand-роль встраивается в field-shorthand-строку
- v0.9 → v0.10

Новый тест-кейс field-roles-rich (балансовые поля с balanceGroupName/balanceType,
@dimension @required) — bit-perfect round-trip с compile.

На сэмпле 30 ERP-отчётов: 754 → 120 sentinel'ов (-84%), 8/30 clean.
ComplexRole 27 → 0.
2026-05-21 17:43:05 +03:00
Nick Shirokov 48b08d77e5 test(skd-decompile): 6 snapshot-based test cases по слоям
Кейсы создают исходник через preRun (skd-compile), декомпилируют его и
сравнивают workDir со снапшотом (Template.xml + decompiled.json):

- minimal-query — базовый DataSetQuery
- fields-types-and-restrictions — типы, роли, restrictions, multilang
  title, appearance, composite type, presentationExpression
- calc-total-params — calculatedFields, totalFields, parameters с
  autoDates/valueList/hidden/availableValues
- templates-with-style-merge-drilldown — built-in стили header/data,
  merge >/|, drilldown свёртка
- variant-full — selection с folder, filter Or, conditionalAppearance,
  outputParameters, dataParameters="auto", structure shorthand,
  groupTemplates
- dataset-types — DataSetQuery + DataSetObject + DataSetUnion

Все 6 passes на runtime=powershell. Готовая база для регрессии при
питон-порте (можно прогнать тот же набор через --runtime python).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 16:20:59 +03:00
Nick Shirokov 5ec21f24b4 feat(skd-decompile): scaffold — Ring 3 fail-fast, sentinel/warnings, query extraction
Layer 1 of the skd-decompile plan: SKILL.md with disable-model-invocation,
ps1 skeleton with XML→JSON pipeline, namespace probe for non-DCS root,
sentinel/warnings accumulator, and DataSetQuery extraction (query only).
Test case minimal-query demonstrates round-trip via skd-compile preRun.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 13:40:46 +03:00
Nick Shirokov 334241bea4 fix(skd-info): handle absolute -OutFile paths correctly
Раньше PS1-порт делал `Join-Path (Get-Location) $OutFile` без проверки,
что приводило к невалидным склейкам типа `C:\cwd\C:\abs\path.txt`, и
запись падала с «The given path's format is not supported».

Теперь: если путь абсолютный — нормализуется через `Path::GetFullPath`,
если относительный — резолвится против CWD. Python-порт уже был корректен,
только version bump.

Дополнительно: `args_extra` в runner.mjs теперь поддерживает подстановку
`{workDir}` — нужно для тестов с абсолютными путями внутри workspace.

Тесты: `skd-info/outfile-absolute-cyrillic` (PS + Python).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 10:19:20 +03:00
Nick Shirokov ce1ba0bab1 feat(skd-edit): normalize line endings + diagnostics on patch-query not-found
patch-query теперь нормализует CRLF/CR → LF в old/new/query перед поиском,
поэтому многострочные шаблоны с любым стилем переводов строк находятся
корректно (XmlDocument декодирует text-узлы как LF).

При not-found вместо сухого сообщения выводится воронка диагностики:
  1) cross-dataset probe — «Found in dataset 'Y' instead — wrong -DataSet?»
  2) tolerant probe (collapse whitespace + NBSP) — «would match with
     whitespace normalized» + точка расхождения
  3) prefix divergence — «matched N of M chars, expected 'X' (U+...) but
     got 'Y' (U+...)» + короткий контекст

Тесты: 4 новых кейса (positive CRLF-tolerant + 3 диагностических negative).
Регрессия 45/45 PS + 45/45 Python.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 19:53:24 +03:00
Nick Shirokov 6e14f2502e feat(skd-edit): empty parameter values, decimal/time/fix/composite
Brings skd-edit to parity with the skd-compile fixes from 449f814 / 0537410
/ ff2d851. Same helpers (Test-EmptyValue / Build-EmptyValueXml in ps1,
is_empty_value / build_empty_value_xml in py) shared by add-parameter,
modify-parameter (value=...), availableValues, add-dataParameter and
modify-dataParameter.

Behavior:
- Sentinel empty (null / "" / "_" / "null") serializes per declared type,
  matching what 1C Designer writes — ref/no-type → xsi:nil, string →
  xsi:type="xs:string" empty, date/time/decimal/boolean → typed zero,
  StandardPeriod → Custom + zero dates, dataParameters → dcscor:value
  xsi:nil="true". @valueList omits <value> entirely.
- Build-ValueTypeXml accepts bare decimal (10,2), decimal(N) (N,0),
  string(N,fix) (AllowedLength=Fixed), time (DateFractions=Time), and
  composite array of types.
- Parse-ParamShorthand / Parse-DataParamShorthand regex .+ → .* so a
  trailing `=` is treated as the empty-value sentinel. New @valueList flag.

New test cases: empty-param-values-add / -modify / empty-dataparam-values.
Three outdated skd-edit snapshots regenerated to reflect upstream skd-compile
empty-value emission (rename-parameter, reorder-parameters,
conditional-appearance-v2).

Regression: 41/41 ps1 + 41/41 py runner; 41/41 verify-snapshots ps1 + py
(live load into 1С 8.3.24). skd-compile 23/23 and skd-validate 15/15
unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 13:38:52 +03:00
Nick Shirokov ff2d8513c4 feat(skd-compile): time type, string(N,fix), and composite type parameters
Calibrated against live Designer output in upload/erf/ПроверкаЭкранирования.

- New type 'time' (synonym 'время'): xs:dateTime with DateFractions=Time
  for time-of-day values. Designer uses the same xs:dateTime XSD type as
  date/dateTime — only DateFractions differs. Empty value: typed-zero
  0001-01-01T00:00:00 (same as dateTime).

- Extended string regex to accept (N,fix) → AllowedLength=Fixed (was
  Variable-only). Non-empty fixed-string values are emitted as-given
  without space-padding to Length — the platform handles padding on save.

- Composite types in parameters (array of types in object form, e.g.
  ["string(10,fix)", "CatalogRef.X"]) now work end-to-end: valueType
  emits each type with its qualifiers, and empty composite values
  serialize as <value xsi:nil="true"/> matching Designer.

Test case empty-param-values extended with 5 new params covering all
three additions. Snapshot validated by skd-validate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 12:52:26 +03:00
Nick Shirokov a5a1636918 feat(skd-validate): catch broken XDTO in valueType and value
skd-validate was purely structural (names/refs/duplicates) and missed an
entire class of bugs that XDTO rejects at db-load-xml — exactly the
kinds of mistakes the LLM (or hand-edits) commonly introduce.

New section 16: valueType structural — each <v8:Type> must have a known
prefix (xs:/v8: or any prefix bound to enterprise/current-config),
qualifier blocks must match their preceding type, and qualifier
internals (Digits/FractionDigits/AllowedSign, Length/AllowedLength,
DateFractions) must use legal tokens.

New section 17: value content — <value xsi:type="dcscor:DesignTimeValue">
rejects literal placeholders ('_') and empty strings, since these are
the exact symptom of the titan team's BUG-2.

5 new fixtures cover: bare-decimal, missing-qualifiers,
qualifier/type mismatch, ref-literal '_', bad AllowedSign token.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 11:57:20 +03:00
Nick Shirokov 05374100c1 fix(skd-compile): accept bare decimal and decimal(N) with sensible defaults
Emit-SingleValueType / emit_single_value_type previously required full
decimal(D,F) — anything else fell through to a fallback that produced
invalid <v8:Type>decimal</v8:Type> (no xs: prefix, no qualifiers).

New regex `^decimal(\((\d+)(,(\d+))?(,nonneg)?\))?$` accepts:
- decimal                → 10,2,Any (money default — most common 1C intent)
- decimal(N)             → N,0,Any (integer)
- decimal(N,nonneg)      → N,0,Nonnegative
- decimal(N,M)           → as before
- decimal(N,M,nonneg)    → as before

Synonyms (число, число(N), etc.) inherit the same forms via Resolve-TypeStr.

Shared Emit-ValueType is called from fields, parameters, and output
parameters — one fix covers all three paths. 3 existing snapshots
regenerated with proper xs:decimal + qualifiers, plus new
decimal-qualifier-defaults test case covering all 5 forms × synonyms.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 11:45:23 +03:00
Nick Shirokov 449f814d16 fix(skd-compile): Designer-compatible empty parameter values
Centralized empty-value handling: shorthand `=`, `= _`, `= null` and
object-form `value: null` / `""` now serialize per type, matching what 1C
Designer writes:
- ref / no-type → <value xsi:nil="true"/>
- string → <value xsi:type="xs:string"/>
- date/decimal/boolean → typed zero (0001-01-01 / 0 / false)
- StandardPeriod → Custom variant with zero dates
- @valueList → omit <value> entirely

Closes BUG-1 (StandardPeriod @autoDates) and BUG-2 (CatalogRef.X = _
producing invalid <value>_</value>) reported by titan team. New helpers
Test-EmptyValue / Emit-EmptyValue (ps1) and is_empty_value /
emit_empty_value (py) shared by Emit-ParamValue, availableValues loop,
and explicit dataParameters emit. Shorthand regex .+ → .* so trailing
`=` parses as empty.

Reference: upload/erf/ПроверкаЭкранирования (live Designer dump).
New test case empty-param-values covers all 10 type×sentinel combos;
3 existing snapshots regenerated to include the now-correct <value>
tags.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 11:27:24 +03:00
Nick Shirokov 3eaa7ffa3b fix(skd-edit): drop unneeded " → &quot; in query/expression
Зеркалим решение из skd-compile: убираем .Replace('"','&quot;') из Esc-Xml
и удаляем post-process, который принудительно ставил &quot; внутри
<query>/<expression>. Реальный Конфигуратор так не пишет — экранирование
было анти-1С-стилем и портило round-trip diff.

Снимок add-calculated-field-restrict обновлён под новый формат.
Кейс preserve-entities-modify-parameter-title удалён: его смысл
инвертировался (теперь проверял бы нормализацию, а не сохранение),
а часть про многострочный xmlns уже покрыта preserve-xmlns-multiline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 16:51:04 +03:00
Nick Shirokov 98ebb478ee fix(skd-compile): drop unneeded " → &quot; — matches Designer style
Конфигуратор внутри текстового контента <query>/<expression> оставляет " сырыми
(проверено на ERP DCS: 1504 raw " против 0 &quot;). Убираем .Replace('"','&quot;')
из esc_xml — теперь round-trip diff против типовых остаётся чистым.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 16:50:55 +03:00
Nick Shirokov fb67b1b80d fix(skd-edit): realistic multilang fixture (ERP-style appearance block)
multilang-base/Template.xml содержал <editFormat xsi:type="v8:LocalStringType">
на <field xsi:type="DataSetFieldField">, что нелегально по XDTO-схеме DCS —
1С Designer падал с "Исключение XDTO" при загрузке через
LoadExternalDataProcessorOrReportFromFiles. Snapshot-тесты этого не ловили
(только byte-equality), а platform-verify (tests/skills/verify-snapshots.mjs)
ронялся на трёх кейсах с этой фикстурой.

Заменил <editFormat> на реалистичный <appearance> блок с вложенным
<dcscor:item xsi:type="dcsset:SettingsParameterValue"> и многоязычным
<dcscor:value> (ru + en) — структура взята из типовой ERP-выгрузки. Это
даёт более правильный test для preserve-unknown-children: <appearance>
содержит вложенный multi-lang xsi:type-узел, который точно прошёл бы
через DOM round-trip с искажениями, если бы _unknownChildren не работал.

preserve-unknown-children-modify-field: shorthand изменён с
"@ignoreNullsInGroups" на "@dimension" (no-op по составу role, но
триггерит rebuild). Прежний @ignoreNullsInGroups без @dimension давал
комбинацию, которую Designer отвергает (ignoreNullsInGroups валиден
только в контексте resource-роли).

39/39 snapshot suite (PS+PY) + 39/39 platform verify через erf-build →
Designer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 15:19:13 +03:00
Nick Shirokov 79db5de6ee fix(skd-edit): preserve multi-lang title + unknown children in modify-*
В типовых конфигурациях (ERP, БП, ЗУП и т.д.) у полей и параметров обычно
есть мульти-язык title (ru + en, иногда + локализация). До этого modify-field /
modify-parameter / modify-dataParameter, перестраивая элемент через
Build-MLTextXml, оставляли только последнее найденное <v8:content> в ru —
en/uk/kk siblings молча терялись, и при следующей выгрузке Designer
ломал миграцию.

Read-FieldProperties сохраняет полный OuterXml <title> в _rawTitle и
коллекционирует OuterXml неизвестных дочерних элементов
(<editFormat>, <appearance>, кастомные расширения) в _unknownChildren.
Build-FieldFragment эмитит:
* _rawTitle как есть, если user не задал новый title;
* Patch-MLTextRu(_rawTitle, newRu) если user задал ru-override — патчит
  только <v8:content> в <v8:lang>ru</v8:lang>, остальные языки сохраняет;
* _unknownChildren в конце поля (после valueType).

modify-parameter аналогично: при title-override проверяет multi-lang
(>1 <v8:item>) и патчит ru через Patch-MLTextRu, иначе ребилдит ru-only.

set-field-role сохраняет нестандартные подэлементы <role> (например
<dcscom:addition>, <dcscom:groupFields>), не входящие в фиксированный
known-children set и не указанные через kv в shorthand.

xmlns-стрип на захваченных OuterXml — лишние декларации (которые сериализаторы
добавляют для standalone-фрагментов) убираются.

PY: lxml etree.tostring по умолчанию включает .tail (whitespace после
закрывающего тега), что приводило к non-idempotent ростy whitespace при
повторных прогонах. Везде добавлен with_tail=False.

Новые тесты с idempotent: true:
* preserve-multilang-modify-field (ru-override на multi-lang title);
* preserve-multilang-modify-parameter (то же для параметра);
* preserve-unknown-children-modify-field (role flag, проверяем что
  <editFormat> и en title не теряются).

Общая fixture: multilang-base/Template.xml с полем и параметром,
у каждого ru + en title; поле также имеет <editFormat>.

39/39 PS + 39/39 PY. skd-edit v1.20 -> v1.21.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 15:18:07 +03:00
Nick Shirokov 23d2cb42de fix(skd-edit): preserve <valueType>, detect line endings, drop CRLF leak
Targeted follow-ups к round-trip фиксу:

* modify-field больше не теряет <valueType> при перестройке поля —
  Read-FieldProperties сохраняет полный OuterXml элемента (StringQualifiers,
  NumberQualifiers, DateQualifiers и т.п.), Build-FieldFragment отдаёт его
  обратно. Лишние xmlns-декларации, добавляемые сериализатором при
  выгрузке поддерева, стрипаются регексом.
* Line-ending convention теперь определяется при load (CRLF vs LF) и
  единообразно применяется в финале save. Раньше CreateWhitespace и
  Build-*Fragment везде использовали CRLF, что приводило к смешанным
  переносам в LF-исходниках (и наоборот) и к non-idempotent выходу
  modify-parameter title (run 1 → \n\t\t<title>\r\n... → run 2 →
  \r\n\t\t<title>\r\n...).
* PS Insert-BeforeElement переведён на LF; все -join "`r`n" → "`n";
  py "\r\n".join → "\n". Конечная нормализация переносов делается в
  save в соответствии со script:LineEnding.
* preserve-entities-modify-parameter-title.json теперь idempotent: true
  (после фикса CRLF leak'а двойной прогон byte-identical).

На реальной схеме diff после modify-field составил 30 строк: целевая
вставка title плюс полезная одноразовая коррекция ранее повреждённых
&quot; в text-content <dcsat:expression>. modify-field идемпотентен.

skd-edit v1.19 -> v1.20.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 15:18:07 +03:00
Nick Shirokov 511bfe7fdf fix(skd-edit): NO-OP skip + format-preserve post-process (round-trip)
XmlDocument round-trip искажал Template.xml даже при отсутствии правок:
декодировал &quot; в <query>/<expression>, схлопывал многострочный xmlns
корня, добавлял пробел перед /> и записывал файл при [WARN] not found.

Дирти-флаг ($script:Dirty / dirty) ставится только на успешной мутации;
финальный save пропускается с [INFO] No changes -- file untouched, если
ни одна операция в batch ничего не изменила. Post-process после OuterXml
восстанавливает raw-форматирование корневого xmlns из исходного файла,
re-escape `"` в текстах <query>/<expression> с anchored regex (не задевая
xsi:type="..."), и нормализует <foo .../> к <foo.../>.

Замеры на реальной схеме после modify-field: diff упал с 423 строк до 37
(94% шума устранено), повторный прогон byte-identical.

В runner.mjs добавлен caseData.idempotent: re-run + byte-equality на всех
файлах workDir. Три новых кейса (NO-OP, entity-preserve, xmlns-multiline)
+ общий fixture roundtrip-base. Все 33 ранее существовавших snapshot
перегенерированы под корректное форматирование (восстанавливают то, что
старый skd-edit ломал).

skd-edit v1.18 -> v1.19. PS и PY порты синхронизированы.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 15:17:42 +03:00
Nick Shirokov 7fa279c354 feat(skd-edit): clear-conditionalAppearance + multiline patch-query (доки)
- Новая операция clear-conditionalAppearance в стиле clear-selection/
  order/filter. Закрывает потребность "заменить набор правил оформления"
  через clear + re-add.
- patch-query: многострочные подстроки уже работали (string.Replace
  корректно обрабатывает \n). Зафиксировано в SKILL.md.
- add-total: shorthand-шаблон с тремя случаями (Func, Func(expr),
  identity-выражение) — после fix Bug 6 поведение нужно явно объяснить.
- Косметика: убрана утечка XML-внутренностей в комментарии примера
  set-field-role @period.
- Пример patch-query @once заменён на более типовой случай уникальной
  подстроки (КАК ВТ_СтароеИмя вместо ЛЕВОЕ СОЕДИНЕНИЕ).

Регресс: 33/33 PS, 33/33 PY, 33/33 платформенный verify.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 17:39:24 +03:00
Nick Shirokov 28a2a34c84 fix(skd-edit): add-total identity expression для не-аггрегатных функций
Раньше "DataPath: X" всегда заворачивалось в X(DataPath). Если X не
аггрегатная функция (например, имя другого ресурса или сам DataPath),
получалось некорректное выражение типа Проверка(Проверка).

Зеркалю логику из skd-compile: whitelist аггрегатных функций
(Сумма, Количество, Минимум, Максимум, Среднее + EN-варианты).
Для остального — identity (использовать funcPart как есть).

Сообщение [OK] теперь показывает фактически записанный expression.

Регресс: 32/32 PS, 32/32 PY, 32/32 платформенный verify.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 16:16:27 +03:00
Nick Shirokov f0f1e88aaa feat(skd-edit): patch-query @once — assert ровно одно вхождение
Защищает от случайных замен в комментариях/совпадениях имён:

  "ЛЕВОЕ СОЕДИНЕНИЕ => ВНУТРЕННЕЕ СОЕДИНЕНИЕ @once"
  # fail, если в запросе 0 или 2+ вхождений

Без флага default — replace-all (как раньше, обратная совместимость).

При успехе сообщение содержит фактическое число вхождений
"(N occurrence(s))", помогает заметить неожиданную множественность
без явного @once.

Регресс: 31/31 PS, 31/31 PY, 31/31 платформенный verify.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 16:00:55 +03:00
Nick Shirokov e7cbf306a0 feat(skd-edit): availableValue — список с replace-семантикой и в add-parameter
- Единый list-синтаксис: availableValue=v1[: p1], v2[: p2], ...
  Элементы через запятую, представление после двоеточия.
- Запятые/двоеточия внутри значений и представлений — в одинарных кавычках:
  availableValue=Окр1: 'руб., коп.', Окр1000: руб.
- add-parameter теперь принимает availableValue= и создаёт начальный список
  в одном вызове (раньше требовался последующий modify-parameter).
- modify-parameter availableValue=... ЗАМЕНЯЕТ весь список (раньше
  append). Согласуется с остальными modify-* для одиночных свойств.
- SKILL.md: добавлен shorthand-шаблон для modify-parameter,
  расширен для add-parameter [availableValue=список].

Существующие тесты мигрированы со старого ;;-batch на новый list-синтаксис.
Снапшоты сохранились (тесты стартовали с пустого списка — semantics
совпадает для greenfield).

Регресс: 29/29 PS, 29/29 PY, 29/29 платформенный verify.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 15:37:56 +03:00
Nick Shirokov 5090deb5bc feat(skd-edit): set-field-role — управление ролями поля
Новая операция: полная замена <role>-блока поля dataSet.

- Shorthand: "<dataPath> [@флаги] [kv=значение]"
- Флаги (зеркало skd-compile): @balance, @dimension, @account, @period,
  @required, @autoOrder, @ignoreNullValues
- KV: balanceGroupName, balanceType, parentDimension, accountTypeExpression,
  orderType, expression, periodNumber, periodType
- Пустой spec (только dataPath) — снимает роль целиком
- Поддерживает пакетный режим

Закрывает потребность временного toggle off/on роли при отладке
(было: ручной Edit XML), а также корректировку balance/dimension
после add-total.

Регресс: 27/27 PS, 27/27 PY, 27/27 платформенный verify.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 14:53:55 +03:00
Nick Shirokov 8b0bcf0194 feat(skd-edit): флаги @hidden и @always для параметров
- @hidden — скрывает параметр от пользовательских настроек
  (useRestriction=true + availableAsField=false). Для констант-параметров.
- @always — параметр всегда подставляется в запрос (use=Always).
  Используется самостоятельно для видимых обязательных параметров.
- Композируются: @hidden @always одной строкой даёт типовой паттерн
  "скрытая константа всегда применяется".
- Поддержка в add-parameter и modify-parameter, идемпотентны.

Регресс: 25/25 PS, 25/25 PY, 25/25 платформенный verify.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 14:14:19 +03:00
Nick Shirokov 529a5cacae feat(skd-edit): modify-structure + фиксы set-structure/parameter/patch-query
- modify-structure: новая операция, меняет groupItems группы по @name=,
  сохраняя Selection/order/filter/conditionalAppearance (Bug 1)
- set-structure: shorthand поддерживает запятую для нескольких полей
  в одном уровне группировки (Bug 2)
- set-structure: @name= с обрамляющими кавычками (двойными/одинарными)
  снимает их при записи в <dcsset:name> (Bug 3)
- add-parameter: ссылочные типы (CatalogRef, ChartOfAccountsRef, …)
  пишут <value xsi:type="dcscor:DesignTimeValue">, не xs:string (Bug 4a)
- modify-parameter: namespace-aware lookup существующих свойств
  — обновляет inplace, не плодит дубли (Bug 4b)
- modify-parameter value=…: пересборка <value> с корректным xsi:type
  из <valueType> (попутно лечит ранее битый XML)
- patch-query: батч ;;-сегментов триммится по краям (Bug 5)
- skd-compile: симметричный фикс ссылочных типов в emit_value

Регресс: 23/23 PS, 23/23 PY (skd-edit), 21/21 PS+PY (skd-compile),
23/23 платформенный verify-snapshots.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 13:40:49 +03:00
Nick Shirokov 8b0f55f1cc feat(form-validate): silent-skip числовых и UUID-DataPath в Check 5
В реальных выгрузках ERP/БП встречаются непрозрачные платформенные
DataPath, которые невозможно проверить из одного Form.xml:
- bare numeric ("10", "1000003") — внутренние индексы платформы
- "N/M:<uuid>" — ссылка на метаданные по UUID

Раньше Check 5 ругался на них "attribute not found". Теперь такие
пути пропускаются без счёта в paths checked и без ошибки.

Реалистичные пользовательские опечатки (кириллица в имени атрибута)
продолжают ловиться обычной проверкой attrMap.

Добавлен тест-кейс datapath-opaque-refs, версия v1.5 → v1.6.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 11:40:27 +03:00
Nick Shirokov 54cbc69a59 feat(form-validate): резолв Items.<Table>.CurrentData.* и ~<Attr>.* в DataPath
В Check 5 раньше брался первый сегмент DataPath и искался в attrMap,
из-за чего ложно ругались реальные формы ERP/БП с путями вида
Items.<TableName>.CurrentData.<Field> (подвалы, инфо-панели) и
~<DynamicListAttr>.<Field> (текущая строка списка).

Теперь:
- ведущий ~ стрипается перед разбором сегментов;
- для Items.<Table>.CurrentData.* находим элемент-таблицу по name,
  берём её <DataPath> (атрибут DynamicList/TableSection) и проверяем
  его в attrMap. Если таблицы нет — Error; если третий сегмент не
  CurrentData — Warn.

Добавлен тест-кейс datapath-currentdata, версия скриптов v1.4 → v1.5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 11:38:56 +03:00
Nick Shirokov b992cd11c5 feat(web-test): _allure/ конвенция + categories.json для триажа падений
run.mjs:
- syncAllureExtras(testDir, reportDir) копирует все файлы из
  <testDir>/_allure/ в reportDir перед генерацией отчёта. Underscore
  в имени параллелен _hooks.mjs (инфра, не тест) — discovery его
  пропускает.
- Вызов после writeAllure при --format=allure.

tests/web-test/_allure/categories.json — 7 правил классификации падений
по нашему 1С-домену:
  1. License pool exhausted (1C) — известный multi-context flake.
  2. 1C application error (modal) — exception modal через fetchErrorStack.
  3. Section panel icon-only — деградация состояния стенда.
  4. Navigation lookup miss — navigateSection/openCommand/navigateLink/switchTab.
  5. Element not found — clickElement/fillField/selectValue/closeForm/fillTableRow/deleteTableRow.
  6. Test timeout — Timeout (Nms) от раннера.
  7. Assertion failure — наши createAssertions + 1С-specific (formHasField/tableHasRow/noErrors).

spec §9: раздел «Доп. файлы Allure через <testDir>/_allure/» с таблицей
поддерживаемых типов (categories.json / environment.properties /
executor.json) и минимальным примером.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 18:53:09 +03:00
Nick Shirokov fc76407877 feat(web-test): auto-suite + severity-резолвер для Allure
run.mjs:
- buildSeverityIndex(config) — валидация config.severity (inverted map
  «уровень → [теги]») при загрузке: ключи только из blocker|critical|
  normal|minor|trivial, теги не дублируются между bucket'ами,
  defaultSeverity тоже валидируется. fail-fast через die.
- resolveSeverity(t, severityIndex):
  1. mod.severity если задан и валидный — выигрывает.
  2. max-rank среди тегов (стандартные имена severity или маппинг).
  3. config.defaultSeverity или 'normal'.
  Rank: blocker(5) > critical(4) > normal(3) > minor(2) > trivial(1).
  Max-wins инвариантен к порядку тегов.
- writeAllure: добавлены labels suite (= dirname(t.file) или 'root') +
  severity. Тег `tag` остался как раньше.
- testResult пробрасывает t.severity (для passed/failed веток).
- SEVERITY_RANK/LEVELS объявлены в модульной шапке (top-level await на
  cmdTest начинается до конца тела модуля, TDZ-аккуратность).

webtest.config.mjs: severity policy для нашего сьюта (smoke +
multi-context → critical, recording → minor, defaultSeverity = normal).

spec.md §7: раздел про severity-policy в конфиге с валидацией.
spec.md §9: «Авто-эмиссия label-ов» — tag/suite/severity + правила резолва.

Регресс 19/19 ✓ (9m 7.6s). Распределение по уровням после исправления
'record' → 'recording' в маппинге: 13 critical / 5 normal / 1 minor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 18:37:58 +03:00
Nick Shirokov eb87be5c04 feat(web-test): M8 — per-context lifecycle (closeContext + afterOpenContext/beforeCloseContext)
browser.mjs:
- + closeContext(name): logout slot + close page (tab) или context (window),
  удаление из реестра. Throw если name неактивен (рулило: nicht den aktiven
  closen, recorder always attached к active → invariant простой).
- _logoutSlot(slot, waitMs) — извлечён из disconnect, переиспользуется в
  closeContext.

run.mjs:
- ensureContext() после createContext вызывает hooks.afterOpenContext(ctx, name, spec).
- wrapCloseContextHook() оборачивает ctx.closeContext (и каждую scoped-обёртку)
  чтобы перед browser.closeContext fir'ить hooks.beforeCloseContext.
- Финальный teardown в finally: для всех живых контекстов кроме первого
  (survivor) — beforeCloseContext + closeContext; для survivor только хук,
  его закрывает disconnect().

_hooks.mjs v0.5:
- afterOpenContext инжектит persistent DOM-badge с displayName в правый
  верхний угол page — в записанном видео всегда видно, какой контекст.
- beforeCloseContext counter-only.
- _state расширен полями afterOpenContext / beforeCloseContext.

15-multi-context-handover.test.mjs:
- +2 шага: closeContext('b') после handover, попытка closeContext(active)
  ловится throw'ом с проверкой message.

00-hooks.test.mjs:
- +1 ассерт: afterOpenContext >= 1 (default уже создан), beforeCloseContext === 0
  в теле первого теста.

spec §6:
- Раздел «Контекстный уровень» (afterOpenContext / beforeCloseContext + правила closeContext).
- ASCII-диаграмма порядка хуков обновлена с per-context lifecycle.

Регресс 19/19 ✓ (9m 16.8s).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 16:07:45 +03:00
Nick Shirokov 43ed9ba142 feat(web-test): M7.5 — title slide в beforeEach для --record
_hooks.mjs v0.4: beforeEach под условием ctx.isRecording() показывает
title slide с testInfo.name + displayName первичного контекста как
subtitle, ждёт 1.5с через ctx.wait() и убирает.

В обычном регрессе (без --record) — ветка скипается, overhead ноль.
Под --record: 01-navigation 12.1s → 13.9s (+1.8с на слайд).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 14:45:14 +03:00
Nick Shirokov 588382cec1 feat(web-test): M7.4 — testlevel-хуки + 00-hooks индикатор
_hooks.mjs v0.3: добавлены beforeAll/afterAll/beforeEach/afterEach
(counter-only) и shared `_state` (счётчики + events log).

tests/web-test/00-hooks.test.mjs (новый, 4 шага, 0s) — индикатор
порядка вызовов: проверяет beforeAll===1, beforeEach для текущего
теста, доступность ctx.testInfo, afterEach < beforeEach.

Multi-context хуки оставлены one-shot. Разведка beforeAll:
navigateSection не нужен, 1С после входа уже на дефолтной секции.

Регресс 19/19 ✓ (9m 12.7s).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 14:35:20 +03:00
Nick Shirokov e0197683e1 feat(web-test): M7.1+M7.2 — ctx.testInfo + проброс custom-полей контекстов
- ctx.testInfo (name/file/filePath/tags/timeout/attempt/maxAttempts/param/contexts/primaryContext)
  выставляется перед каждой попыткой, доступен в beforeEach/test/afterEach
- ctx.testResult (status/duration/attempts/error/steps) доступен в afterEach
- run.mjs:411 spread полного contextSpec (был whitelist {url, isolation});
  CLI --url override сохраняет custom-поля через merge
- webtest.config.mjs: displayName для a/b
- spec §3 — подраздел «Метаданные теста», §6 — availability testInfo/testResult,
  §7 — рекомендация латинский ID + кириллический displayName
- Full regression 18/18 ✓ (9m 9.8s)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 12:44:07 +03:00
Nick Shirokov 96dad75b2f feat(web-test): M6-MVP follow-up — 13-misc setup + URL webtest-runner
13-misc.test.mjs: setup-шаг упрощён до `assert.ok(existsSync(epfPath))`.
EPF-сборку (epf-init → form-add → form-compile → epf-build) забрал
_hooks.mjs.prepare() — здесь только проверка артефакта с понятной
ошибкой при отсутствии: «запустите раннер с `-- --rebuild-epf`».

webtest.config.mjs: URL обоих контекстов переключён на
`/webtest-runner/ru_RU` — отдельная публикация автономного стенда,
не конфликтует с интерактивной разведкой через `/webtest` на 8081.
2026-05-12 20:25:54 +03:00
Nick Shirokov 5c734202b6 feat(web-test): M6-MVP — автономный стенд через _hooks.mjs
Новый tests/web-test/_hooks.mjs v0.2 с prepare()/cleanup().
prepare() поднимает изолированный стенд:
- Hash-locks `tests/skills/.cache/webtest-stand/{config,epf}.lock`
  на sha256 от build-steps и EPF_SPEC — автоматический skip
  пересборки при отсутствии изменений.
- Слои конфиг XML / БД / EPF пересобираются независимо. Триггер
  ручной — флаги `--rebuild-config`/`--reload-data`/`--rebuild-epf`/
  `--rebuild-stand` (через `-- ...` после CLI раннера).
- Smart Apache: web-stop+web-publish выполняются только когда
  пересоздаём БД (нужно освободить блокировку). Иначе probe-first:
  жив (200) → no-op; мёртв → publish + probeReady. На warm-старте
  prepare сводится к чтению локов и одному probe (~200ms).
- web-publish на собственном AppName `webtest-runner` :9191 — не
  пересекается с интерактивной публикацией `webtest`.
- Кросс-платформенно: env WEBTEST_HOOKS_RUNTIME=python переключает
  на зеркальные py-порты скиллов (для не-Windows стендов).

cleanup() пока stub — оставляем стенд поднятым между прогонами,
для full-shutdown ручной /web-stop или `-- --rebuild-stand`.

E2E-проверено: cold-start `--rebuild-stand` поднимает стенд за
~38s; warm-старт prepare = 0.0s; полный регресс 18/18 зелёный
за 9m 7.1s (включая оба multi-context-теста, которые исторически
флапали).
2026-05-12 20:25:47 +03:00
Nick Shirokov b8ebbf6a6f feat(build-webtest-db): v0.2 — dual-mode CLI + module exports
Извлечены exports: getProjectInfo, resolveScript, execSkill,
replacePlaceholders, runSteps, platformLoadSteps, loadBuildSteps.
CLI-режим сохранён через import.meta.url-guard. Подготовка к
переиспользованию из tests/web-test/_hooks.mjs без дублирования
exec-логики и pipeline-шагов.
2026-05-12 20:25:25 +03:00
Nick Shirokov 43ba6ce16c feat(web-test): M5-pre #4b — 09-filter/unfilter-specific (multi-badge)
Раньше шаг был deferred с комментарием «требует список с видимой
filter-панелью». На самом деле существующая абстракция работает:
два advanced filterList на разных колонках Контрагентов создают
два badge'а в state.filters[], а unfilterList({field}) снимает
конкретный — оставляя остальные.

Новый шаг 09-filter/unfilter-specific (~14s):
- filterList('ООО', {field:'Наименование'}) + filterList('123', {field:'ИНН'})
  → state.filters = [{field:'Наименование',value:'ООО'}, {field:'ИНН',value:'123'}]
- unfilterList({field:'ИНН'}) → остался только Наименование badge
- unfilterList() → пусто

Старый комментарий «defer to filter-panel synthetic» удалён —
оказался устаревшим (видимо unfilterList({field}) уже умел работать
с advanced-filter badge'ами на синтетических списках).

timeout 09-filter поднят с 60000 → 120000ms (8 шагов теперь, +14s
для unfilter-specific).

Регресс: 16/18 зелёных. Два multi-context-теста (14/15) упали на
лицензионном пределе 1С — known environmental issue, не связано с
этим коммитом.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:01:00 +03:00
Nick Shirokov 51e37f9874 feat(web-test): M5-pre #4a — Менеджер (choiceHistoryOnInput=Auto) + selectValue/auto-history
Реквизит шапки ПриходнаяНакладная.Менеджер типа CatalogRef.Контрагенты
с дефолтным choiceHistoryOnInput=Auto. Существующий Контрагент в той же
шапке имеет DontUse, что даёт парный контраст для тестирования влияния
флага на selectValue.

Новый шаг 04-selectvalue/auto-history:
- selectValue('Менеджер', 'ООО Юг') → method='dropdown' (typeahead активен,
  префиксный поиск по Description находит «ООО Юг» в catalogue).
- Парный 04-selectvalue/direct-form (existing): selectValue('Контрагент',
  'Север') → method='form' (typeahead подавлен DontUse → форма выбора).

Тест покрывает существующее ветвление selectValue по флагу
choiceHistoryOnInput без engine-доработок. Истории на сервере писать
заранее не нужно: typeahead использует prefix-match по Description,
а не статистику истории.

Полный регресс **18/18 зелёный** (8m 47.3s).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 16:56:00 +03:00
Nick Shirokov 62e864e474 feat(web-test): M5-pre #3 — textEdit:false поле + 03-fillfields/direct-edit-form
Расширение синтетики: реквизит Поставщик типа CatalogRef.Контрагенты
добавлен в шапку ПриходнаяНакладная. Элемент формы Поставщик скомпилирован
с textEdit:false (новый DSL ключ form-compile v1.21 из коммита 32bf9c1):
ручной ввод запрещён, селект-кнопки нет, выбор только через форму выбора
по pick-кнопке.

Новый шаг 03-fillfields/direct-edit-form (~7s) — fillFields на Поставщик
('ООО Юг') возвращает method:'form', минуя обычные paste/typeahead/dropdown
ветки. fillFields внутренне детектит textEdit:false и сразу идёт через
форму выбора (selectValue path).

Полный регресс **18/18 зелёный** (8m 40.6s).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 16:11:46 +03:00
Nick Shirokov ddebd7b6df feat(web-test): M5-pre #2 — составной тип Источник + 03-fillfields/composite
Расширение синтетики: реквизит Источник составного типа
(CatalogRef.Контрагенты + CatalogRef.Номенклатура + CatalogRef.Организации)
добавлен в шапку ПриходнаяНакладная и в ТЧ Товары. meta-compile принимает
составной тип через строковый синтаксис `A + B + C` (см. SKILL.md:56) —
эмитит три `<v8:Type>` элемента с правильным `d5p1:` префиксом.

Элемент ТЧ-колонки переименован в ИсточникТЧ (path/title оставлены
оригинальные) — иначе form-compile генерирует одинаковые companion-имена
(`ИсточникКонтекстноеМеню`) для шапки и ТЧ, и платформа отказывает в
открытии формы документа: "К сожалению, возникла непредвиденная ошибка"
(server-side, без полезного stack). TODO в form-compile-bugs.md: учитывать
путь поля при генерации companion-имён, чтобы избежать конфликта.

Новый шаг 03-fillfields/composite (~25s) — покрывает selectValue с
параметром `{type}` на составном поле:
- Шапка: selectValue('Источник', 'ООО Север', {type:'Контрагенты'})
  → method:'form', type:'Контрагенты', выбор через каталог-форму.
- ТЧ: fillTableRow({Источник: {value:'Альфа', type:'Организации'}},
  {row:0}) → method:'form', type:'Организации' (quickChoice=true →
  без формы выбора, прямой dropdown).

fillFields на composite без type выбрасывает понятную ошибку
с инструкцией «specify the type: selectValue(...,{type:'ИмяТипа'})» —
поведение API стабильно.

timeout 03-fillfields поднят с 60000 → 120000ms (6 шагов суммарно
~63s, новый composite step добавляет ~25s).

Полный регресс **18/18 зелёный** (8m 28.7s).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 15:51:41 +03:00
Nick Shirokov 3d16e35e80 feat(web-test): M5-pre #1 — ValueTree + ДеревоНоменклатуры + tree-form smoke
Расширение синтетики: новая обработка ДеревоНоменклатуры с реквизитом
формы Дерево типа ДеревоЗначений и колонками Номенклатура (ссылка,
read-only) + Цена (Number, editable). ПриСозданииНаСервере рекурсивно
обходит Справочник.Номенклатура и заполняет дерево, отражая иерархию
групп/элементов из справочника.

Обработка зарегистрирована в подсистеме Администрирование и в роли
Администратор (Use+View).

Новый smoke 16-tree-form.test.mjs (5 шагов, 17.1s) — покрывает
05-table/edit-form (fillTableRow method:'direct' на FormDataTree-колонке)
и 08-hierarchy/tree-edit (expand узла + правка Цены через index-row):
- setup: navigateLink('Обработка.ДеревоНоменклатуры'), таблица Дерево
- read-roots: 2 корневые группы (_kind:'group'), columns=Номенклатура,Цена
- expand: clickElement('Товары',{expand:true}) → 16 строк (1 + 15)
- tree-edit: fillTableRow({Цена:1500},{row:1}) → method:'direct',
  Цена становится '1 500,00' (с non-breaking space 1С)
- cleanup: closeForm

Гэп: fillTableRow с row-by-name ('Товар 01') ловит SyntaxError в JS
eval. Использую row-by-index (TODO в web-test-bugs).

Полный регресс **18/18 зелёный** (8m 9.8s) на порту 9191.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 14:03:28 +03:00
Nick Shirokov 56822c4533 test(web-test): switch webtest publication to port 9191
Чтобы не конфликтовать с интерактивной разработкой на основном
Apache (8081, занят сторонним проектом), регрессионный регресс
теперь использует отдельный httpd-процесс на порту 9191. Тот же
httpd запускает /web-publish webtest -Port 9191 -V8Path 8.3.24.

Один процесс Apache → собственный пул лицензий 1С. На 8081 другие
проекты — наши тесты их не блокируют и наоборот.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 14:03:15 +03:00