@appaflytech/zeyrek-ai-mcp 0.0.6 → 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 (98) hide show
  1. package/.env.example +2 -2
  2. package/.env.production.example +8 -0
  3. package/README.md +9 -8
  4. package/dist/config.js +18 -3
  5. package/dist/knowledge/backendConstraints.js +88 -0
  6. package/dist/knowledge/componentCatalog.js +415 -68
  7. package/dist/knowledge/componentEvents.js +120 -25
  8. package/dist/knowledge/functionGuards.js +134 -0
  9. package/dist/knowledge/normalize.js +512 -25
  10. package/dist/knowledge/outlines.js +31 -0
  11. package/dist/knowledge/policy.js +14 -5
  12. package/dist/knowledge/propTypes.js +5 -4
  13. package/dist/knowledge/rules.js +453 -6
  14. package/dist/knowledge/styleResolve.js +6 -1
  15. package/dist/platform/csv.js +123 -0
  16. package/dist/platform/errors.js +62 -3
  17. package/dist/platform/http.js +11 -2
  18. package/dist/platform/responses.js +16 -4
  19. package/dist/platform/screenshot.js +98 -9
  20. package/dist/platform/strictArguments.js +32 -0
  21. package/dist/platform/writeDiff.js +6 -3
  22. package/dist/resources/examples/flow-call-function.json +66 -0
  23. package/dist/resources/examples/function-total.json +70 -0
  24. package/dist/resources/examples/microfunction-node-sum.json +56 -0
  25. package/dist/resources/examples/page-dynamic-form.json +1 -1
  26. package/dist/resources/examples/page-sign-in.json +2 -2
  27. package/dist/resources/guides/component.md +14 -2
  28. package/dist/resources/guides/entity.md +321 -17
  29. package/dist/resources/guides/expressions.md +9 -0
  30. package/dist/resources/guides/functions.md +262 -0
  31. package/dist/resources/guides/getting-started.md +7 -0
  32. package/dist/resources/guides/layout.md +6 -0
  33. package/dist/resources/guides/merge.md +231 -0
  34. package/dist/resources/guides/microfunction.md +166 -0
  35. package/dist/resources/guides/microservice.md +166 -7
  36. package/dist/resources/guides/modify-existing.md +23 -0
  37. package/dist/resources/guides/page.md +101 -22
  38. package/dist/resources/guides/translation.md +35 -10
  39. package/dist/resources/guides/workflow.md +51 -2
  40. package/dist/resources/schema-fingerprint.json +4 -4
  41. package/dist/resources/schemas/AnalyzeImportQuery.json +51 -0
  42. package/dist/resources/schemas/ApplyMergePlanCommand.json +19 -0
  43. package/dist/resources/schemas/CreateBulkRowCommand.json +3 -0
  44. package/dist/resources/schemas/CreateEnvironmentCommand.json +31 -0
  45. package/dist/resources/schemas/CreateGlobalFunctionCommand.json +95 -0
  46. package/dist/resources/schemas/CreateImportPlanCommand.json +278 -0
  47. package/dist/resources/schemas/CreateMergePlanCommand.json +34 -0
  48. package/dist/resources/schemas/CreateMicrofunctionCommand.json +117 -0
  49. package/dist/resources/schemas/CreateMicrofunctionRuntimeCommand.json +51 -0
  50. package/dist/resources/schemas/CreateMicroserviceCommand.json +123 -2
  51. package/dist/resources/schemas/CreateWorkflowCommand.json +17 -3
  52. package/dist/resources/schemas/DataJobDto.json +267 -0
  53. package/dist/resources/schemas/ExportRowsCommand.json +172 -0
  54. package/dist/resources/schemas/Filter.json +3 -1
  55. package/dist/resources/schemas/GetRowsQuery.json +3 -1
  56. package/dist/resources/schemas/GlobalFunctionDto.json +89 -0
  57. package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +249 -0
  58. package/dist/resources/schemas/ImportAnalysisDto.json +401 -0
  59. package/dist/resources/schemas/ImportPlanDto.json +260 -0
  60. package/dist/resources/schemas/ImportSampleDto.json +90 -0
  61. package/dist/resources/schemas/InvokeMicrofunctionCommand.json +25 -0
  62. package/dist/resources/schemas/PreviewMicroserviceCommand.json +123 -2
  63. package/dist/resources/schemas/PreviewWorkflowCommand.json +17 -3
  64. package/dist/resources/schemas/ResolveMergeConflictsCommand.json +58 -0
  65. package/dist/resources/schemas/RevertMergePlanCommand.json +19 -0
  66. package/dist/resources/schemas/RunImportCommand.json +62 -0
  67. package/dist/resources/schemas/StepDto.json +17 -3
  68. package/dist/resources/schemas/UpdateBulkRowCommand.json +3 -0
  69. package/dist/resources/schemas/UpdateEntityCommand.json +3 -1
  70. package/dist/resources/schemas/UpdateGlobalFunctionCommand.json +66 -0
  71. package/dist/resources/schemas/UpdateMicrofunctionCommand.json +121 -0
  72. package/dist/resources/schemas/UpdateMicrofunctionRuntimeCommand.json +54 -0
  73. package/dist/resources/schemas/UpdateMicroserviceCommand.json +123 -2
  74. package/dist/resources/schemas/UpdateWorkflowCommand.json +17 -3
  75. package/dist/server.js +17 -4
  76. package/dist/tools/activities.js +40 -0
  77. package/dist/tools/automations.js +14 -5
  78. package/dist/tools/common.js +16 -0
  79. package/dist/tools/dependencies.js +1 -1
  80. package/dist/tools/environments.js +52 -0
  81. package/dist/tools/files.js +51 -50
  82. package/dist/tools/flows.js +104 -5
  83. package/dist/tools/functions.js +331 -0
  84. package/dist/tools/importSchemas.js +79 -0
  85. package/dist/tools/imports.js +241 -0
  86. package/dist/tools/layouts.js +4 -4
  87. package/dist/tools/merges.js +113 -0
  88. package/dist/tools/microfunctions.js +155 -0
  89. package/dist/tools/microservices.js +112 -9
  90. package/dist/tools/pages.js +188 -66
  91. package/dist/tools/platformInfo.js +4 -1
  92. package/dist/tools/queries.js +72 -6
  93. package/dist/tools/revisions.js +94 -0
  94. package/dist/tools/rows.js +342 -22
  95. package/dist/tools/screenWrite.js +11 -2
  96. package/dist/tools/templates.js +32 -5
  97. package/dist/tools/workflows.js +28 -6
  98. package/package.json +3 -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) {
@@ -80,6 +81,33 @@ function fillRootAlias(table, fixes) {
80
81
  table.alias = alias;
81
82
  fixes.push(`kök alias '${alias}' olarak dolduruldu — join'li table'da kök alias boşsa SQL'e '.<alan>' gider ve Postgres 42601 (zero-length delimited identifier) döner`);
82
83
  }
84
+ /**
85
+ * S-06 (2026-09-09 koşusu): yazma alanlarında `alias` sözleşmede YOKTUR. Okuma/dönüş seçimleri
86
+ * `TableSelect` ile modellenir ve `Alias`/`Path`/`FieldSlug` taşır; yazılacak alanlar ise
87
+ * `TableField` ile — o sınıfta yalnız `FieldId`, `Source`, `Value`, `FolderId`, `Order`,
88
+ * `WriteOn` vardır (`MicroserviceTableGetParameter.cs:326`; aynı tip `WorkflowDto.cs:252`
89
+ * table step kuralında da kullanılıyor). Gönderilen `alias` model bağlamada düşer, yanıt onu
90
+ * geri getirmez ve `silentlyDropped` her yazma çağrısında sahte bir kayıp satırı basardı.
91
+ * Alan gövdeden **istek gitmeden** çıkarılır ve `gatewayNormalized` ile bildirilir — kayıp değil,
92
+ * sözleşme dışı bir alan.
93
+ *
94
+ * Sıra önemlidir: `fillRootAlias` kök alias'ı türetirken `fields[].alias`'ı da aday sayar, bu
95
+ * yüzden ayıklama ondan SONRA çalışır.
96
+ */
97
+ function dropWriteFieldAliases(table, fixes) {
98
+ const dropped = [];
99
+ for (const field of asObjects(table.fields)) {
100
+ const alias = String(field.alias ?? '').trim();
101
+ if (!Object.hasOwn(field, 'alias'))
102
+ continue;
103
+ delete field.alias;
104
+ if (alias)
105
+ dropped.push(`${alias}.${String(field.fieldSlug ?? field.fieldId ?? '?')}`);
106
+ }
107
+ if (dropped.length === 0)
108
+ return;
109
+ fixes.push(`fields[].alias gövdeden çıkarıldı (${dropped.join(', ')}): yazma alanları TableField ile modelleniyor ve o sözleşmede alias YOK — alias yalnız selectFields / returnFields / filters / joins için geçerlidir (platform_guide('microservice') §Alias nerede geçerli)`);
110
+ }
83
111
  /**
84
112
  * Platform sort/limit'i null bırakır ve path'i türetmez; Studio ikisini de guard'sız okur.
85
113
  * Eksik olanları doldurur, dolu olanlara DOKUNMAZ (yanlış değerleri MS-007 block'u yakalar).
@@ -104,6 +132,7 @@ export function normalizeTableParameters(parameters, fallbackEntityId) {
104
132
  fillJoinPaths(rootEntityId, table.joins, [], aliasChains, fixes);
105
133
  fillSelectionPaths(table, aliasChains, fixes);
106
134
  }
135
+ dropWriteFieldAliases(table, fixes);
107
136
  return fixes;
108
137
  }
109
138
  export function normalizeMicroserviceDefinition(definition) {
@@ -259,6 +288,15 @@ export function normalizeFlowSteps(steps) {
259
288
  const list = asObjects(steps);
260
289
  const fixes = normalizeFlowHostGaps(steps, list);
261
290
  for (const step of list) {
291
+ if (step.type === 'function') {
292
+ const args = (step.args ?? {});
293
+ if (!Array.isArray(args.parameters)) {
294
+ args.parameters = [];
295
+ step.args = args;
296
+ fixes.push(`'${String(step.stepId ?? step.name ?? '?')}': args.parameters=[] yazıldı — function adımı parametreleri dizi bekler; eşleme bildirimdeki ada göre, sıra konumsal (K5)`);
297
+ }
298
+ continue;
299
+ }
262
300
  if (step.type !== 'microservice')
263
301
  continue;
264
302
  const args = (step.args ?? {});
@@ -288,7 +326,7 @@ const REQUIRED_COLLECTIONS = ['events', 'validations', 'stateBindings', 'childre
288
326
  // PropValueType (DesignComponentProperty.cs:421) — 'translation' listeden düşünce çeviriye bağlı
289
327
  // prop'un KENDİSİ veri sanılıp static Prop'a sarılıyordu: istek 200 dönüyor, prop bozuk kuruluyordu
290
328
  // (09-04 Y-22). Enum burada eksiksiz tutulur.
291
- 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']);
292
330
  function isPropShaped(value) {
293
331
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value) && PROP_VALUE_TYPES.has(String(value.type));
294
332
  }
@@ -452,6 +490,77 @@ export function schemaOpValidationErrors(ops) {
452
490
  }
453
491
  return errors;
454
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
+ }
455
564
  /**
456
565
  * F-06 — `style` prop'una kebab-case CSS anahtarı yazmak sessizce etkisiz kalıyor: platform
457
566
  * değeri kabul ediyor, `page_schema_get` aynen geri veriyor ve runtime hiç uygulamıyor. Tek işaret
@@ -512,6 +621,28 @@ export function normalizeComponentDefaults(nodes) {
512
621
  node[key] = [];
513
622
  missing.push(key);
514
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
+ }
515
646
  if (node.props && typeof node.props === 'object' && !Array.isArray(node.props)) {
516
647
  const props = node.props;
517
648
  const styleKeys = [];
@@ -538,14 +669,26 @@ export function normalizeComponentDefaults(nodes) {
538
669
  * add_component micro-op'unun taşıdığı komponent ağacı da aynı ToDomain yolundan geçer.
539
670
  */
540
671
  /**
541
- * `replace_prop` micro-op'u Prop DEĞİL ham değer alır: PageSchemaMicroOpApplier.cs:77
542
- * `new Prop(PropValueType.Static) { Data = op.Value }` kurar (ifade verilmişse Code). Prop sarmalı
543
- * gönderildiğinde sarmalın kendisi `Data`'ya yazılır ve platform onu `Dictionary<string, Prop>`
544
- * olarak çözmeye çalışıp **500 JsonException** verir ("could not be converted to Prop. Path: $.type").
545
- * add_component'te sarmal ZORUNLU olduğu için bu tuzağa düşmek çok kolay; şekil tamamen
546
- * türetilebilir olduğundan lint değil normalizasyon: static sarmalını açıyor, code sarmalını
547
- * op'un `expression` alanına taşıyoruz. Diğer prop tipleri (page/file/flow/widget/element) bu
548
- * 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).
549
692
  *
550
693
  * DIŞ sarmalın olmaması İÇ sarmalın da olmaması demek değil: applier'ın kurduğu Prop yine
551
694
  * `ToDomain` → `CreateProp` yolundan geçer ve `Data`'nın içi HER SEVİYEDE
@@ -555,31 +698,64 @@ export function normalizeComponentDefaults(nodes) {
555
698
  * Studio'nun `mapRawPropToSchemaProp` çıktısının aynısı: dış sarmal YOK, her yaprak sarılı.
556
699
  */
557
700
  function normalizeReplacePropValue(op) {
558
- const value = op.value;
559
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;
560
713
  if (!isPropShaped(value)) {
561
- if (!deepWrapPropData(value))
562
- return [];
563
- 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;
564
717
  }
565
718
  const propType = String(value.type);
566
719
  if (propType === 'static') {
567
720
  const data = value.data;
568
721
  op.value = data;
569
- 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`);
570
723
  if (deepWrapPropData(data))
571
724
  notes.push(`${label}: value'nun iç değerleri Prop'a sarıldı`);
572
725
  return notes;
573
726
  }
574
727
  if (propType === 'code') {
575
- const expression = value.data ?? value.ref;
576
- delete op.value;
577
- op.expression = expression;
578
- 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;
579
743
  }
580
- // Applier her value'yu Static (ifade verilmişse Code) Prop'a çevirir; diğer tipler bu op'la
581
- // ifade EDİLEMEZ. Sessiz bozuk kayıt yerine çağrı burada durur (09-04 Y-22).
582
- 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);
583
759
  }
584
760
  export function normalizeSchemaOps(ops) {
585
761
  const components = [];
@@ -597,6 +773,13 @@ export function normalizeSchemaOps(ops) {
597
773
  normalizeValidationList(op.validations, `'${String(op.target ?? '?')}'`, validations);
598
774
  continue;
599
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
+ }
600
783
  if (op.op !== 'add_component' || !op.component)
601
784
  continue;
602
785
  components.push(...normalizeComponentDefaults([op.component]));
@@ -645,7 +828,7 @@ export function pageContainerNode() {
645
828
  },
646
829
  blocks: [{ breakpoint: 1024, size: { h: 24, w: 12 }, position: { x: 0, y: 0 } }],
647
830
  states: { isSticky: false, isDroppable: true, isScrollable: true, isWrapper: true },
648
- 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 },
649
832
  events: [],
650
833
  validations: [],
651
834
  stateBindings: [],
@@ -822,6 +1005,17 @@ export function sizeStringNote(fixed) {
822
1005
  return [];
823
1006
  return [`Boyut string'i normalize edildi (${fixed.join(', ')}): Studio paneli çıplak sayıyı px'e çevirerek yazar (appearance-size.functions.ts buildFlexSizeValue), gateway aynı biçimi üretir`];
824
1007
  }
1008
+ /**
1009
+ * U-05 (2026-09-09 koşusu): platform yazılmamış bir `appearance` alanını gövdede `null` olarak
1010
+ * döndürüyor — `page_container` her yeni sayfada `widthType: null` ile geliyor. Yanıt katmanı
1011
+ * null'ları eliyor (`responses.ts` `prune`), yani ajan öyle bir alan GÖRMÜYOR; lint ise ham
1012
+ * gövdeye baktığı için `String(null)` = `'null'` diye bilinmeyen mod uyarısı basıyordu. Her yeni
1013
+ * sayfanın ilk `page_schema_patch`'inde çıkan, kullanıcının düzeltemeyeceği gürültü buydu.
1014
+ * `null` ve boş string = **yazılmamış**; `widthString`/`heightString` zaten böyle davranıyor.
1015
+ */
1016
+ function sizeMode(raw) {
1017
+ return raw === undefined || raw === null || raw === '' ? undefined : String(raw);
1018
+ }
825
1019
  /**
826
1020
  * İki uyarı tek yürüyüşte:
827
1021
  *
@@ -832,6 +1026,9 @@ export function sizeStringNote(fixed) {
832
1026
  * - **geçersiz değer / bilinmeyen mod** (`invalidCode`): `normalizeCssSize`'dan geçmeyen
833
1027
  * string ya da `WIDTH_TYPES` dışı `widthType` — platform 200 döner ama değer sessizce
834
1028
  * düşer (eski backend bilinmeyen enum'u da düşürür).
1029
+ *
1030
+ * Uyarı yalnız **yazılmış** değer için çıkar: `null` ve boş string yazılmamış sayılır
1031
+ * (`sizeMode`). Boyut string'leri bunu zaten yapıyordu, modlar yapmıyordu.
835
1032
  */
836
1033
  export function flexSizeWarnings(nodes, mismatchCode, invalidCode) {
837
1034
  const warnings = [];
@@ -839,8 +1036,8 @@ export function flexSizeWarnings(nodes, mismatchCode, invalidCode) {
839
1036
  for (const node of list) {
840
1037
  const appearance = appearanceOf(node);
841
1038
  const label = String(node.elementId ?? node.name ?? node.type ?? '?');
842
- const widthType = appearance.widthType === undefined ? undefined : String(appearance.widthType);
843
- const heightType = appearance.heightType === undefined ? undefined : String(appearance.heightType);
1039
+ const widthType = sizeMode(appearance.widthType);
1040
+ const heightType = sizeMode(appearance.heightType);
844
1041
  if (widthType !== undefined && !WIDTH_TYPES.has(widthType)) {
845
1042
  warnings.push(`${invalidCode}: '${label}' düğümünde bilinmeyen widthType '${widthType}' — geçerli küme: cover · contain · auto · fixed · fill. Platform kabul eder ama renderer okumaz, eski backend enum'u sessizce düşürür.`);
846
1043
  }
@@ -861,6 +1058,71 @@ export function flexSizeWarnings(nodes, mismatchCode, invalidCode) {
861
1058
  walk(asObjects(nodes));
862
1059
  return warnings;
863
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
+ }
864
1126
  const GRID_COLUMNS = 12;
865
1127
  const DESKTOP_BREAKPOINT = 1024;
866
1128
  const DEFAULT_RECT = { x: 0, y: 0, w: 12, h: 6 };
@@ -1192,6 +1454,171 @@ export function verticalGapWarnings(nodes, code, onlyParents) {
1192
1454
  gapWalk('kök', asObjects(nodes), code, onlyParents, true, warnings, false);
1193
1455
  return warnings;
1194
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
+ }
1195
1622
  /**
1196
1623
  * View gövdesinde alan→tablo bağı **alias** üzerinden kurulur (`ViewSourceMapper.Map`:
1197
1624
  * `tablesByAlias.GetValueOrDefault(input.ViewEntityAlias) ?? tables.FirstOrDefault()`) ama
@@ -1624,7 +2051,7 @@ export function staleSetterWarnings(flows, ops, schema) {
1624
2051
  seen.add(key);
1625
2052
  const stepName = String(step.name ?? step.stepId ?? 'adsız adım');
1626
2053
  warnings.push(readded.has(element)
1627
- ? `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.`
1628
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.`);
1629
2056
  affected.push(`${flowName} · ${stepName} → ${element}`);
1630
2057
  }
@@ -1638,6 +2065,66 @@ export function staleSetterWarnings(flows, ops, schema) {
1638
2065
  }
1639
2066
  return warnings;
1640
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
+ }
1641
2128
  /**
1642
2129
  * FLOW-017'nin kör noktası: o kural yalnız BOŞ `element`/`property` görür. 2026-09-09 koşusunda
1643
2130
  * (S-14) setter dolu ama HENÜZ VAR OLMAYAN bir elementId'ye bağlandı — platform alanı doğrulamaz,