@appaflytech/zeyrek-ai-mcp 0.0.7 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/knowledge/backendConstraints.js +3 -0
- package/dist/knowledge/componentCatalog.js +59 -15
- package/dist/knowledge/normalize.js +459 -23
- package/dist/knowledge/propTypes.js +5 -4
- package/dist/knowledge/rules.js +110 -5
- package/dist/knowledge/styleResolve.js +6 -1
- package/dist/platform/http.js +10 -2
- package/dist/platform/screenshot.js +80 -8
- package/dist/platform/strictArguments.js +32 -0
- package/dist/platform/writeDiff.js +6 -3
- package/dist/resources/examples/page-sign-in.json +1 -1
- package/dist/resources/guides/component.md +1 -1
- package/dist/resources/guides/expressions.md +6 -0
- package/dist/resources/guides/getting-started.md +2 -0
- package/dist/resources/guides/layout.md +6 -0
- package/dist/resources/guides/microservice.md +105 -5
- package/dist/resources/guides/page.md +81 -18
- package/dist/resources/guides/translation.md +11 -10
- package/dist/resources/guides/workflow.md +50 -1
- package/dist/resources/schema-fingerprint.json +2 -2
- package/dist/resources/schemas/CreateMicroserviceCommand.json +4 -1
- package/dist/resources/schemas/CreateWorkflowCommand.json +4 -1
- package/dist/resources/schemas/PreviewMicroserviceCommand.json +4 -1
- package/dist/resources/schemas/PreviewWorkflowCommand.json +4 -1
- package/dist/resources/schemas/StepDto.json +4 -1
- package/dist/resources/schemas/UpdateMicroserviceCommand.json +4 -1
- package/dist/resources/schemas/UpdateWorkflowCommand.json +4 -1
- package/dist/server.js +2 -1
- package/dist/tools/flows.js +9 -3
- package/dist/tools/layouts.js +4 -4
- package/dist/tools/microservices.js +70 -6
- package/dist/tools/pages.js +127 -57
- package/dist/tools/queries.js +72 -6
- package/dist/tools/workflows.js +9 -5
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { logStep } from '../observability/trace.js';
|
|
2
2
|
import { PlatformApiError } from '../platform/errors.js';
|
|
3
3
|
import { ensureWorkflowErrorPath } from './errorPath.js';
|
|
4
|
+
import { TYPED_STYLE_COMPONENTS } from './styleResolve.js';
|
|
4
5
|
const SORT_DEFAULT = { name: 'sortField', type: 'sortType', source: 'query-string' };
|
|
5
6
|
const LIMIT_DEFAULT = { name: 'limit', offset: 'offset', source: 'query-string' };
|
|
6
7
|
function asArray(value) {
|
|
@@ -325,7 +326,7 @@ const REQUIRED_COLLECTIONS = ['events', 'validations', 'stateBindings', 'childre
|
|
|
325
326
|
// PropValueType (DesignComponentProperty.cs:421) — 'translation' listeden düşünce çeviriye bağlı
|
|
326
327
|
// prop'un KENDİSİ veri sanılıp static Prop'a sarılıyordu: istek 200 dönüyor, prop bozuk kuruluyordu
|
|
327
328
|
// (09-04 Y-22). Enum burada eksiksiz tutulur.
|
|
328
|
-
const PROP_VALUE_TYPES = new Set(['static', 'file', 'flow', 'widget', 'element', 'page', 'code', 'translation']);
|
|
329
|
+
export const PROP_VALUE_TYPES = new Set(['static', 'file', 'flow', 'widget', 'element', 'page', 'code', 'translation']);
|
|
329
330
|
function isPropShaped(value) {
|
|
330
331
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value) && PROP_VALUE_TYPES.has(String(value.type));
|
|
331
332
|
}
|
|
@@ -489,6 +490,77 @@ export function schemaOpValidationErrors(ops) {
|
|
|
489
490
|
}
|
|
490
491
|
return errors;
|
|
491
492
|
}
|
|
493
|
+
/**
|
|
494
|
+
* G-02 (2026-09-15) — prop düzeyindeki `code` bir ŞABLONDUR: runtime yalnız `{{ }}` içini çalıştırır
|
|
495
|
+
* (ng-apps-flow `runPropertyCode`), parantezsiz ref OLDUĞU GİBİ metin döner. Akış tarafında aynı
|
|
496
|
+
* sarmal (setter iteratee, microservice parametresi) ifadeyi parantezsiz koşturduğu için ajan bu
|
|
497
|
+
* alışkanlığı prop'a taşıyor: yanıt temiz, ekran "çalışıyor", değer ise ifadenin kendi metni.
|
|
498
|
+
* eventcalendar'da `"new Date().getFullYear() + …"` tarihe çözülmeye çalışıldı ve V8 onu 2001-01-01
|
|
499
|
+
* okudu. Niyet türetilemez (düz metin mi, ifade mi?) → normalizasyon değil, PAGE-032 block'u.
|
|
500
|
+
*/
|
|
501
|
+
function bracketlessCodeRef(value) {
|
|
502
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
503
|
+
return undefined;
|
|
504
|
+
const record = value;
|
|
505
|
+
if (record.type !== 'code' || typeof record.ref !== 'string')
|
|
506
|
+
return undefined;
|
|
507
|
+
const ref = record.ref.trim();
|
|
508
|
+
return ref !== '' && !ref.includes('{{') ? ref : undefined;
|
|
509
|
+
}
|
|
510
|
+
function codeTemplateMessage(label, ref) {
|
|
511
|
+
return `${label}: code prop'unun ifadesi '{{ }}' içinde değil ('${ref.length > 80 ? `${ref.slice(0, 80)}…` : ref}'). Prop düzeyinde code bir ŞABLONDUR — runtime yalnız '{{ }}' içini çalıştırır, parantezsiz ref olduğu gibi METİN olarak prop'a düşer ve hiçbir hata üretmez. İfadeyse '{{ <ifade> }}' yazın; düz metin kastediyorsanız type: 'static' kullanın. (Akış adımlarındaki value.type='code' parantezsiz koşar — iki sözdizimi farklıdır, platform_guide('expressions').)`;
|
|
512
|
+
}
|
|
513
|
+
function collectBracketlessCodeProps(value, label, errors) {
|
|
514
|
+
if (Array.isArray(value)) {
|
|
515
|
+
value.forEach((entry, index) => collectBracketlessCodeProps(entry, `${label}[${index}]`, errors));
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
if (!value || typeof value !== 'object')
|
|
519
|
+
return;
|
|
520
|
+
const ref = bracketlessCodeRef(value);
|
|
521
|
+
if (ref !== undefined) {
|
|
522
|
+
errors.push(codeTemplateMessage(label, ref));
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
526
|
+
collectBracketlessCodeProps(entry, `${label}.${key}`, errors);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* set_binding ifadeyi `expression` alanında taşır ve platform onu code Prop'un ref'ine yazar
|
|
531
|
+
* (PageSchemaMicroOpApplier; screen-builder patch.ts aynısı); replace_prop ifadeyi `value`'daki
|
|
532
|
+
* code sarmalının ref'inde taşır — aynı şablon kuralı. normalizeSchemaOps'tan SONRA çağrılır:
|
|
533
|
+
* replace_prop'un `expression`'ı ve ref'siz code sarmalı o noktada ref'e taşınmış olur.
|
|
534
|
+
*/
|
|
535
|
+
export function codePropTemplateErrors(ops) {
|
|
536
|
+
const errors = [];
|
|
537
|
+
const walkNodes = (nodes) => {
|
|
538
|
+
for (const node of asObjects(nodes)) {
|
|
539
|
+
const label = String(node.elementId ?? node.name ?? node.type ?? '?');
|
|
540
|
+
if (node.props && typeof node.props === 'object' && !Array.isArray(node.props)) {
|
|
541
|
+
for (const [name, prop] of Object.entries(node.props)) {
|
|
542
|
+
collectBracketlessCodeProps(prop, `${label}.${name}`, errors);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
walkNodes(node.children);
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
for (const op of asObjects(ops)) {
|
|
549
|
+
const label = `'${String(op.target ?? '?')}.${String(op.prop ?? '?')}'`;
|
|
550
|
+
if ((op.op === 'set_binding' || op.op === 'replace_prop') && typeof op.expression === 'string') {
|
|
551
|
+
const ref = bracketlessCodeRef({ type: 'code', ref: op.expression });
|
|
552
|
+
if (ref !== undefined)
|
|
553
|
+
errors.push(codeTemplateMessage(label, ref));
|
|
554
|
+
}
|
|
555
|
+
if (op.op === 'replace_prop') {
|
|
556
|
+
collectBracketlessCodeProps(op.value, label, errors);
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
if (op.op === 'add_component' && op.component)
|
|
560
|
+
walkNodes([op.component]);
|
|
561
|
+
}
|
|
562
|
+
return errors;
|
|
563
|
+
}
|
|
492
564
|
/**
|
|
493
565
|
* F-06 — `style` prop'una kebab-case CSS anahtarı yazmak sessizce etkisiz kalıyor: platform
|
|
494
566
|
* değeri kabul ediyor, `page_schema_get` aynen geri veriyor ve runtime hiç uygulamıyor. Tek işaret
|
|
@@ -549,6 +621,28 @@ export function normalizeComponentDefaults(nodes) {
|
|
|
549
621
|
node[key] = [];
|
|
550
622
|
missing.push(key);
|
|
551
623
|
}
|
|
624
|
+
// S-16 — görünürlük tek yerden okunur: RenderComponent `{#if !props.isHidden}`. Appearance'ta
|
|
625
|
+
// isHidden alanı YOKTUR (backend 2026-09-15'te kaldırdı); gövdede kalırsa platform onu
|
|
626
|
+
// sessizce düşürür ve komponent sayfa açılır açılmaz görünür. Değer props.isHidden'a taşınır;
|
|
627
|
+
// prop zaten verilmişse o kazanır. false yalnız silinir — varsayılan zaten görünür.
|
|
628
|
+
const appearance = node.appearance;
|
|
629
|
+
if (appearance && typeof appearance === 'object' && !Array.isArray(appearance) && Object.hasOwn(appearance, 'isHidden')) {
|
|
630
|
+
const appearanceRecord = appearance;
|
|
631
|
+
const hidden = appearanceRecord.isHidden;
|
|
632
|
+
delete appearanceRecord.isHidden;
|
|
633
|
+
if (hidden !== false && hidden !== null && hidden !== undefined) {
|
|
634
|
+
if (!node.props || typeof node.props !== 'object' || Array.isArray(node.props))
|
|
635
|
+
node.props = {};
|
|
636
|
+
const props = node.props;
|
|
637
|
+
if (Object.hasOwn(props, 'isHidden')) {
|
|
638
|
+
missing.push("appearance.isHidden silindi — props.isHidden zaten verilmiş, o geçerli (appearance'ta isHidden alanı yok)");
|
|
639
|
+
}
|
|
640
|
+
else {
|
|
641
|
+
props.isHidden = isPropShaped(hidden) ? hidden : { type: 'static', ref: null, data: hidden };
|
|
642
|
+
missing.push("appearance.isHidden → props.isHidden taşındı (runtime görünürlüğü yalnız props.isHidden'dan okur; appearance'ta isHidden alanı yok)");
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
552
646
|
if (node.props && typeof node.props === 'object' && !Array.isArray(node.props)) {
|
|
553
647
|
const props = node.props;
|
|
554
648
|
const styleKeys = [];
|
|
@@ -575,14 +669,26 @@ export function normalizeComponentDefaults(nodes) {
|
|
|
575
669
|
* add_component micro-op'unun taşıdığı komponent ağacı da aynı ToDomain yolundan geçer.
|
|
576
670
|
*/
|
|
577
671
|
/**
|
|
578
|
-
* `replace_prop` micro-op'u
|
|
579
|
-
* `new Prop(PropValueType.Static) { Data = op.Value }` kurar
|
|
580
|
-
*
|
|
581
|
-
*
|
|
582
|
-
*
|
|
583
|
-
*
|
|
584
|
-
*
|
|
585
|
-
*
|
|
672
|
+
* `replace_prop` micro-op'u ham değer alır: PageSchemaMicroOpApplier `ApplyOne` sarmalsız değerden
|
|
673
|
+
* `new Prop(PropValueType.Static) { Data = op.Value }` kurar. Prop sarmalı gönderildiğinde sarmalın
|
|
674
|
+
* kendisi `Data`'ya yazılır ve platform onu `Dictionary<string, Prop>` olarak çözmeye çalışıp
|
|
675
|
+
* **500 JsonException** verir ("could not be converted to Prop. Path: $.type"). add_component'te
|
|
676
|
+
* sarmal ZORUNLU olduğu için bu tuzağa düşmek çok kolay; şekil tamamen türetilebilir olduğundan
|
|
677
|
+
* lint değil normalizasyon: static sarmalını açıyoruz.
|
|
678
|
+
*
|
|
679
|
+
* `code` sarmalı ise OLDUĞU GİBİ gider (2026-09-15): applier 2026-09-04'ten beri `value` bir
|
|
680
|
+
* `{type, ref, data}` sözlüğüyse tipini korur (StObjectConverter JSON nesnesini
|
|
681
|
+
* `Dictionary<string, object>`'e çözer — `PageSchemaMicroOpApplierTests` request gövdesiyle ölçer).
|
|
682
|
+
* `replace_prop` `expression` alanını HİÇ OKUMAZ — yalnız set_binding okur; eskiden sarmalı
|
|
683
|
+
* `expression`'a taşıyorduk ve prop 200 dönerken `{type:'static', data:null}` kaydediliyordu.
|
|
684
|
+
* İfade `ref`'tedir (PG-117); ref yoksa string `data`'dan alınır, ikisi de yoksa çağrı durur.
|
|
685
|
+
* Platform code prop'unda yalnız `Ref`'i saklar (`Prop` kurucusu), `data` kalıcı değildir; ama
|
|
686
|
+
* `CreateProp` veri tipini `Data`'dan seçtiği için nesne/dizi `data` 500 verir — ref ile değiştirilir.
|
|
687
|
+
* Aynı sebeple `value`'suz `expression` code sarmalına çevrilir, `value`'nun yanındaki `expression`
|
|
688
|
+
* silinir. `translation` sarmalı da aynı gerekçeyle olduğu gibi gider (G-04): applier
|
|
689
|
+
* `Translation` Prop'u `Ref` + metin `Data` ile kurar; eskiden gateway onu reddedip remove+add
|
|
690
|
+
* öneriyordu ve o yol düğüme yazan setter iteratee'lerinin `element`'ini sildi. Diğer prop tipleri
|
|
691
|
+
* (page/file/flow/widget/element) bu op'la kurulmaz (Y-22).
|
|
586
692
|
*
|
|
587
693
|
* DIŞ sarmalın olmaması İÇ sarmalın da olmaması demek değil: applier'ın kurduğu Prop yine
|
|
588
694
|
* `ToDomain` → `CreateProp` yolundan geçer ve `Data`'nın içi HER SEVİYEDE
|
|
@@ -592,31 +698,64 @@ export function normalizeComponentDefaults(nodes) {
|
|
|
592
698
|
* Studio'nun `mapRawPropToSchemaProp` çıktısının aynısı: dış sarmal YOK, her yaprak sarılı.
|
|
593
699
|
*/
|
|
594
700
|
function normalizeReplacePropValue(op) {
|
|
595
|
-
const value = op.value;
|
|
596
701
|
const label = `'${String(op.target ?? '?')}.${String(op.prop ?? '?')}'`;
|
|
702
|
+
if (op.value === undefined && typeof op.expression === 'string') {
|
|
703
|
+
op.value = { type: 'code', ref: op.expression, data: op.expression };
|
|
704
|
+
delete op.expression;
|
|
705
|
+
return [`${label}: replace_prop 'expression' alanı code sarmalına (value: { type: 'code', ref, data }) çevrildi — platform replace_prop'ta expression'ı okumaz, prop static null kaydedilirdi (ifade için set_binding de kullanılabilir)`];
|
|
706
|
+
}
|
|
707
|
+
const notes = [];
|
|
708
|
+
if (Object.hasOwn(op, 'expression')) {
|
|
709
|
+
delete op.expression;
|
|
710
|
+
notes.push(`${label}: replace_prop 'expression' alanı silindi — platform onu replace_prop'ta okumaz, geçerli olan value`);
|
|
711
|
+
}
|
|
712
|
+
const value = op.value;
|
|
597
713
|
if (!isPropShaped(value)) {
|
|
598
|
-
if (
|
|
599
|
-
|
|
600
|
-
return
|
|
714
|
+
if (deepWrapPropData(value))
|
|
715
|
+
notes.push(`${label}: value'nun iç değerleri Prop'a sarıldı — op ham değer alır ama platform Data'nın içini her seviyede Dictionary<string, Prop> olarak çözer, sarımsız iç değer 500 JsonException verir`);
|
|
716
|
+
return notes;
|
|
601
717
|
}
|
|
602
718
|
const propType = String(value.type);
|
|
603
719
|
if (propType === 'static') {
|
|
604
720
|
const data = value.data;
|
|
605
721
|
op.value = data;
|
|
606
|
-
|
|
722
|
+
notes.push(`${label}: replace_prop value'su Prop sarmalından çıkarıldı — bu op ham değer alır, sarmal gönderilirse platform 500 JsonException verir`);
|
|
607
723
|
if (deepWrapPropData(data))
|
|
608
724
|
notes.push(`${label}: value'nun iç değerleri Prop'a sarıldı`);
|
|
609
725
|
return notes;
|
|
610
726
|
}
|
|
611
727
|
if (propType === 'code') {
|
|
612
|
-
const
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
728
|
+
const code = value;
|
|
729
|
+
if (typeof code.ref !== 'string') {
|
|
730
|
+
if (typeof code.data !== 'string') {
|
|
731
|
+
throw new PlatformApiError(`${label}: code sarmalında ifade yok — runtime ifadeyi ref'ten okur, ref'siz code prop'u 200 dönüp BOŞ kaydedilir. value: { type: 'code', ref: '{{ <ifade> }}' } yazın; sabit değer kastediyorsanız type: 'static' kullanın.`, 400);
|
|
732
|
+
}
|
|
733
|
+
code.ref = code.data;
|
|
734
|
+
notes.push(`${label}: code sarmalında ref yoktu, data'daki ifade ref'e kopyalandı — runtime ifadeyi ref'ten okur`);
|
|
735
|
+
}
|
|
736
|
+
if (code.data !== null && typeof code.data === 'object') {
|
|
737
|
+
notes.push(`${label}: code sarmalının nesne/dizi data'sı ref ile değiştirildi — platform code prop'unda data'yı saklamaz, nesne data'yı Dictionary<string, Prop> diye çözüp 500 verir`);
|
|
738
|
+
code.data = code.ref;
|
|
739
|
+
}
|
|
740
|
+
if (code.data === undefined)
|
|
741
|
+
code.data = code.ref;
|
|
742
|
+
return notes;
|
|
616
743
|
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
744
|
+
if (propType === 'translation') {
|
|
745
|
+
const translation = value;
|
|
746
|
+
if (typeof translation.ref !== 'string' || translation.ref.trim().length === 0) {
|
|
747
|
+
throw new PlatformApiError(`${label}: translation sarmalında çeviri anahtarı yok — anahtar ref'te taşınır, data yalnız tasarım yedeğidir. value: { type: 'translation', ref: '<anahtar>' } yazın; anahtarlar için translation_list.`, 400);
|
|
748
|
+
}
|
|
749
|
+
if (translation.data !== undefined && translation.data !== null && typeof translation.data === 'object') {
|
|
750
|
+
notes.push(`${label}: translation sarmalının nesne/dizi data'sı silindi — platform translation prop'unda data'yı metin olarak saklar, nesne tip adına dönüşürdü`);
|
|
751
|
+
translation.data = null;
|
|
752
|
+
}
|
|
753
|
+
return notes;
|
|
754
|
+
}
|
|
755
|
+
// Applier her sarmalın tipini korur, ama page/file/flow/widget/element taşıyıcıları başka alanlara
|
|
756
|
+
// (FlowId, RedirectPageId, dosya) yazılır ve bu op'la bağları kurulmaz. Remove+add önerilmez:
|
|
757
|
+
// düğümü yeniden kurmak ona yazan setter iteratee'lerinin element'ini siler (G-04, FLOW-018).
|
|
758
|
+
throw new PlatformApiError(`${label}: '${propType}' tipli Prop replace_prop ile kurulmaz — bu op static, code ve translation sarmalını taşır. Komponenti silip yeniden EKLEMEYİN: ona yazan setter adımlarının hedefi silinir (FLOW-018). Akış bağı için set_event, diğer tipler için page_update(schema) ile tam şema yazın.`, 400);
|
|
620
759
|
}
|
|
621
760
|
export function normalizeSchemaOps(ops) {
|
|
622
761
|
const components = [];
|
|
@@ -634,6 +773,13 @@ export function normalizeSchemaOps(ops) {
|
|
|
634
773
|
normalizeValidationList(op.validations, `'${String(op.target ?? '?')}'`, validations);
|
|
635
774
|
continue;
|
|
636
775
|
}
|
|
776
|
+
if (op.op === 'set_appearance') {
|
|
777
|
+
const appearance = op.appearance;
|
|
778
|
+
if (appearance && typeof appearance === 'object' && Object.hasOwn(appearance, 'isHidden')) {
|
|
779
|
+
throw new PlatformApiError(`'${String(op.target ?? '?')}': set_appearance isHidden yazamaz — appearance'ta isHidden alanı YOKTUR, runtime görünürlüğü yalnız props.isHidden'dan okur (S-16). Gizlemek için replace_prop { target, prop: 'isHidden', value: true }, koşullu göstermek için set_binding { target, prop: 'isHidden', expression } kullanın.`, 400);
|
|
780
|
+
}
|
|
781
|
+
continue;
|
|
782
|
+
}
|
|
637
783
|
if (op.op !== 'add_component' || !op.component)
|
|
638
784
|
continue;
|
|
639
785
|
components.push(...normalizeComponentDefaults([op.component]));
|
|
@@ -682,7 +828,7 @@ export function pageContainerNode() {
|
|
|
682
828
|
},
|
|
683
829
|
blocks: [{ breakpoint: 1024, size: { h: 24, w: 12 }, position: { x: 0, y: 0 } }],
|
|
684
830
|
states: { isSticky: false, isDroppable: true, isScrollable: true, isWrapper: true },
|
|
685
|
-
appearance: { heightType: 'auto',
|
|
831
|
+
appearance: { heightType: 'auto', marginValue: 0, marginType: 'none', paddingType: 'none', paddingValue: 0 },
|
|
686
832
|
events: [],
|
|
687
833
|
validations: [],
|
|
688
834
|
stateBindings: [],
|
|
@@ -912,6 +1058,71 @@ export function flexSizeWarnings(nodes, mismatchCode, invalidCode) {
|
|
|
912
1058
|
walk(asObjects(nodes));
|
|
913
1059
|
return warnings;
|
|
914
1060
|
}
|
|
1061
|
+
/**
|
|
1062
|
+
* İç boşluğun HİÇ çizilmediği tipler — `zeyrek-frontend` `packages/ng-apps-screen-builder/src/constants.ts`
|
|
1063
|
+
* `NO_PADDING_TYPES` (kaplama çerçeveleri) + `spacing.ts` `SHELL_ROOT_TYPES` (kabuk kökleri).
|
|
1064
|
+
*/
|
|
1065
|
+
const PADDING_NEVER_DRAWN_TYPES = new Set(['auth-layout', 'modal', 'drawer', 'layout', 'layout-shell']);
|
|
1066
|
+
/**
|
|
1067
|
+
* Düğümün `appearance.padding*` değeri runtime'da çiziliyor mu. Kural üç yüzeyde aynıdır
|
|
1068
|
+
* (`render.functions.ts` `resolveZonePadding`, `spacing.ts` `leafPaddingCss`/`getSpacingLayers`):
|
|
1069
|
+
* droppable kap dolguyu çocuk sarmalayıcısına, tipli stil modeline göçmüş yaprak kendi iç
|
|
1070
|
+
* kutusuna alır; geri kalan her düğümde (slot dahil) dolgu HİÇBİR kutuya yazılmaz ve Studio
|
|
1071
|
+
* paneli alanı göstermez.
|
|
1072
|
+
*/
|
|
1073
|
+
function drawsPadding(node) {
|
|
1074
|
+
const type = String(node.type ?? '');
|
|
1075
|
+
const states = (node.states && typeof node.states === 'object' ? node.states : {});
|
|
1076
|
+
if (PADDING_NEVER_DRAWN_TYPES.has(type) || states.isShell === true)
|
|
1077
|
+
return false;
|
|
1078
|
+
return states.isDroppable === true || Object.hasOwn(TYPED_STYLE_COMPONENTS, type);
|
|
1079
|
+
}
|
|
1080
|
+
function spacingSides(appearance, layer) {
|
|
1081
|
+
const values = appearance[`${layer}Values`];
|
|
1082
|
+
const sides = values && typeof values === 'object' && !Array.isArray(values)
|
|
1083
|
+
? ['top', 'right', 'bottom', 'left'].map(side => values[side])
|
|
1084
|
+
: [];
|
|
1085
|
+
return [appearance[`${layer}Value`], ...sides].filter(value => value !== undefined && value !== null);
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* S-9 (2026-09-15): layout slot'una `paddingType:'custom'` + `paddingValue:"24px"` yazıldı, istek
|
|
1089
|
+
* 200 döndü, slot 0px dolguyla çizildi ve sekiz sayfanın her birine sarmalayıcı kap eklendi. İki
|
|
1090
|
+
* hata üst üste: slot dolgu çizmez (`drawsPadding`), değer de sayı değil string — boşluk kanalı
|
|
1091
|
+
* sayı saklar, çizim `${value}px` birleştirir ve "24px" → `24pxpx` geçersiz CSS olur.
|
|
1092
|
+
*
|
|
1093
|
+
* - `unusedCode`: çizilmeyen düğümde sıfırdan büyük custom dolgu. Slot'un sayfa içeriğinden
|
|
1094
|
+
* uzaklığı `marginType`/`marginValue`'dur (Studio iskeleti de 8px custom margin yazar).
|
|
1095
|
+
* - `numberCode`: `padding*`/`margin*` değeri sayı değil.
|
|
1096
|
+
*
|
|
1097
|
+
* Yalnız `custom` dolguya bakılır: `normal`/`none` kayıtlı düğümlerde varsayılan olarak durur ve
|
|
1098
|
+
* panelin gizlediği alanda ajanın bilinçli bir kararı değildir.
|
|
1099
|
+
*/
|
|
1100
|
+
export function spacingLayerWarnings(nodes, unusedCode, numberCode) {
|
|
1101
|
+
const warnings = [];
|
|
1102
|
+
const walk = (list) => {
|
|
1103
|
+
for (const node of list) {
|
|
1104
|
+
const appearance = appearanceOf(node);
|
|
1105
|
+
const label = String(node.elementId ?? node.name ?? node.type ?? '?');
|
|
1106
|
+
for (const layer of ['padding', 'margin']) {
|
|
1107
|
+
const invalid = spacingSides(appearance, layer).filter(value => typeof value !== 'number');
|
|
1108
|
+
if (invalid.length > 0) {
|
|
1109
|
+
warnings.push(`${numberCode}: '${label}' düğümünde ${layer}Value/${layer}Values sayı değil (${invalid.map(value => JSON.stringify(value)).join(', ')}) — boşluk kanalı piksel SAYISI saklar ("24px" değil 24); çizim değere 'px' ekler ve string değer geçersiz CSS üretir.`);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
const hasCustomPadding = appearance.paddingType === 'custom'
|
|
1113
|
+
&& spacingSides(appearance, 'padding').some(value => value !== 0 && value !== '0');
|
|
1114
|
+
if (hasCustomPadding && !drawsPadding(node)) {
|
|
1115
|
+
const hint = node.type === 'slot'
|
|
1116
|
+
? "Slot'un sayfa içeriğinden uzaklığı dış boşluğudur: appearance.marginType:'custom' + marginValue (sayı) yaz — slot'un zemini/çerçevesi olmadığı için görsel sonuç iç boşlukla aynıdır."
|
|
1117
|
+
: "İç boşluk yalnız droppable kaplarda ve tipli stil modelindeki yapraklarda (box · text · container · statcard · icon · form) çizilir; kenardan uzaklık için marginType/marginValue, iç boşluk için sarmalayıcı container kullan.";
|
|
1118
|
+
warnings.push(`${unusedCode}: '${label}' (${String(node.type ?? '?')}) düğümüne custom iç boşluk yazıldı ama bu düğümde iç boşluk HİÇBİR kutuya çizilmez — platform 200 döner, ekran değişmez. ${hint}`);
|
|
1119
|
+
}
|
|
1120
|
+
walk(asObjects(node.children));
|
|
1121
|
+
}
|
|
1122
|
+
};
|
|
1123
|
+
walk(asObjects(nodes));
|
|
1124
|
+
return warnings;
|
|
1125
|
+
}
|
|
915
1126
|
const GRID_COLUMNS = 12;
|
|
916
1127
|
const DESKTOP_BREAKPOINT = 1024;
|
|
917
1128
|
const DEFAULT_RECT = { x: 0, y: 0, w: 12, h: 6 };
|
|
@@ -1243,6 +1454,171 @@ export function verticalGapWarnings(nodes, code, onlyParents) {
|
|
|
1243
1454
|
gapWalk('kök', asObjects(nodes), code, onlyParents, true, warnings, false);
|
|
1244
1455
|
return warnings;
|
|
1245
1456
|
}
|
|
1457
|
+
const FLOW_LAYOUTS = new Set(['flex', 'cssgrid']);
|
|
1458
|
+
function childrenOf(node) {
|
|
1459
|
+
if (!Array.isArray(node.children))
|
|
1460
|
+
node.children = [];
|
|
1461
|
+
return node.children;
|
|
1462
|
+
}
|
|
1463
|
+
function locateSlot(nodes, elementId, parent) {
|
|
1464
|
+
for (let index = 0; index < nodes.length; index++) {
|
|
1465
|
+
const node = nodes[index];
|
|
1466
|
+
if (!node || typeof node !== 'object')
|
|
1467
|
+
continue;
|
|
1468
|
+
if (node.elementId === elementId)
|
|
1469
|
+
return { siblings: nodes, parent, index };
|
|
1470
|
+
const found = locateSlot(Array.isArray(node.children) ? node.children : [], elementId, node);
|
|
1471
|
+
if (found)
|
|
1472
|
+
return found;
|
|
1473
|
+
}
|
|
1474
|
+
return undefined;
|
|
1475
|
+
}
|
|
1476
|
+
function collectDesktopPositions(nodes, into) {
|
|
1477
|
+
for (const node of nodes) {
|
|
1478
|
+
const block = desktopBlock(node);
|
|
1479
|
+
if (typeof node.elementId === 'string' && block)
|
|
1480
|
+
into.set(node.elementId, rectOf(block));
|
|
1481
|
+
collectDesktopPositions(asObjects(node.children), into);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
/**
|
|
1485
|
+
* G-10 (2026-09-15): `add_component`/`move_component`'in `index`'i dizi sırasını değiştirir ama
|
|
1486
|
+
* ÇİZİMİ değiştirmez — runtime flex (ve eski `cssgrid`) kabın çocuklarını dizi sırasıyla değil
|
|
1487
|
+
* `blocks.position` sırasıyla (önce y, sonra x) çizer (`zeyrek-frontend` `RenderComponent.svelte` →
|
|
1488
|
+
* `sortNodesByBlockPosition`). Studio flex tuvalinde sıralarken bunu kardeşlerin desktop bloklarını
|
|
1489
|
+
* yeniden yazarak çözer (`flex-designer.functions.ts` `resolveFlexOrder` + `buildFlexOrderedNodes`:
|
|
1490
|
+
* görünen sıraya yerleştir, x=0, birikimli y). Burada aynısı yapılır: akış kabında `index` GÖRÜNEN
|
|
1491
|
+
* sıradaki yerdir (eski kayıtlarda dizi sırası çizimle aynı değildir, Studio sürüklerken diziyi
|
|
1492
|
+
* değil y'yi yazar). Op'lar okunan şemanın kopyasına sırayla uygulanır; eklenen düğümün son konumu
|
|
1493
|
+
* kendi gövdesine, var olan kardeşlerinki yamanın sonuna `move_component {target, position}` olarak
|
|
1494
|
+
* yazılır. Platform position'ı düğümün İLK bloğuna yazar: ilk bloğu desktop olmayan kardeş atlanır ve
|
|
1495
|
+
* uyarılır. Izgara kapta çizim mutlak koordinattır; sona eklemeyen `index` orada PAGE-033 uyarısıdır.
|
|
1496
|
+
* `index` verilmemiş op'a dokunulmaz.
|
|
1497
|
+
*/
|
|
1498
|
+
export function flexIndexOrder(schema, ops) {
|
|
1499
|
+
const sent = asObjects(ops).map(op => JSON.parse(JSON.stringify(op)));
|
|
1500
|
+
const tree = JSON.parse(JSON.stringify(asObjects(schema)));
|
|
1501
|
+
const original = new Map();
|
|
1502
|
+
const reordered = new Set();
|
|
1503
|
+
const warnings = [];
|
|
1504
|
+
collectDesktopPositions(tree, original);
|
|
1505
|
+
for (const op of sent) {
|
|
1506
|
+
if (op.op === 'replace_prop' && op.prop === 'layout') {
|
|
1507
|
+
const target = findNodeByElementId(tree, String(op.target ?? ''));
|
|
1508
|
+
const value = op.value !== null && typeof op.value === 'object' ? op.value : { type: 'static', data: op.value };
|
|
1509
|
+
if (target)
|
|
1510
|
+
target.props = { ...(target.props ?? {}), layout: value };
|
|
1511
|
+
}
|
|
1512
|
+
if (op.op === 'remove_component') {
|
|
1513
|
+
const slot = locateSlot(tree, String(op.target ?? ''), undefined);
|
|
1514
|
+
if (slot)
|
|
1515
|
+
slot.siblings.splice(slot.index, 1);
|
|
1516
|
+
}
|
|
1517
|
+
if (op.op !== 'add_component' && op.op !== 'move_component')
|
|
1518
|
+
continue;
|
|
1519
|
+
let node;
|
|
1520
|
+
let siblings = tree;
|
|
1521
|
+
let parent;
|
|
1522
|
+
if (op.op === 'add_component') {
|
|
1523
|
+
node = op.component !== null && typeof op.component === 'object' ? JSON.parse(JSON.stringify(op.component)) : undefined;
|
|
1524
|
+
}
|
|
1525
|
+
else {
|
|
1526
|
+
const slot = locateSlot(tree, String(op.target ?? ''), undefined);
|
|
1527
|
+
if (slot) {
|
|
1528
|
+
node = slot.siblings.splice(slot.index, 1)[0];
|
|
1529
|
+
siblings = slot.siblings;
|
|
1530
|
+
parent = slot.parent;
|
|
1531
|
+
}
|
|
1532
|
+
const block = node ? desktopBlock(node) : undefined;
|
|
1533
|
+
const position = op.position;
|
|
1534
|
+
if (block && position && typeof position === 'object')
|
|
1535
|
+
block.position = { x: Number(position.x ?? 0), y: Number(position.y ?? 0) };
|
|
1536
|
+
}
|
|
1537
|
+
if (!node)
|
|
1538
|
+
continue;
|
|
1539
|
+
const requestedParent = op.parent;
|
|
1540
|
+
if (op.op === 'add_component' || (requestedParent !== undefined && requestedParent !== null)) {
|
|
1541
|
+
parent = typeof requestedParent === 'string' && requestedParent !== '' && requestedParent !== 'root'
|
|
1542
|
+
? findNodeByElementId(tree, requestedParent)
|
|
1543
|
+
: undefined;
|
|
1544
|
+
if (typeof requestedParent === 'string' && requestedParent !== '' && requestedParent !== 'root' && !parent)
|
|
1545
|
+
continue;
|
|
1546
|
+
siblings = parent ? childrenOf(parent) : tree;
|
|
1547
|
+
}
|
|
1548
|
+
const hasIndex = typeof op.index === 'number' && Number.isFinite(op.index);
|
|
1549
|
+
const insertAt = hasIndex ? Math.max(0, Math.min(op.index, siblings.length)) : siblings.length;
|
|
1550
|
+
siblings.splice(insertAt, 0, node);
|
|
1551
|
+
if (!hasIndex)
|
|
1552
|
+
continue;
|
|
1553
|
+
const elementId = String(node.elementId ?? '?');
|
|
1554
|
+
const parentLabel = String(parent?.elementId ?? 'kök');
|
|
1555
|
+
if (!parent || !FLOW_LAYOUTS.has(String(staticPropValue(parent, 'layout')))) {
|
|
1556
|
+
if (insertAt < siblings.length - 1) {
|
|
1557
|
+
warnings.push(`PAGE-033 (uyarı): '${parentLabel}' ızgara kap — '${elementId}' için verilen index:${op.index} yalnız DİZİ sırasını değiştirir, ÇİZİMİ değil: ızgarada her çocuk kendi blocks.position'ında çizilir. Sırayı değiştirmek için position.y ver (move_component {target, position: {x, y}}) ya da kabı layout:'flex' yap — flex kapta index görünen sırayı belirler ve gateway kardeş konumlarını yeniden yazar.`);
|
|
1558
|
+
}
|
|
1559
|
+
continue;
|
|
1560
|
+
}
|
|
1561
|
+
const others = siblings
|
|
1562
|
+
.filter(child => child !== node && desktopBlock(child) !== undefined)
|
|
1563
|
+
.sort((left, right) => rectOf(desktopBlock(left)).y - rectOf(desktopBlock(right)).y || rectOf(desktopBlock(left)).x - rectOf(desktopBlock(right)).x);
|
|
1564
|
+
const orderAt = Math.max(0, Math.min(op.index, others.length));
|
|
1565
|
+
let y = 0;
|
|
1566
|
+
for (const child of [...others.slice(0, orderAt), node, ...others.slice(orderAt)]) {
|
|
1567
|
+
const block = desktopBlock(child);
|
|
1568
|
+
if (!block)
|
|
1569
|
+
continue;
|
|
1570
|
+
const rect = rectOf(block);
|
|
1571
|
+
if (rect.x !== 0 || rect.y !== y) {
|
|
1572
|
+
block.position = { x: 0, y };
|
|
1573
|
+
reordered.add(String(child.elementId ?? '?'));
|
|
1574
|
+
}
|
|
1575
|
+
y += Math.max(1, rect.h);
|
|
1576
|
+
}
|
|
1577
|
+
if (op.op === 'move_component')
|
|
1578
|
+
reordered.add(elementId);
|
|
1579
|
+
}
|
|
1580
|
+
const addedBodies = new Map();
|
|
1581
|
+
const moveTargets = new Set(sent.filter(op => op.op === 'move_component').map(op => String(op.target ?? '')));
|
|
1582
|
+
const trailing = [];
|
|
1583
|
+
const moved = [];
|
|
1584
|
+
for (const op of sent) {
|
|
1585
|
+
const component = op.component;
|
|
1586
|
+
if (op.op === 'add_component' && component && typeof component.elementId === 'string')
|
|
1587
|
+
addedBodies.set(component.elementId, component);
|
|
1588
|
+
}
|
|
1589
|
+
for (const elementId of reordered) {
|
|
1590
|
+
const node = findNodeByElementId(tree, elementId);
|
|
1591
|
+
const block = node ? desktopBlock(node) : undefined;
|
|
1592
|
+
if (!node || !block)
|
|
1593
|
+
continue;
|
|
1594
|
+
const final = rectOf(block);
|
|
1595
|
+
const body = addedBodies.get(elementId);
|
|
1596
|
+
const bodyBlock = body ? desktopBlock(body) : undefined;
|
|
1597
|
+
if (bodyBlock && !moveTargets.has(elementId)) {
|
|
1598
|
+
const before = rectOf(bodyBlock);
|
|
1599
|
+
if (before.x !== final.x || before.y !== final.y) {
|
|
1600
|
+
bodyBlock.position = { x: final.x, y: final.y };
|
|
1601
|
+
moved.push(`${elementId} y:${before.y}→${final.y}`);
|
|
1602
|
+
}
|
|
1603
|
+
continue;
|
|
1604
|
+
}
|
|
1605
|
+
const before = original.get(elementId);
|
|
1606
|
+
if (!body && !moveTargets.has(elementId) && before && before.x === final.x && before.y === final.y)
|
|
1607
|
+
continue;
|
|
1608
|
+
if (Number(asObjects(node.blocks)[0]?.breakpoint) !== DESKTOP_BREAKPOINT) {
|
|
1609
|
+
warnings.push(`PAGE-033 (uyarı): '${elementId}' flex sırasına göre y:${final.y}'e taşınmalıydı ama ilk bloğu desktop (1024) değil — platform move_component'in position'ını İLK bloğa yazar, yanlış kırılımı bozmamak için konum yazılmadı. Düğümü page_update(schema) ile düzeltin.`);
|
|
1610
|
+
continue;
|
|
1611
|
+
}
|
|
1612
|
+
trailing.push({ op: 'move_component', target: elementId, position: { x: final.x, y: final.y } });
|
|
1613
|
+
moved.push(`${elementId} y:${before?.y ?? '?'}→${final.y}`);
|
|
1614
|
+
}
|
|
1615
|
+
return { ops: [...sent, ...trailing], moved, warnings };
|
|
1616
|
+
}
|
|
1617
|
+
export function flexIndexOrderNote(moved) {
|
|
1618
|
+
if (moved.length === 0)
|
|
1619
|
+
return [];
|
|
1620
|
+
return [`flex kapta index görünen sıraya çevrildi, kardeş konumları yeniden yazıldı (${moved.join(', ')}): runtime akış kabının çocuklarını dizi sırasıyla değil blocks.position sırasıyla (önce y, sonra x) çizer; Studio flex tuvali de sıralarken konumları böyle yazar (x=0, birikimli y)`];
|
|
1621
|
+
}
|
|
1246
1622
|
/**
|
|
1247
1623
|
* View gövdesinde alan→tablo bağı **alias** üzerinden kurulur (`ViewSourceMapper.Map`:
|
|
1248
1624
|
* `tablesByAlias.GetValueOrDefault(input.ViewEntityAlias) ?? tables.FirstOrDefault()`) ama
|
|
@@ -1675,7 +2051,7 @@ export function staleSetterWarnings(flows, ops, schema) {
|
|
|
1675
2051
|
seen.add(key);
|
|
1676
2052
|
const stepName = String(step.name ?? step.stepId ?? 'adsız adım');
|
|
1677
2053
|
warnings.push(readded.has(element)
|
|
1678
|
-
? `FLOW-018 (uyarı): '${element}' bu yamada silinip yeniden
|
|
2054
|
+
? `FLOW-018 (uyarı): '${element}' bu yamada silinip yeniden kuruluyor ve '${flowName}' akışının '${stepName}' setter adımı ona yazıyor. Setter hedefi elementId değil komponentin iç kimliğidir; platform yeni kimlik üretirse iteratee'nin element'i SİLİNİR — akış koşar, istek 200 döner ve komponente HİÇBİR ŞEY yazılmaz. Gateway yazmadan sonra akışları yeniden okur: bağ koptuysa flowTargetsLost onarım op'larını verir. Prop tipini değiştirmek için düğümü yeniden kurmayın — replace_prop sarmalı (static/code/translation) ya da set_prop_type yeter.`
|
|
1679
2055
|
: `FLOW-018 (uyarı): '${element}' siliniyor ama '${flowName}' akışının '${stepName}' setter adımının hedefi o — adımın hedefi artık yok, akış sessizce hiçbir şeye yazmaz. Adımı kaldırın ya da flow_update ile başka bir komponente bağlayın.`);
|
|
1680
2056
|
affected.push(`${flowName} · ${stepName} → ${element}`);
|
|
1681
2057
|
}
|
|
@@ -1689,6 +2065,66 @@ export function staleSetterWarnings(flows, ops, schema) {
|
|
|
1689
2065
|
}
|
|
1690
2066
|
return warnings;
|
|
1691
2067
|
}
|
|
2068
|
+
/**
|
|
2069
|
+
* FLOW-018'in yazma SONRASI ayağı (G-04). Uyarı yamadan önce "kopabilir" der ama sonucu bilemez:
|
|
2070
|
+
* backend aynı yamada silinip aynı elementId ile eklenen düğüme eski kimliğini geri verir; o düzeltme
|
|
2071
|
+
* deploy edilmemiş bir ortamda ise `step_iterates.DesignComponentId` FK'sı `SetNull` ile boşalır ve
|
|
2072
|
+
* `flow_get` iteratee'yi `element`'siz döndürür (09-15 koşusu: pano açıldı, alt başlık ve tarih
|
|
2073
|
+
* aralığı boş kaldı). Yamadan önce ve sonra okunan akışlar karşılaştırılır; YENİDEN EKLENEN bir
|
|
2074
|
+
* düğüme yazıp `element`'ini kaybeden her iteratee için elle uygulanacak `flow_patch` op'u üretilir.
|
|
2075
|
+
* Yalnız silinen düğümün kaybı burada sayılmaz: hedef gerçekten yoktur, onu FLOW-018 zaten söyler.
|
|
2076
|
+
*/
|
|
2077
|
+
export function lostSetterTargets(before, after, ops) {
|
|
2078
|
+
const readded = new Set();
|
|
2079
|
+
for (const op of asObjects(ops).filter(op => op.op === 'add_component'))
|
|
2080
|
+
collectElementIds(op.component, readded);
|
|
2081
|
+
const afterById = new Map(asObjects(after).map(flow => [String(flow.id ?? ''), flow]));
|
|
2082
|
+
const lost = [];
|
|
2083
|
+
for (const flow of asObjects(before)) {
|
|
2084
|
+
const saved = afterById.get(String(flow.id ?? ''));
|
|
2085
|
+
if (!saved)
|
|
2086
|
+
continue;
|
|
2087
|
+
const flowName = String(flow.name ?? flow.slug ?? flow.id ?? 'adsız akış');
|
|
2088
|
+
for (const collection of ['steps', 'errorSteps']) {
|
|
2089
|
+
const savedSteps = new Map(asObjects(saved[collection]).map(step => [String(step.stepId ?? ''), step]));
|
|
2090
|
+
for (const step of asObjects(flow[collection]).filter(step => step.type === 'setter')) {
|
|
2091
|
+
const savedStep = savedSteps.get(String(step.stepId ?? ''));
|
|
2092
|
+
if (!savedStep)
|
|
2093
|
+
continue;
|
|
2094
|
+
const savedIteratees = asObjects((savedStep.args ?? {}).iteratees);
|
|
2095
|
+
asObjects((step.args ?? {}).iteratees).forEach((iteratee, index) => {
|
|
2096
|
+
const element = typeof iteratee.element === 'string' ? iteratee.element : '';
|
|
2097
|
+
const savedIteratee = savedIteratees.find(entry => iteratee.id !== undefined && entry.id === iteratee.id) ?? savedIteratees[index];
|
|
2098
|
+
if (!readded.has(element) || !savedIteratee || (typeof savedIteratee.element === 'string' && savedIteratee.element.length > 0))
|
|
2099
|
+
return;
|
|
2100
|
+
const savedIndex = savedIteratees.indexOf(savedIteratee);
|
|
2101
|
+
lost.push(`FLOW-018 (yazma sonrası): '${flowName}' · '${String(step.name ?? step.stepId)}' iteratee ${savedIndex} hedefini kaybetti ('${element}') — akış koşar ama komponente yazmaz. Onarım: flow_patch operations: [{ op: 'add', path: '/${collection}[stepId=${String(step.stepId)}]/args/iteratees/${savedIndex}/element', value: '${element}' }]`);
|
|
2102
|
+
});
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
return lost;
|
|
2107
|
+
}
|
|
2108
|
+
/**
|
|
2109
|
+
* FLOW-017 yazarken element'siz setter'ı reddeder; ama hedef SONRADAN silinince platform alanı
|
|
2110
|
+
* boşaltır ve akış geçerli görünerek koşmaya devam eder (G-04). `flow_get` bu durumu okurken söyler —
|
|
2111
|
+
* o gövdeyle `flow_update` zaten FLOW-017'ye takılır, sebebini önceden bilmek gerekir.
|
|
2112
|
+
*/
|
|
2113
|
+
export function targetlessSetterWarnings(flow) {
|
|
2114
|
+
const record = (flow && typeof flow === 'object' && !Array.isArray(flow) ? flow : {});
|
|
2115
|
+
const body = (record.data && typeof record.data === 'object' && !Array.isArray(record.data) ? record.data : record);
|
|
2116
|
+
const warnings = [];
|
|
2117
|
+
for (const collection of ['steps', 'errorSteps']) {
|
|
2118
|
+
for (const step of asObjects(body[collection]).filter(step => step.type === 'setter')) {
|
|
2119
|
+
asObjects((step.args ?? {}).iteratees).forEach((iteratee, index) => {
|
|
2120
|
+
if (typeof iteratee.element === 'string' && iteratee.element.trim().length > 0)
|
|
2121
|
+
return;
|
|
2122
|
+
warnings.push(`FLOW-017 (okuma): '${String(step.name ?? step.stepId)}' setter adımının iteratee ${index} hedefsiz — element yok, adım koşar ama HİÇBİR ŞEYE yazmaz. Hedef komponent silinmiş ya da yeniden kurulmuş olabilir (FLOW-018). page_schema_get ile elementId'yi bulup flow_patch ile yazın: { op: 'add', path: '/${collection}[stepId=${String(step.stepId)}]/args/iteratees/${index}/element', value: '<elementId>' }`);
|
|
2123
|
+
});
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
return warnings;
|
|
2127
|
+
}
|
|
1692
2128
|
/**
|
|
1693
2129
|
* FLOW-017'nin kör noktası: o kural yalnız BOŞ `element`/`property` görür. 2026-09-09 koşusunda
|
|
1694
2130
|
* (S-14) setter dolu ama HENÜZ VAR OLMAYAN bir elementId'ye bağlandı — platform alanı doğrulamaz,
|
|
@@ -2,10 +2,11 @@ import { findNode } from '../tools/screens.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Prop değerinin `type` alanı üç değer alır ve her biri taşıyıcısı farklıdır.
|
|
4
4
|
*
|
|
5
|
-
* Platformun kendi micro-op applier'ı `replace_prop`'ta gönderilen ham değeri
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* şemayı
|
|
5
|
+
* Platformun kendi micro-op applier'ı `replace_prop`'ta gönderilen ham değeri `Static` Prop'una
|
|
6
|
+
* sarar, `{type:'code'|'translation', ref}` sarmalını tipiyle korur (`PageSchemaMicroOpApplier`
|
|
7
|
+
* `ApplyOne`); gateway bu sarmalları 2026-09-15'ten beri olduğu gibi geçirir (G-04). Bu op aynı
|
|
8
|
+
* işi şemayı okuyarak yapar: prop'un TİPİNİ yerinde değiştirir, mevcut `data`'yı tasarım yedeği
|
|
9
|
+
* olarak taşır ve gövdeyi tam yazar — layout ağacında micro-op hattı olmadığı için orada tek yoldur.
|
|
9
10
|
*
|
|
10
11
|
* Alternatifi `remove_component` + `add_component`'tı ve pahalıydı: 2026-09-06 koşusunda on bir
|
|
11
12
|
* etiket o yolla yeniden kuruldu, o düğümlere yazan DOKUZ setter iteratee'si platform tarafından
|