@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.
Files changed (35) hide show
  1. package/dist/knowledge/backendConstraints.js +3 -0
  2. package/dist/knowledge/componentCatalog.js +59 -15
  3. package/dist/knowledge/normalize.js +459 -23
  4. package/dist/knowledge/propTypes.js +5 -4
  5. package/dist/knowledge/rules.js +110 -5
  6. package/dist/knowledge/styleResolve.js +6 -1
  7. package/dist/platform/http.js +10 -2
  8. package/dist/platform/screenshot.js +80 -8
  9. package/dist/platform/strictArguments.js +32 -0
  10. package/dist/platform/writeDiff.js +6 -3
  11. package/dist/resources/examples/page-sign-in.json +1 -1
  12. package/dist/resources/guides/component.md +1 -1
  13. package/dist/resources/guides/expressions.md +6 -0
  14. package/dist/resources/guides/getting-started.md +2 -0
  15. package/dist/resources/guides/layout.md +6 -0
  16. package/dist/resources/guides/microservice.md +105 -5
  17. package/dist/resources/guides/page.md +81 -18
  18. package/dist/resources/guides/translation.md +11 -10
  19. package/dist/resources/guides/workflow.md +50 -1
  20. package/dist/resources/schema-fingerprint.json +2 -2
  21. package/dist/resources/schemas/CreateMicroserviceCommand.json +4 -1
  22. package/dist/resources/schemas/CreateWorkflowCommand.json +4 -1
  23. package/dist/resources/schemas/PreviewMicroserviceCommand.json +4 -1
  24. package/dist/resources/schemas/PreviewWorkflowCommand.json +4 -1
  25. package/dist/resources/schemas/StepDto.json +4 -1
  26. package/dist/resources/schemas/UpdateMicroserviceCommand.json +4 -1
  27. package/dist/resources/schemas/UpdateWorkflowCommand.json +4 -1
  28. package/dist/server.js +2 -1
  29. package/dist/tools/flows.js +9 -3
  30. package/dist/tools/layouts.js +4 -4
  31. package/dist/tools/microservices.js +70 -6
  32. package/dist/tools/pages.js +127 -57
  33. package/dist/tools/queries.js +72 -6
  34. package/dist/tools/workflows.js +9 -5
  35. 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 Prop DEĞİL ham değer alır: PageSchemaMicroOpApplier.cs:77
579
- * `new Prop(PropValueType.Static) { Data = op.Value }` kurar (ifade verilmişse Code). Prop sarmalı
580
- * gönderildiğinde sarmalın kendisi `Data`'ya yazılır ve platform onu `Dictionary<string, Prop>`
581
- * olarak çözmeye çalışıp **500 JsonException** verir ("could not be converted to Prop. Path: $.type").
582
- * add_component'te sarmal ZORUNLU olduğu için bu tuzağa düşmek çok kolay; şekil tamamen
583
- * türetilebilir olduğundan lint değil normalizasyon: static sarmalını açıyor, code sarmalını
584
- * op'un `expression` alanına taşıyoruz. Diğer prop tipleri (page/file/flow/widget/element) bu
585
- * op'la ifade EDİLEMEZ — onlar için PAGE-006 uyarısı üretilir.
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 (!deepWrapPropData(value))
599
- return [];
600
- return [`${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`];
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
- const notes = [`${label}: replace_prop value'su Prop sarmalından çıkarıldı — bu op ham değer alır, sarmal gönderilirse platform 500 JsonException verir`];
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 expression = value.data ?? value.ref;
613
- delete op.value;
614
- op.expression = expression;
615
- return [`${label}: replace_prop code prop'u 'expression' alanına taşındı — kod prop'u value ile değil expression ile verilir`];
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
- // Applier her value'yu Static (ifade verilmişse Code) Prop'a çevirir; diğer tipler bu op'la
618
- // ifade EDİLEMEZ. Sessiz bozuk kayıt yerine çağrı burada durur (09-04 Y-22).
619
- throw new PlatformApiError(`${label}: '${propType}' tipli Prop replace_prop ile kurulamaz — bu op her değeri static (ifade verilmişse code) Prop'a çevirir, gönderdiğiniz sarmal Data'nın içine düşer ve prop sessizce bozulur. Bu prop'u add_component ile (komponenti yeniden ekleyerek) ya da page_update(schema) ile tam şema yazarak kurun; çeviri bağlama şekli için platform_guide('translation').`, 400);
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', isHidden: false, marginValue: 0, marginType: 'none', paddingType: 'none', paddingValue: 0 },
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 kuruldu ama '${flowName}' akışının '${stepName}' setter adımı hâlâ ona yazıyor. Platform komponent kimliğini yeniden üretir: akış koşar, istek 200 döner ve komponente HİÇBİR ŞEY yazılmaz — tek belirti boş komponenttir. flow_update ile akışı aynı gövdeyle yeniden kaydedin.`
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 HER ZAMAN `Static`
6
- * (ifade verilmişse `Code`) Prop'una sarar (`PageSchemaMicroOpApplier.cs:77`), yani kurulu bir
7
- * etiketi çeviriye bağlamanın platform tarafında bir yolu yoktur. Bu op o boşluğu kapatır:
8
- * şemayı okur, prop'un TİPİNİ yerinde değiştirir, gövdeyi tam yazar.
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