From 1ed23b2a0882a01a1c65148d55003a7d5fefb8b9 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Tue, 9 Jun 2026 16:51:14 +0300 Subject: [PATCH] =?UTF-8?q?feat(form-decompile,form-compile):=20=D1=87?= =?UTF-8?q?=D0=B0=D1=81=D1=82=D0=B8=D1=87=D0=BD=D0=B0=D1=8F/=D0=BC=D0=B8?= =?UTF-8?q?=D0=BD=D0=B8=D0=BC=D0=B0=D0=BB=D1=8C=D0=BD=D0=B0=D1=8F=20=D1=84?= =?UTF-8?q?=D0=BE=D1=80=D0=BC=D0=B0=20ListSettings=20(=D0=B4=D0=B5=D1=81?= =?UTF-8?q?=D0=BA=D1=80=D0=B8=D0=BF=D1=82=D0=BE=D1=80=20settings.listSetti?= =?UTF-8?q?ngs)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Раундтрип TOTAL 21→0, match 153→156. Компилятор всегда эмитил ПОЛНЫЙ каноничный скелет (filter+order+conditionalAppearance+itemsViewMode+ itemsUserSettingID), а ~7% форм имеют частичный (напр. только с userSettingID) → лишние контейнеры = ADDED. - Декомпилятор: Get-ListSettingsShape фиксирует «форму» скелета в settings.listSettings (ordered-карта present top-level: filter/order/ conditionalAppearance → блок-мета 'v'/'u'/'vu'/''; itemsViewMode/ itemsUserSettingID → true). Дескриптор пишется ТОЛЬКО для не-каноничных форм ($null для полного канона и неподдержанных top-level item/dataParameters/…). - Компилятор: при наличии дескриптора эмитит ТОЛЬКО указанные части (контент из settings.filter/order/CA, блок-мета из дескриптора); иначе — полный канон (без изменений). Аддитивно, дескриптор-gated → 93% канон-форм не затронуты. Зеркало py. Формы ОстаткиАлкогольнойПродукцииЕГАИС (filter-only) и ОстаткиПартийЗЕРНО (filter+order) → ListSettings бит-в-бит. Регресс 39/39 (канон-путь). Партиал-путь — harness + provenance (подмножество канона). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../form-compile/scripts/form-compile.ps1 | 29 ++++++++++++---- .../form-compile/scripts/form-compile.py | 33 +++++++++++++++---- .../form-decompile/scripts/form-decompile.ps1 | 31 ++++++++++++++++- docs/form-dsl-spec.md | 4 ++- 4 files changed, 83 insertions(+), 14 deletions(-) diff --git a/.claude/skills/form-compile/scripts/form-compile.ps1 b/.claude/skills/form-compile/scripts/form-compile.ps1 index bf51422d..0494f2f8 100644 --- a/.claude/skills/form-compile/scripts/form-compile.ps1 +++ b/.claude/skills/form-compile/scripts/form-compile.ps1 @@ -1,4 +1,4 @@ -# form-compile v1.92 — Compile 1C managed form from JSON or object metadata +# form-compile v1.93 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$JsonPath, @@ -4711,11 +4711,28 @@ function Emit-Attributes { # Нет items → контейнеры всё равно эмитятся (blockMeta) = каноничный пустой скелет платформы. $lsi = "$si`t" X "$si" - Emit-Filter -items $st.filter -indent $lsi -blockViewMode 'Normal' -blockUserSettingID $script:CANON_FILTER_ID - Emit-Order -items $st.order -indent $lsi -blockViewMode 'Normal' -blockUserSettingID $script:CANON_ORDER_ID - Emit-ConditionalAppearance -items $st.conditionalAppearance -indent $lsi -blockViewMode 'Normal' -blockUserSettingID $script:CANON_CA_ID - X "$lsiNormal" - X "$lsi$($script:CANON_ITEMS_ID)" + if ($st.PSObject.Properties['listSettings'] -and $null -ne $st.listSettings) { + # Частичная/минимальная форма скелета — эмитим ТОЛЬКО указанные части с их блок-метой. + # meta: 'v'=viewMode, 'u'=userSettingID (контейнеры); itemsViewMode/itemsUserSettingID → present. + foreach ($prop in $st.listSettings.PSObject.Properties) { + $tag = $prop.Name; $meta = "$($prop.Value)" + $bvm = if ($meta -match 'v') { 'Normal' } else { $null } + switch ($tag) { + 'filter' { $bus = if ($meta -match 'u') { $script:CANON_FILTER_ID } else { $null }; Emit-Filter -items $st.filter -indent $lsi -blockViewMode $bvm -blockUserSettingID $bus } + 'order' { $bus = if ($meta -match 'u') { $script:CANON_ORDER_ID } else { $null }; Emit-Order -items $st.order -indent $lsi -blockViewMode $bvm -blockUserSettingID $bus } + 'conditionalAppearance' { $bus = if ($meta -match 'u') { $script:CANON_CA_ID } else { $null }; Emit-ConditionalAppearance -items $st.conditionalAppearance -indent $lsi -blockViewMode $bvm -blockUserSettingID $bus } + 'itemsViewMode' { X "$lsiNormal" } + 'itemsUserSettingID' { X "$lsi$($script:CANON_ITEMS_ID)" } + } + } + } else { + # Полный каноничный скелет (умолчание, ~93% форм) — без изменений. + Emit-Filter -items $st.filter -indent $lsi -blockViewMode 'Normal' -blockUserSettingID $script:CANON_FILTER_ID + Emit-Order -items $st.order -indent $lsi -blockViewMode 'Normal' -blockUserSettingID $script:CANON_ORDER_ID + Emit-ConditionalAppearance -items $st.conditionalAppearance -indent $lsi -blockViewMode 'Normal' -blockUserSettingID $script:CANON_CA_ID + X "$lsiNormal" + X "$lsi$($script:CANON_ITEMS_ID)" + } X "$si" X "$inner" } diff --git a/.claude/skills/form-compile/scripts/form-compile.py b/.claude/skills/form-compile/scripts/form-compile.py index d443ff77..e4683c85 100644 --- a/.claude/skills/form-compile/scripts/form-compile.py +++ b/.claude/skills/form-compile/scripts/form-compile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# form-compile v1.92 — Compile 1C managed form from JSON or object metadata +# form-compile v1.93 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import copy @@ -4414,11 +4414,32 @@ def emit_attributes(lines, attrs, indent): # Нет items → контейнеры всё равно эмитятся (blockMeta) = каноничный пустой скелет платформы. lsi = f'{si}\t' lines.append(f'{si}') - emit_filter(lines, s.get('filter'), lsi, block_view_mode='Normal', block_user_setting_id=CANON_FILTER_ID) - emit_order(lines, s.get('order'), lsi, block_view_mode='Normal', block_user_setting_id=CANON_ORDER_ID) - emit_conditional_appearance(lines, s.get('conditionalAppearance'), lsi, block_view_mode='Normal', block_user_setting_id=CANON_CA_ID) - lines.append(f'{lsi}Normal') - lines.append(f'{lsi}{CANON_ITEMS_ID}') + ls_shape = s.get('listSettings') + if ls_shape is not None: + # Частичная/минимальная форма скелета — эмитим ТОЛЬКО указанные части с их блок-метой. + for tag, meta in ls_shape.items(): + meta = str(meta) + bvm = 'Normal' if 'v' in meta else None + if tag == 'filter': + bus = CANON_FILTER_ID if 'u' in meta else None + emit_filter(lines, s.get('filter'), lsi, block_view_mode=bvm, block_user_setting_id=bus) + elif tag == 'order': + bus = CANON_ORDER_ID if 'u' in meta else None + emit_order(lines, s.get('order'), lsi, block_view_mode=bvm, block_user_setting_id=bus) + elif tag == 'conditionalAppearance': + bus = CANON_CA_ID if 'u' in meta else None + emit_conditional_appearance(lines, s.get('conditionalAppearance'), lsi, block_view_mode=bvm, block_user_setting_id=bus) + elif tag == 'itemsViewMode': + lines.append(f'{lsi}Normal') + elif tag == 'itemsUserSettingID': + lines.append(f'{lsi}{CANON_ITEMS_ID}') + else: + # Полный каноничный скелет (умолчание, ~93% форм) — без изменений. + emit_filter(lines, s.get('filter'), lsi, block_view_mode='Normal', block_user_setting_id=CANON_FILTER_ID) + emit_order(lines, s.get('order'), lsi, block_view_mode='Normal', block_user_setting_id=CANON_ORDER_ID) + emit_conditional_appearance(lines, s.get('conditionalAppearance'), lsi, block_view_mode='Normal', block_user_setting_id=CANON_CA_ID) + lines.append(f'{lsi}Normal') + lines.append(f'{lsi}{CANON_ITEMS_ID}') lines.append(f'{si}') lines.append(f'{inner}') diff --git a/.claude/skills/form-decompile/scripts/form-decompile.ps1 b/.claude/skills/form-decompile/scripts/form-decompile.ps1 index 20170a1a..6a9250ce 100644 --- a/.claude/skills/form-decompile/scripts/form-decompile.ps1 +++ b/.claude/skills/form-decompile/scripts/form-decompile.ps1 @@ -1,4 +1,4 @@ -# form-decompile v0.69 — Decompile 1C managed Form.xml to JSON DSL (draft) +# form-decompile v0.70 — Decompile 1C managed Form.xml to JSON DSL (draft) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью. param( @@ -113,6 +113,31 @@ function Test-ListSettingsHasContent { return $false } +# Форма ListSettings: ordered-карта present top-level элементов (filter/order/conditionalAppearance → +# блок-мета 'v'/'u'/'vu'/''; itemsViewMode/itemsUserSettingID → $true). Возвращает $null, если форма == +# полному каноничному скелету (компилятор регенерит сам) ИЛИ содержит неподдержанные top-level элементы +# (item/dataParameters/viewMode/userSettingID/… → fallback на канон). Иначе — дескриптор для компилятора. +function Get-ListSettingsShape { + param($lsNode) + if (-not $lsNode) { return $null } + $shape = [ordered]@{} + foreach ($child in $lsNode.ChildNodes) { + if ($child.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue } + $tag = $child.LocalName + if ($tag -in @('filter','order','conditionalAppearance')) { + $hasVM = $null -ne $child.SelectSingleNode("dcsset:viewMode", $ns) + $hasUS = $null -ne $child.SelectSingleNode("dcsset:userSettingID", $ns) + $shape[$tag] = "$(if ($hasVM) {'v'})$(if ($hasUS) {'u'})" + } elseif ($tag -eq 'itemsViewMode') { $shape['itemsViewMode'] = $true } + elseif ($tag -eq 'itemsUserSettingID') { $shape['itemsUserSettingID'] = $true } + else { return $null } # item/dataParameters/itemsUserSettingPresentation/… → канон-fallback + } + # Полный каноничный скелет → опускаем (компилятор регенерит) + if ($shape.Count -eq 5 -and $shape['filter'] -eq 'vu' -and $shape['order'] -eq 'vu' -and ` + $shape['conditionalAppearance'] -eq 'vu' -and $shape['itemsViewMode'] -eq $true -and $shape['itemsUserSettingID'] -eq $true) { return $null } + return $shape +} + # --- 1b. Ring-3 scan: конструкции вне зоны поддержки (draft list) --- function Fail-Ring3 { param([string]$kind, [string]$loc) @@ -1955,6 +1980,10 @@ if ($attrsNode) { $ca = Build-ConditionalAppearance -caNode $caNode -loc "settings/conditionalAppearance" if (@($ca).Count -gt 0) { $so['conditionalAppearance'] = @($ca) } } + # Форма скелета ListSettings: дескриптор только для НЕ-каноничных форм (частичные/минимальные). + # Канон → $null (компилятор регенерит полный скелет, как раньше). + $lsShape = Get-ListSettingsShape $lsNode + if ($null -ne $lsShape) { $so['listSettings'] = $lsShape } } if ($so.Count -gt 0) { $ao['settings'] = $so } } diff --git a/docs/form-dsl-spec.md b/docs/form-dsl-spec.md index 572a8fa7..f19fb120 100644 --- a/docs/form-dsl-spec.md +++ b/docs/form-dsl-spec.md @@ -814,7 +814,9 @@ Pages поддерживает `pagesRepresentation`: `None`, `TabsOnTop`, `Tabs `ManualQuery` выводится из наличия `query` — отдельным ключом не задаётся. -Пустой блок настроек компоновщика (`ListSettings`) генерируется автоматически (каноничный скелет платформы); указывать ничего не нужно. +Пустой блок настроек компоновщика (`ListSettings`) генерируется автоматически (каноничный полный скелет платформы — filter+order+conditionalAppearance+itemsViewMode+itemsUserSettingID, ~93% форм); указывать ничего не нужно. + +| `listSettings` | object | **Дескриптор формы скелета ``** — только для НЕ-каноничных (частичных/минимальных) форм. Ordered-карта present top-level элементов: контейнеры `filter`/`order`/`conditionalAppearance` → блок-мета (`"vu"`=viewMode+userSettingID, `"u"`=только userSettingID, `"v"`, `""`); `itemsViewMode`/`itemsUserSettingID` → `true`. Компилятор эмитит ТОЛЬКО указанные части (контент берёт из `filter`/`order`/`conditionalAppearance`). Нет ключа → полный каноничный скелет. Декомпилятор пишет дескриптор только для отклонений от канона | #### parameters — параметры схемы дин-списка