@appaflytech/zeyrek-ai-mcp 0.0.5 → 0.0.7

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 (145) hide show
  1. package/.env.example +2 -2
  2. package/.env.production.example +9 -1
  3. package/README.md +14 -38
  4. package/dist/config.js +36 -3
  5. package/dist/knowledge/backendConstraints.js +982 -0
  6. package/dist/knowledge/componentCatalog.js +549 -92
  7. package/dist/knowledge/componentEvents.js +120 -25
  8. package/dist/knowledge/coverage.js +43 -0
  9. package/dist/knowledge/flowGuards.js +30 -0
  10. package/dist/knowledge/functionGuards.js +134 -0
  11. package/dist/knowledge/guideSections.js +49 -0
  12. package/dist/knowledge/hydrate.js +97 -0
  13. package/dist/knowledge/languageCodes.js +36 -0
  14. package/dist/knowledge/languages.js +105 -0
  15. package/dist/knowledge/layoutSummary.js +10 -0
  16. package/dist/knowledge/localization.js +51 -0
  17. package/dist/knowledge/normalize.js +500 -16
  18. package/dist/knowledge/outlines.js +167 -12
  19. package/dist/knowledge/policy.js +14 -5
  20. package/dist/knowledge/propTypes.js +80 -0
  21. package/dist/knowledge/rules.js +961 -44
  22. package/dist/knowledge/schemaGuard.js +199 -0
  23. package/dist/knowledge/styleResolve.js +63 -4
  24. package/dist/observability/trace.js +27 -3
  25. package/dist/platform/csv.js +123 -0
  26. package/dist/platform/errors.js +107 -6
  27. package/dist/platform/http.js +34 -4
  28. package/dist/platform/patch.js +27 -0
  29. package/dist/platform/refs.js +104 -0
  30. package/dist/platform/responses.js +25 -6
  31. package/dist/platform/screenshot.js +544 -16
  32. package/dist/platform/secrets.js +40 -0
  33. package/dist/platform/tokenVault.js +89 -0
  34. package/dist/platform/writeDiff.js +281 -0
  35. package/dist/resources/examples/flow-call-function.json +66 -0
  36. package/dist/resources/examples/function-total.json +70 -0
  37. package/dist/resources/examples/layout-app-shell.json +1 -1
  38. package/dist/resources/examples/microfunction-node-sum.json +56 -0
  39. package/dist/resources/examples/microservice-table-list.json +2 -4
  40. package/dist/resources/examples/page-dynamic-form.json +3 -3
  41. package/dist/resources/examples/page-flex-sections.json +99 -0
  42. package/dist/resources/examples/page-sign-in.json +4 -3
  43. package/dist/resources/guides/component.md +81 -8
  44. package/dist/resources/guides/entity.md +712 -20
  45. package/dist/resources/guides/expressions.md +47 -2
  46. package/dist/resources/guides/external-component.md +9 -0
  47. package/dist/resources/guides/functions.md +262 -0
  48. package/dist/resources/guides/getting-started.md +82 -7
  49. package/dist/resources/guides/layout.md +53 -10
  50. package/dist/resources/guides/merge.md +231 -0
  51. package/dist/resources/guides/microfunction.md +166 -0
  52. package/dist/resources/guides/microservice.md +287 -15
  53. package/dist/resources/guides/middleware.md +89 -1
  54. package/dist/resources/guides/modify-existing.md +23 -0
  55. package/dist/resources/guides/page.md +402 -49
  56. package/dist/resources/guides/translation.md +150 -8
  57. package/dist/resources/guides/view.md +16 -1
  58. package/dist/resources/guides/workflow.md +199 -28
  59. package/dist/resources/knowledge.js +5 -0
  60. package/dist/resources/rules.js +27 -0
  61. package/dist/resources/schema-fingerprint.json +8 -0
  62. package/dist/resources/schemas/AnalyzeImportQuery.json +51 -0
  63. package/dist/resources/schemas/ApplyMergePlanCommand.json +19 -0
  64. package/dist/resources/schemas/CreateBulkRowCommand.json +36 -0
  65. package/dist/resources/schemas/CreateDatasourceCommand.json +15 -6
  66. package/dist/resources/schemas/CreateDiscoveredEntitiesCommand.json +19 -8
  67. package/dist/resources/schemas/CreateEntityCommand.json +52 -12
  68. package/dist/resources/schemas/CreateEnvironmentCommand.json +31 -0
  69. package/dist/resources/schemas/CreateGlobalFunctionCommand.json +95 -0
  70. package/dist/resources/schemas/CreateImportPlanCommand.json +278 -0
  71. package/dist/resources/schemas/CreateMergePlanCommand.json +34 -0
  72. package/dist/resources/schemas/CreateMicrofunctionCommand.json +117 -0
  73. package/dist/resources/schemas/CreateMicrofunctionRuntimeCommand.json +51 -0
  74. package/dist/resources/schemas/CreateMicroserviceCommand.json +145 -3
  75. package/dist/resources/schemas/CreateQueryCommand.json +56 -3
  76. package/dist/resources/schemas/CreateRowCommand.json +38 -0
  77. package/dist/resources/schemas/CreateViewCommand.json +13 -10
  78. package/dist/resources/schemas/CreateWorkflowCommand.json +106 -14
  79. package/dist/resources/schemas/DataJobDto.json +267 -0
  80. package/dist/resources/schemas/ExportRowsCommand.json +172 -0
  81. package/dist/resources/schemas/Filter.json +3 -1
  82. package/dist/resources/schemas/GetRowsQuery.json +6 -1
  83. package/dist/resources/schemas/GlobalFunctionDto.json +89 -0
  84. package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +249 -0
  85. package/dist/resources/schemas/ImportAnalysisDto.json +401 -0
  86. package/dist/resources/schemas/ImportPlanDto.json +260 -0
  87. package/dist/resources/schemas/ImportSampleDto.json +90 -0
  88. package/dist/resources/schemas/InvokeMicrofunctionCommand.json +25 -0
  89. package/dist/resources/schemas/PreviewMicroserviceCommand.json +135 -1
  90. package/dist/resources/schemas/PreviewQueryCommand.json +5 -2
  91. package/dist/resources/schemas/PreviewWorkflowCommand.json +45 -9
  92. package/dist/resources/schemas/ResolveMergeConflictsCommand.json +58 -0
  93. package/dist/resources/schemas/RevertMergePlanCommand.json +19 -0
  94. package/dist/resources/schemas/RunImportCommand.json +62 -0
  95. package/dist/resources/schemas/StepDto.json +34 -3
  96. package/dist/resources/schemas/UpdateBulkRowCommand.json +36 -0
  97. package/dist/resources/schemas/UpdateDatasourceCommand.json +15 -6
  98. package/dist/resources/schemas/UpdateEntityCommand.json +52 -11
  99. package/dist/resources/schemas/UpdateGlobalFunctionCommand.json +66 -0
  100. package/dist/resources/schemas/UpdateMicrofunctionCommand.json +121 -0
  101. package/dist/resources/schemas/UpdateMicrofunctionRuntimeCommand.json +54 -0
  102. package/dist/resources/schemas/UpdateMicroserviceCommand.json +147 -4
  103. package/dist/resources/schemas/UpdateQueryCommand.json +13 -3
  104. package/dist/resources/schemas/UpdateRowCommand.json +40 -0
  105. package/dist/resources/schemas/UpdateViewCommand.json +15 -10
  106. package/dist/resources/schemas/UpdateWorkflowCommand.json +106 -14
  107. package/dist/server.js +17 -1
  108. package/dist/tools/access.js +3 -3
  109. package/dist/tools/activities.js +40 -0
  110. package/dist/tools/applications.js +1 -1
  111. package/dist/tools/automations.js +76 -34
  112. package/dist/tools/common.js +55 -0
  113. package/dist/tools/components.js +16 -7
  114. package/dist/tools/datasources.js +83 -21
  115. package/dist/tools/dependencies.js +1 -1
  116. package/dist/tools/deployments.js +34 -2
  117. package/dist/tools/dtos.js +25 -12
  118. package/dist/tools/entities.js +113 -28
  119. package/dist/tools/environments.js +52 -0
  120. package/dist/tools/externalComponents.js +9 -3
  121. package/dist/tools/files.js +57 -42
  122. package/dist/tools/flows.js +205 -13
  123. package/dist/tools/folders.js +7 -2
  124. package/dist/tools/functions.js +331 -0
  125. package/dist/tools/globalFunctions.js +63 -0
  126. package/dist/tools/importSchemas.js +79 -0
  127. package/dist/tools/imports.js +241 -0
  128. package/dist/tools/layouts.js +103 -29
  129. package/dist/tools/merges.js +113 -0
  130. package/dist/tools/microfunctions.js +155 -0
  131. package/dist/tools/microservices.js +270 -40
  132. package/dist/tools/organizations.js +5 -6
  133. package/dist/tools/pages.js +421 -104
  134. package/dist/tools/platformInfo.js +155 -11
  135. package/dist/tools/projects.js +183 -33
  136. package/dist/tools/queries.js +32 -10
  137. package/dist/tools/revisions.js +94 -0
  138. package/dist/tools/rows.js +484 -50
  139. package/dist/tools/screenWrite.js +68 -0
  140. package/dist/tools/screens.js +28 -0
  141. package/dist/tools/templates.js +32 -5
  142. package/dist/tools/translations.js +24 -8
  143. package/dist/tools/views.js +20 -10
  144. package/dist/tools/workflows.js +56 -21
  145. package/package.json +7 -2
@@ -1,4 +1,5 @@
1
1
  import { logStep } from '../observability/trace.js';
2
+ import { PlatformApiError } from '../platform/errors.js';
2
3
  import { ensureWorkflowErrorPath } from './errorPath.js';
3
4
  const SORT_DEFAULT = { name: 'sortField', type: 'sortType', source: 'query-string' };
4
5
  const LIMIT_DEFAULT = { name: 'limit', offset: 'offset', source: 'query-string' };
@@ -79,6 +80,33 @@ function fillRootAlias(table, fixes) {
79
80
  table.alias = alias;
80
81
  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`);
81
82
  }
83
+ /**
84
+ * S-06 (2026-09-09 koşusu): yazma alanlarında `alias` sözleşmede YOKTUR. Okuma/dönüş seçimleri
85
+ * `TableSelect` ile modellenir ve `Alias`/`Path`/`FieldSlug` taşır; yazılacak alanlar ise
86
+ * `TableField` ile — o sınıfta yalnız `FieldId`, `Source`, `Value`, `FolderId`, `Order`,
87
+ * `WriteOn` vardır (`MicroserviceTableGetParameter.cs:326`; aynı tip `WorkflowDto.cs:252`
88
+ * table step kuralında da kullanılıyor). Gönderilen `alias` model bağlamada düşer, yanıt onu
89
+ * geri getirmez ve `silentlyDropped` her yazma çağrısında sahte bir kayıp satırı basardı.
90
+ * Alan gövdeden **istek gitmeden** çıkarılır ve `gatewayNormalized` ile bildirilir — kayıp değil,
91
+ * sözleşme dışı bir alan.
92
+ *
93
+ * Sıra önemlidir: `fillRootAlias` kök alias'ı türetirken `fields[].alias`'ı da aday sayar, bu
94
+ * yüzden ayıklama ondan SONRA çalışır.
95
+ */
96
+ function dropWriteFieldAliases(table, fixes) {
97
+ const dropped = [];
98
+ for (const field of asObjects(table.fields)) {
99
+ const alias = String(field.alias ?? '').trim();
100
+ if (!Object.hasOwn(field, 'alias'))
101
+ continue;
102
+ delete field.alias;
103
+ if (alias)
104
+ dropped.push(`${alias}.${String(field.fieldSlug ?? field.fieldId ?? '?')}`);
105
+ }
106
+ if (dropped.length === 0)
107
+ return;
108
+ 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)`);
109
+ }
82
110
  /**
83
111
  * Platform sort/limit'i null bırakır ve path'i türetmez; Studio ikisini de guard'sız okur.
84
112
  * Eksik olanları doldurur, dolu olanlara DOKUNMAZ (yanlış değerleri MS-007 block'u yakalar).
@@ -103,6 +131,7 @@ export function normalizeTableParameters(parameters, fallbackEntityId) {
103
131
  fillJoinPaths(rootEntityId, table.joins, [], aliasChains, fixes);
104
132
  fillSelectionPaths(table, aliasChains, fixes);
105
133
  }
134
+ dropWriteFieldAliases(table, fixes);
106
135
  return fixes;
107
136
  }
108
137
  export function normalizeMicroserviceDefinition(definition) {
@@ -194,9 +223,79 @@ export function normalizeWorkflowDefinition(definition) {
194
223
  * '<adım adı>.data'dır; verilmemiş isRaw'ı buna eşitliyoruz ki editörde bir kez açılan akış
195
224
  * sessizce başka bir şeye dönüşmesin. Kalan tutarsızlığı FLOW-009 yakalar.
196
225
  */
197
- export function normalizeFlowSteps(steps) {
226
+ /**
227
+ * Adım tipi sözlüğü backend'in, KOŞAN tipler barındırıcının: motor yedi tip register eder, web
228
+ * `Runtime.svelte` altı, mobil `ScreenRuntime.tsx` beş tip ekler ve aynı sayfa kaydı ikisinde de
229
+ * koşar. Register edilmemiş bir tipe gelen akış sessizce ölür (`flow-engine.ts` `process()`
230
+ * callback bulamazsa next/completed/stopped hiç çağrılmaz), üstelik `run()` promise'i
231
+ * çözülmediği için `__ngAppsFlowActivity.running` sıfıra inmez. İki hâl tamamen türetilebilir
232
+ * olduğu için lint değil NORMALİZASYON — kalanı (`confirm`/`download` mobilde yok) yalnız
233
+ * uygulama tarafında kapanabilir, `FLOW-023` onları uyarır.
234
+ */
235
+ function normalizeFlowHostGaps(steps, list) {
198
236
  const fixes = [];
199
- for (const step of asObjects(steps)) {
237
+ const label = (step) => String(step.stepId ?? step.name ?? '?');
238
+ // 'end' adımını hiçbir barındırıcı register etmez ve Studio yeni akışı YALNIZ bir start adımıyla
239
+ // tohumlar (`event-actions.context.ts` initFlow) — yani End düğümü hiçbir yerde gerekmiyor.
240
+ // Ona işaret eden dal `nextStepId: null` olur (motor completed/stopped'ı düzgün çağırır) ve ölü
241
+ // adım gövdeden çıkar; bırakılsaydı FLOW-005 onu haklı olarak erişilemez adım diye bloklardı.
242
+ const endStepIds = new Set(list
243
+ .filter(step => step.type === 'end')
244
+ .map(step => String(step.stepId ?? ''))
245
+ .filter(id => id.length > 0));
246
+ for (const step of list) {
247
+ // navigate ile redirect'in argümanı AYNI ({ path, pageId, query, targetBlank }) ama navigate'i
248
+ // yalnız mobil register eder; redirect ise iki barındırıcıda da koşar (mobilde aynı navigate
249
+ // uygulamasına eşlenir), yani redirect kesin üstün.
250
+ if (step.type === 'navigate') {
251
+ step.type = 'redirect';
252
+ fixes.push(`'${label(step)}': type navigate → redirect yazıldı — argüman şekli aynı ({ path, pageId, query, targetBlank }) ama navigate'i web barındırıcısı register etmez, redirect ikisinde de koşar`);
253
+ }
254
+ if (endStepIds.size === 0)
255
+ continue;
256
+ const branches = step.branches;
257
+ if (Array.isArray(branches)) {
258
+ for (const branch of asObjects(branches)) {
259
+ if (!endStepIds.has(String(branch.nextStepId ?? '')))
260
+ continue;
261
+ branch.nextStepId = null;
262
+ fixes.push(`'${label(step)}': '${String(branch.name ?? branch.id ?? '?')}' dalının nextStepId'si null yazıldı — hedef bir 'end' adımıydı, o tipi hiçbir barındırıcı register etmez ve akış oraya girince çıkamaz`);
263
+ }
264
+ }
265
+ else if (branches && typeof branches === 'object') {
266
+ const dictionary = branches;
267
+ for (const name of Object.keys(dictionary)) {
268
+ if (!endStepIds.has(String(dictionary[name] ?? '')))
269
+ continue;
270
+ dictionary[name] = null;
271
+ fixes.push(`'${label(step)}': '${name}' dalının nextStepId'si null yazıldı — hedef bir 'end' adımıydı, o tipi hiçbir barındırıcı register etmez ve akış oraya girince çıkamaz`);
272
+ }
273
+ }
274
+ }
275
+ if (Array.isArray(steps) && endStepIds.size > 0) {
276
+ for (let index = steps.length - 1; index >= 0; index--) {
277
+ const step = steps[index];
278
+ if (!step || typeof step !== 'object' || step.type !== 'end')
279
+ continue;
280
+ steps.splice(index, 1);
281
+ fixes.push(`'${label(step)}': 'end' adımı gövdeden çıkarıldı — motor bu tipi register etmez, Studio da yeni akışı yalnız start ile tohumlar; akış son adımın dalındaki nextStepId: null ile biter`);
282
+ }
283
+ }
284
+ return fixes;
285
+ }
286
+ export function normalizeFlowSteps(steps) {
287
+ const list = asObjects(steps);
288
+ const fixes = normalizeFlowHostGaps(steps, list);
289
+ for (const step of list) {
290
+ if (step.type === 'function') {
291
+ const args = (step.args ?? {});
292
+ if (!Array.isArray(args.parameters)) {
293
+ args.parameters = [];
294
+ step.args = args;
295
+ 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)`);
296
+ }
297
+ continue;
298
+ }
200
299
  if (step.type !== 'microservice')
201
300
  continue;
202
301
  const args = (step.args ?? {});
@@ -223,7 +322,10 @@ const REQUIRED_COLLECTIONS = ['events', 'validations', 'stateBindings', 'childre
223
322
  * girmez) — doğrulayıcı "valid" der, yazma patlar. Tamamen türetilebilir olduğu için lint
224
323
  * değil normalizasyon.
225
324
  */
226
- const PROP_VALUE_TYPES = new Set(['static', 'file', 'flow', 'widget', 'element', 'page', 'code']);
325
+ // PropValueType (DesignComponentProperty.cs:421) — 'translation' listeden düşünce çeviriye bağlı
326
+ // prop'un KENDİSİ veri sanılıp static Prop'a sarılıyordu: istek 200 dönüyor, prop bozuk kuruluyordu
327
+ // (09-04 Y-22). Enum burada eksiksiz tutulur.
328
+ const PROP_VALUE_TYPES = new Set(['static', 'file', 'flow', 'widget', 'element', 'page', 'code', 'translation']);
227
329
  function isPropShaped(value) {
228
330
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value) && PROP_VALUE_TYPES.has(String(value.type));
229
331
  }
@@ -387,6 +489,52 @@ export function schemaOpValidationErrors(ops) {
387
489
  }
388
490
  return errors;
389
491
  }
492
+ /**
493
+ * F-06 — `style` prop'una kebab-case CSS anahtarı yazmak sessizce etkisiz kalıyor: platform
494
+ * değeri kabul ediyor, `page_schema_get` aynen geri veriyor ve runtime hiç uygulamıyor. Tek işaret
495
+ * tarayıcı konsolundaki tek satır ("Using kebab-case for css properties in objects is not
496
+ * supported. Did you mean minHeight?") ve `page_screenshot` konsolu döndürmediği için yalnız MCP
497
+ * ile çalışan bir ajan bunu HİÇ göremiyor. 09-05 koşusunun en pahalı yanlış teşhisi buydu (~15 dk)
498
+ * ve yanlış teşhis düğümleri silip yeniden kurmaya, o da 20 setter'ın kopmasına yol açtı.
499
+ *
500
+ * Seçici anahtarlara (`&__body`, `:hover`, `--token`) DOKUNULMAZ; yalnız `a-b` biçimli düz CSS
501
+ * özellik adları camelCase'e çevrilir ve çevrim `gatewayNormalized` ile bildirilir.
502
+ */
503
+ const styleKeyPattern = /^[a-z][a-z0-9]*(-[a-z0-9]+)+$/;
504
+ export function isStylePropName(name) {
505
+ return name === 'style' || /Style$/.test(name);
506
+ }
507
+ export function normalizeStyleKeys(value, label, fixed) {
508
+ if (Array.isArray(value)) {
509
+ for (const entry of value)
510
+ normalizeStyleKeys(entry, label, fixed);
511
+ return;
512
+ }
513
+ if (!value || typeof value !== 'object')
514
+ return;
515
+ const record = value;
516
+ for (const key of Object.keys(record)) {
517
+ normalizeStyleKeys(record[key], label, fixed);
518
+ if (!styleKeyPattern.test(key))
519
+ continue;
520
+ const camel = key.replace(/-([a-z0-9])/g, (_, character) => character.toUpperCase());
521
+ if (Object.hasOwn(record, camel)) {
522
+ delete record[key];
523
+ }
524
+ else {
525
+ record[camel] = record[key];
526
+ delete record[key];
527
+ }
528
+ fixed.push(`${label}: '${key}' → '${camel}'`);
529
+ }
530
+ }
531
+ export function styleKeyNote(fixed) {
532
+ if (fixed.length === 0)
533
+ return [];
534
+ return [`stil anahtarları camelCase'e çevrildi (${fixed.join(' · ')}) — runtime kebab-case anahtarı OKUMAZ, `
535
+ + 'değer kaydedilir ama hiç uygulanmaz ve tek işareti tarayıcı konsoludur (F-06). '
536
+ + "Seçici anahtarlara ('&__body', ':hover', '--token') dokunulmadı."];
537
+ }
390
538
  export function normalizeComponentDefaults(nodes) {
391
539
  const fixed = [];
392
540
  for (const node of asObjects(nodes)) {
@@ -402,7 +550,14 @@ export function normalizeComponentDefaults(nodes) {
402
550
  missing.push(key);
403
551
  }
404
552
  if (node.props && typeof node.props === 'object' && !Array.isArray(node.props)) {
405
- const { wrapped, deepened } = wrapNodeProps(node.props);
553
+ const props = node.props;
554
+ const styleKeys = [];
555
+ for (const name of Object.keys(props).filter(isStylePropName)) {
556
+ normalizeStyleKeys(props[name], `${String(node.elementId ?? node.name ?? '?')}.${name}`, styleKeys);
557
+ }
558
+ if (styleKeys.length > 0)
559
+ missing.push(`stil anahtarları camelCase'e çevrildi: ${styleKeys.join(' · ')}`);
560
+ const { wrapped, deepened } = wrapNodeProps(props);
406
561
  if (wrapped.length > 0)
407
562
  missing.push(`prop'lar Prop sarmalına alındı: ${wrapped.join('+')}`);
408
563
  if (deepened.length > 0)
@@ -459,7 +614,9 @@ function normalizeReplacePropValue(op) {
459
614
  op.expression = expression;
460
615
  return [`${label}: replace_prop code prop'u 'expression' alanına taşındı — kod prop'u value ile değil expression ile verilir`];
461
616
  }
462
- return [];
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);
463
620
  }
464
621
  export function normalizeSchemaOps(ops) {
465
622
  const components = [];
@@ -467,6 +624,9 @@ export function normalizeSchemaOps(ops) {
467
624
  const validations = [];
468
625
  for (const op of asObjects(ops)) {
469
626
  if (op.op === 'replace_prop') {
627
+ if (isStylePropName(String(op.prop ?? ''))) {
628
+ normalizeStyleKeys(op.value, `'${String(op.target ?? '?')}.${String(op.prop)}'`, props);
629
+ }
470
630
  props.push(...normalizeReplacePropValue(op));
471
631
  continue;
472
632
  }
@@ -501,6 +661,8 @@ export const PAGE_CONTAINER_ELEMENT_ID = 'page_container';
501
661
  * `CreatePageWhenApplicationCreatedEventHandler` + `SeedData`). Renderer varsayılanı
502
662
  * BİLMEZ — `isFlexLayout` yalnız düğümün kendi prop'una bakar — bu yüzden gateway'in kurduğu
503
663
  * kapsayıcı da seti açıkça taşır; yoksa Studio'dan açılan sayfa flex, MCP'den açılan ızgara olurdu.
664
+ * 2026-09-01: `wrap` varsayılanı `true` oldu (`DEFAULT_CONTAINER_LAYOUT_PROPS`) ve sete eklendi —
665
+ * `row` + `wrap:true` altında w:12 çocuklar alt satıra sararak dikey akar (eski tek-satır sıkışması bitti).
504
666
  */
505
667
  export function pageContainerNode() {
506
668
  return {
@@ -515,7 +677,8 @@ export function pageContainerNode() {
515
677
  horizontalAlign: { type: 'static', data: 'left' },
516
678
  verticalAlign: { type: 'static', data: 'top' },
517
679
  size: { type: 'static', data: 'shrink' },
518
- gap: { type: 'static', data: 'medium' }
680
+ gap: { type: 'static', data: 'medium' },
681
+ wrap: { type: 'static', data: true }
519
682
  },
520
683
  blocks: [{ breakpoint: 1024, size: { h: 24, w: 12 }, position: { x: 0, y: 0 } }],
521
684
  states: { isSticky: false, isDroppable: true, isScrollable: true, isWrapper: true },
@@ -632,6 +795,123 @@ export function heightTypeNote(stamped) {
632
795
  : stamped.join(', ');
633
796
  return [`appearance.heightType yazıldı (${detail}): platform boş bırakılan appearance'ı 'fixed'e sabitliyor, Studio ise katalogdaki isDefaultAutoHeight'a bakıyor. 'fixed' düğüm tuvalde tam size.h*10px çizilip taşan içeriği kırptığı için katalog varsayılanı gövdeye açıkça yazıldı; bilerek sınırlamak istediğin düğüme appearance.heightType:'fixed' ver`];
634
797
  }
798
+ /**
799
+ * Flex çocuğunun boyut modeli (2026-09-01, `AddSizeStringsToComponentAppearance`):
800
+ * `appearance.widthType` beş değer alır — `cover`/`contain` medya sığdırma, `auto`/`fixed`/`fill`
801
+ * flex-çocuk boyut modu. `fixed` iken serbest CSS uzunluğu `widthString`/`heightString`'den okunur;
802
+ * `fill`/`auto` string taşımaz (Studio paneli mod değişiminde string'i SİLER —
803
+ * `appearance-size.functions.ts` `buildFlexWidthMode`). Öncelik: fill > fixed(+string) > auto >
804
+ * eski 12 kolon span (`widthType` hiç yoksa). Eski backend bilinmeyen `widthType`'ı sessizce
805
+ * düşürür (`ComponentWidthType` enum'una `Fixed`/`Fill` 2026-09-01'de eklendi).
806
+ */
807
+ const WIDTH_TYPES = new Set(['cover', 'contain', 'auto', 'fixed', 'fill']);
808
+ /**
809
+ * `packages/ng-apps-screen-builder/src/flex-layout.ts` `normalizeCssSize`'ın birebir portu:
810
+ * çıplak sayı → px; birimler px/%/vw/vh/vmin/vmax/rem/em/ch; `calc(...)` aynen geçer; gerisi
811
+ * (negatif, `pt`, `fr`, `auto`…) undefined — platform kaydeder ama panel/runtime değeri düşürür.
812
+ */
813
+ export function normalizeCssSize(raw) {
814
+ if (typeof raw === 'number')
815
+ return Number.isFinite(raw) && raw > 0 ? `${raw}px` : undefined;
816
+ const value = String(raw ?? '').trim();
817
+ if (!value)
818
+ return undefined;
819
+ if (/^\d+(\.\d+)?$/.test(value))
820
+ return `${value}px`;
821
+ if (/^\d+(\.\d+)?(px|%|vw|vh|vmin|vmax|rem|em|ch)$/.test(value))
822
+ return value;
823
+ if (/^calc\(.+\)$/.test(value))
824
+ return value;
825
+ return undefined;
826
+ }
827
+ function appearanceOf(node) {
828
+ return (node.appearance && typeof node.appearance === 'object' && !Array.isArray(node.appearance)
829
+ ? node.appearance
830
+ : {});
831
+ }
832
+ /**
833
+ * Studio paritesi: panel çıplak sayıyı `px`e çevirerek yazar (`buildFlexSizeValue`), gateway de
834
+ * aynısını yapar ki iki yoldan kurulan şema aynı görünsün. Yalnız türetilebilir olan çevrilir;
835
+ * geçersiz değerler burada DEĞİL `flexSizeWarnings`'te raporlanır (uyarı, silme değil — platform
836
+ * da silmiyor, yalnız okumuyor).
837
+ */
838
+ export function normalizeSizeStrings(nodes) {
839
+ const fixed = [];
840
+ for (const node of asObjects(nodes)) {
841
+ const appearance = appearanceOf(node);
842
+ for (const field of ['widthString', 'heightString']) {
843
+ const raw = appearance[field];
844
+ if (raw === undefined || raw === null || raw === '')
845
+ continue;
846
+ const normalized = normalizeCssSize(raw);
847
+ if (normalized !== undefined && normalized !== raw) {
848
+ appearance[field] = normalized;
849
+ node.appearance = appearance;
850
+ fixed.push(`${String(node.elementId ?? node.name ?? node.type ?? '?')}.${field}: ${String(raw)} → ${normalized}`);
851
+ }
852
+ }
853
+ fixed.push(...normalizeSizeStrings(node.children));
854
+ }
855
+ return fixed;
856
+ }
857
+ export function sizeStringNote(fixed) {
858
+ if (fixed.length === 0)
859
+ return [];
860
+ 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`];
861
+ }
862
+ /**
863
+ * U-05 (2026-09-09 koşusu): platform yazılmamış bir `appearance` alanını gövdede `null` olarak
864
+ * döndürüyor — `page_container` her yeni sayfada `widthType: null` ile geliyor. Yanıt katmanı
865
+ * null'ları eliyor (`responses.ts` `prune`), yani ajan öyle bir alan GÖRMÜYOR; lint ise ham
866
+ * gövdeye baktığı için `String(null)` = `'null'` diye bilinmeyen mod uyarısı basıyordu. Her yeni
867
+ * sayfanın ilk `page_schema_patch`'inde çıkan, kullanıcının düzeltemeyeceği gürültü buydu.
868
+ * `null` ve boş string = **yazılmamış**; `widthString`/`heightString` zaten böyle davranıyor.
869
+ */
870
+ function sizeMode(raw) {
871
+ return raw === undefined || raw === null || raw === '' ? undefined : String(raw);
872
+ }
873
+ /**
874
+ * İki uyarı tek yürüyüşte:
875
+ *
876
+ * - **mod uyumsuzluğu** (`mismatchCode`): `widthString`/`heightString` var ama ilgili
877
+ * `*Type` `'fixed'` değil — string yalnız `fixed` modda okunur (`getFlexChildStyle`;
878
+ * `fill` her şeyi ezer), Studio paneli mod değişiminde string'i siler. Yazan ajan
879
+ * büyük olasılıkla modu yazmayı unutmuştur.
880
+ * - **geçersiz değer / bilinmeyen mod** (`invalidCode`): `normalizeCssSize`'dan geçmeyen
881
+ * string ya da `WIDTH_TYPES` dışı `widthType` — platform 200 döner ama değer sessizce
882
+ * düşer (eski backend bilinmeyen enum'u da düşürür).
883
+ *
884
+ * Uyarı yalnız **yazılmış** değer için çıkar: `null` ve boş string yazılmamış sayılır
885
+ * (`sizeMode`). Boyut string'leri bunu zaten yapıyordu, modlar yapmıyordu.
886
+ */
887
+ export function flexSizeWarnings(nodes, mismatchCode, invalidCode) {
888
+ const warnings = [];
889
+ const walk = (list) => {
890
+ for (const node of list) {
891
+ const appearance = appearanceOf(node);
892
+ const label = String(node.elementId ?? node.name ?? node.type ?? '?');
893
+ const widthType = sizeMode(appearance.widthType);
894
+ const heightType = sizeMode(appearance.heightType);
895
+ if (widthType !== undefined && !WIDTH_TYPES.has(widthType)) {
896
+ 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.`);
897
+ }
898
+ for (const [field, type, mode] of [['widthString', widthType, 'widthType'], ['heightString', heightType, 'heightType']]) {
899
+ const raw = appearance[field];
900
+ if (raw === undefined || raw === null || raw === '')
901
+ continue;
902
+ if (normalizeCssSize(raw) === undefined) {
903
+ warnings.push(`${invalidCode}: '${label}' düğümünde ${field} ('${String(raw)}') geçerli bir CSS uzunluğu değil — kabul edilenler: çıplak sayı (px sayılır), px · % · vw · vh · vmin · vmax · rem · em · ch birimli değer, calc(...). Negatif/pt/fr/auto GEÇMEZ; platform kaydeder ama panel ve runtime değeri sessizce düşürür.`);
904
+ }
905
+ if (type !== 'fixed') {
906
+ warnings.push(`${mismatchCode}: '${label}' düğümünde ${field} var ama ${mode} 'fixed' değil (${type ?? 'yok'}) — string yalnız ${mode}:'fixed' iken okunur; 'fill' her şeyi ezer, 'auto'/mod yokken 12 kolon span geçerlidir. Sabit ölçü istiyorsan aynı düğüme ${mode}:'fixed' da yaz — platform_guide('component') §appearance.`);
907
+ }
908
+ }
909
+ walk(asObjects(node.children));
910
+ }
911
+ };
912
+ walk(asObjects(nodes));
913
+ return warnings;
914
+ }
635
915
  const GRID_COLUMNS = 12;
636
916
  const DESKTOP_BREAKPOINT = 1024;
637
917
  const DEFAULT_RECT = { x: 0, y: 0, w: 12, h: 6 };
@@ -732,7 +1012,7 @@ function moveActive(items, active) {
732
1012
  ? item
733
1013
  : moved.find(entry => entry.elementId === item.elementId) ?? item));
734
1014
  }
735
- function reorderSiblings(nodes) {
1015
+ function reorderSiblings(nodes, causes) {
736
1016
  const laidOut = nodes
737
1017
  .map((node, index) => ({ node, elementId: typeof node.elementId === 'string' && node.elementId !== '' ? node.elementId : `#${index}` }))
738
1018
  .filter(entry => !ROOT_LAYER_TYPES.has(String(entry.node.type ?? '')));
@@ -741,8 +1021,17 @@ function reorderSiblings(nodes) {
741
1021
  let items = laidOut.map(entry => ({ elementId: entry.elementId, rect: rectOf(desktopBlock(entry.node)) }));
742
1022
  for (const entry of laidOut) {
743
1023
  const active = items.find(item => item.elementId === entry.elementId);
744
- if (active)
745
- items = moveActive(items, active);
1024
+ if (!active)
1025
+ continue;
1026
+ // Kimin ittiğini burada yakalıyoruz: bildirim "y değişti" demekle kalmayıp hangi kardeşle
1027
+ // hücre paylaşıldığını da söylesin (G-31). Aynı düğüm birden çok turda itilirse SON iten
1028
+ // kalır — düzeni açıklayan da odur.
1029
+ const beforeMove = new Map(items.map(item => [item.elementId, item.rect.y]));
1030
+ items = moveActive(items, active);
1031
+ for (const item of items) {
1032
+ if (beforeMove.get(item.elementId) !== item.rect.y)
1033
+ causes.set(item.elementId, entry.elementId);
1034
+ }
746
1035
  }
747
1036
  let moved = 0;
748
1037
  for (const [index, entry] of laidOut.entries()) {
@@ -764,11 +1053,11 @@ function reorderSiblings(nodes) {
764
1053
  }
765
1054
  return moved;
766
1055
  }
767
- function reorderPass(nodes) {
1056
+ function reorderPass(nodes, causes) {
768
1057
  const siblings = asObjects(nodes);
769
- let moved = reorderSiblings(siblings);
1058
+ let moved = reorderSiblings(siblings, causes);
770
1059
  for (const node of siblings) {
771
- moved += reorderPass(node.children);
1060
+ moved += reorderPass(node.children, causes);
772
1061
  }
773
1062
  return moved;
774
1063
  }
@@ -811,12 +1100,17 @@ const MAX_REORDER_PASSES = 8;
811
1100
  */
812
1101
  export function reorderSchemaBlocks(nodes) {
813
1102
  const before = [];
1103
+ const causes = new Map();
814
1104
  collectLayout(nodes, before);
815
- for (let pass = 0; pass < MAX_REORDER_PASSES && reorderPass(nodes) > 0; pass++)
1105
+ for (let pass = 0; pass < MAX_REORDER_PASSES && reorderPass(nodes, causes) > 0; pass++)
816
1106
  continue;
817
1107
  return before
818
1108
  .filter(entry => rectOf(desktopBlock(entry.node)).y !== entry.y)
819
- .map(entry => `${entry.label} y:${entry.y}→${rectOf(desktopBlock(entry.node)).y}${entry.hasBlock ? '' : " (desktop block'u yazıldı)"}`);
1109
+ .map(entry => {
1110
+ const cause = causes.get(entry.label);
1111
+ const reason = cause && cause !== entry.label ? ` — '${cause}' ile aynı hücreleri paylaşıyordu` : '';
1112
+ return `${entry.label} y:${entry.y}→${rectOf(desktopBlock(entry.node)).y}${entry.hasBlock ? '' : " (desktop block'u yazıldı)"}${reason}`;
1113
+ });
820
1114
  }
821
1115
  export function reorderNote(moved) {
822
1116
  if (moved.length === 0)
@@ -1177,6 +1471,9 @@ export function stringStyleWarning(ids) {
1177
1471
  * kapsayıcıya `direction: 'column'` yazmak SESSİZCE hiçbir şey yapmaz — doğrulama duvarı da
1178
1472
  * susar, çünkü prop katalogda VARDIR (2026-08-25 ölçümü: `page_schema_validate` yedi prop'a da
1179
1473
  * `valid: true` döndü). Katalog varsayılanına eşit değerler elenir; onlar niyet taşımaz.
1474
+ * `wrap` varsayılanı 2026-09-01'de beş kapsayıcı şemasında ve Studio
1475
+ * `DEFAULT_CONTAINER_LAYOUT_PROPS`'ta `false` → `true` oldu; buradaki küme onu izler,
1476
+ * yoksa varsayılanla kurulmuş her düğüm yanlış pozitif üretir.
1180
1477
  */
1181
1478
  const FLEX_LAYOUT_DEFAULTS = {
1182
1479
  direction: 'row',
@@ -1184,16 +1481,34 @@ const FLEX_LAYOUT_DEFAULTS = {
1184
1481
  verticalAlign: 'top',
1185
1482
  size: 'shrink',
1186
1483
  gap: 'medium',
1187
- wrap: false
1484
+ wrap: true
1188
1485
  };
1189
1486
  /** Kapsayıcı düzenini kuran prop kümesi (`ContainerComponent` şeması): `layout` + ona bağlı altı ayar. */
1190
1487
  export const FLEX_LAYOUT_PROP_NAMES = new Set(['layout', ...Object.keys(FLEX_LAYOUT_DEFAULTS)]);
1488
+ /**
1489
+ * Düzen prop grubunu taşıyan komponent tipleri: `packages/ng-apps-ui/src/components` altında
1490
+ * kendi `schema.ts` dosyasında `horizontalAlign` tanımlayanlar. Kural yalnız bunlara uygulanır — `button` · `avatar` ·
1491
+ * `icon` gibi komponentlerin KENDİ `size` prop'u flex `size`'ı değildir ve dokuz komponent için
1492
+ * yanlış uyarı üretiyordu (09-04 Y-08).
1493
+ */
1494
+ const FLEX_LAYOUT_HOST_TYPES = new Set(['aibox', 'box', 'box-section', 'container', 'page-container', 'form', 'layout', 'widget-area']);
1495
+ /**
1496
+ * Eski `cssgrid` (box/aibox'ın akan gerçek `display:grid` kipi; 2026-09-02'de seçenek olmaktan
1497
+ * çıktı, kayıtlı düğümlerde çalışmaya devam ediyor) düzen prop'larının HEPSİNİ ölü yapmaz:
1498
+ * `getGridChildrenStyle` boşluğu (`gap`/`gapCustom` → `resolveFlexGap`) ve `verticalAlign`'ı
1499
+ * ('stretch' ise `align-items`) okur. Bu ikisini ölü diye bildirmek, çalışan bir ayarı
1500
+ * sildirirdi. Kaynak: `packages/ng-apps-screen-builder/src/grid-layout.ts`.
1501
+ */
1502
+ const CSS_GRID_READ_PROPS = new Set(['gap', 'verticalAlign']);
1191
1503
  export function inertFlexPropNodes(nodes) {
1192
1504
  const found = [];
1193
1505
  const walk = (list) => {
1194
1506
  for (const node of list) {
1195
- if (!isFlexContainer(node)) {
1507
+ if (FLEX_LAYOUT_HOST_TYPES.has(String(node.type ?? '')) && !isFlexContainer(node)) {
1508
+ const isCssGrid = staticPropValue(node, 'layout') === 'cssgrid';
1196
1509
  const inert = Object.keys(FLEX_LAYOUT_DEFAULTS).filter(name => {
1510
+ if (isCssGrid && CSS_GRID_READ_PROPS.has(name))
1511
+ return false;
1197
1512
  const value = staticPropValue(node, name);
1198
1513
  return value !== undefined && value !== null && value !== FLEX_LAYOUT_DEFAULTS[name];
1199
1514
  });
@@ -1214,6 +1529,69 @@ export function inertFlexPropWarning(entries) {
1214
1529
  + "sessizce yok sayılır (istek reddedilmez, prop katalogda vardır). Akış düzeni istiyorsan aynı düğüme "
1215
1530
  + "layout:'flex' de yaz, istemiyorsan bu prop'ları kaldır — platform_guide('page') Kural 1f-2."];
1216
1531
  }
1532
+ /**
1533
+ * Bir yamanın SİLECEĞİ akışlar. Platform referanssız kalan akışı KALICI siler; uyarı yamadan sonra
1534
+ * geldiğinde "önce yedekle" tavsiyesi uygulanamaz hâle geliyordu (09-05 koşusu: sekiz adımlı giriş
1535
+ * akışı sıfırdan yeniden yazıldı). Bu yüzden plan yamadan ÖNCE hesaplanır — `remove_component`
1536
+ * kadar `set_event`/`remove_event` de akış öldürür ve o yolda hiç uyarı yoktu.
1537
+ */
1538
+ export function orphanedFlowPlan(schema, pageEvents, ops) {
1539
+ const nodes = asObjects(schema);
1540
+ const opList = asObjects(ops);
1541
+ const plan = [];
1542
+ const survivingRefs = (skipElementId, skipEvent) => {
1543
+ const surviving = new Set();
1544
+ for (const event of asObjects(pageEvents)) {
1545
+ if (typeof event.value === 'string')
1546
+ surviving.add(event.value);
1547
+ }
1548
+ collectFlowRefsExcept(nodes, skipElementId, surviving);
1549
+ if (skipEvent !== undefined) {
1550
+ const node = findNodeByElementId(nodes, skipElementId);
1551
+ for (const event of asObjects(node?.events)) {
1552
+ if (String(event.name ?? '') !== skipEvent && typeof event.value === 'string')
1553
+ surviving.add(event.value);
1554
+ }
1555
+ for (const flowId of collectFlowRefs({ ...(node ?? {}), events: [] }))
1556
+ surviving.add(flowId);
1557
+ }
1558
+ return surviving;
1559
+ };
1560
+ for (const op of opList) {
1561
+ const target = String(op.target ?? '');
1562
+ if (target === '')
1563
+ continue;
1564
+ if (op.op === 'remove_component') {
1565
+ const doomed = findNodeByElementId(nodes, target);
1566
+ if (!doomed)
1567
+ continue;
1568
+ const losing = collectFlowRefs(doomed);
1569
+ const surviving = survivingRefs(target);
1570
+ const orphaned = [...losing].filter(flowId => !surviving.has(flowId));
1571
+ if (orphaned.length > 0)
1572
+ plan.push({ target, op: 'remove_component', flowIds: orphaned });
1573
+ continue;
1574
+ }
1575
+ if (op.op !== 'set_event' && op.op !== 'remove_event')
1576
+ continue;
1577
+ const eventName = String(op.event ?? '');
1578
+ const node = findNodeByElementId(nodes, target);
1579
+ const current = asObjects(node?.events).find(event => String(event.name ?? '') === eventName);
1580
+ const currentValue = typeof current?.value === 'string' ? current.value : '';
1581
+ if (currentValue === '' || currentValue === String(op.eventValue ?? ''))
1582
+ continue;
1583
+ const surviving = survivingRefs(target, eventName);
1584
+ if (!surviving.has(currentValue))
1585
+ plan.push({ target, op: String(op.op), flowIds: [currentValue] });
1586
+ }
1587
+ return plan;
1588
+ }
1589
+ export function orphanedFlowMessage(plan) {
1590
+ const lines = plan.map(entry => `'${entry.target}' (${entry.op}) → ${entry.flowIds.join(', ')}`);
1591
+ return `İstek gönderilmedi: bu yama ${plan.reduce((total, entry) => total + entry.flowIds.length, 0)} akışı sayfada referanssız bırakıyor ve platform referanssız akışı KALICI siler — ${lines.join(' · ')}. `
1592
+ + 'Akışı korumak için önce flow_get ile gövdesini yedekleyin ya da set_event ile başka bir komponente bağlayın. '
1593
+ + 'Silinmesini bilerek istiyorsanız aynı çağrıyı allowFlowDeletion: true ile tekrarlayın; yanıt hangi akışların silindiğini listeler.';
1594
+ }
1217
1595
  export function orphanedFlowWarnings(schema, pageEvents, ops) {
1218
1596
  const removals = asObjects(ops).filter(op => op.op === 'remove_component');
1219
1597
  if (removals.length === 0)
@@ -1251,6 +1629,112 @@ function findNodeByElementId(nodes, elementId) {
1251
1629
  }
1252
1630
  return undefined;
1253
1631
  }
1632
+ /**
1633
+ * `orphanedFlowWarnings` silinen düğümün REFERANS VERDİĞİ akışları bulur; burası ters yönü kapsar:
1634
+ * silinen düğümü HEDEFLEYEN setter adımlarını. Aynı `elementId` ile hemen yeniden eklense bile
1635
+ * platform komponent kimliğini yeniden üretir ve setter sessizce ölür — akış koşar, istek 200
1636
+ * döner, komponente hiçbir şey yazılmaz ve tek belirti boş komponenttir. ÖLÇÜLDÜ 2026-09-04
1637
+ * (benchmark 4. koşu, `S-9`): teşhis ~8 dakika sürdü, uç doğru veri döndürdüğü ve şema geçerli
1638
+ * olduğu için hatayı yalnız ekran görüntüsü gösterdi. Kural `platform_guide('page')` Kural 1f'de
1639
+ * yazılı ama düğüm silinmeden ÖNCE okunması gerekiyor; bu uyarı tek erken sinyaldir.
1640
+ */
1641
+ export function staleSetterWarnings(flows, ops, schema) {
1642
+ const opList = asObjects(ops);
1643
+ const removed = new Set(opList
1644
+ .filter(op => op.op === 'remove_component')
1645
+ .map(op => String(op.target ?? ''))
1646
+ .filter(target => target.length > 0));
1647
+ if (removed.size === 0)
1648
+ return [];
1649
+ // Silinen KAPSAYICININ altındaki düğümler de gider. Eskiden yalnız op'un kendi target'ına
1650
+ // bakılıyordu; 09-05 koşusunda `sign_in_form` silinince ona değil ÇOCUĞU `btn_sign_in`'e yazan
1651
+ // setter sessizce koptu ve uyarı hiç çıkmadı (G-21). Komşu işlev `orphanedFlowWarnings` alt ağacı
1652
+ // zaten geziyordu — iki işlev aynı olaya bakıp farklı derinlik kullanıyordu.
1653
+ for (const target of [...removed]) {
1654
+ const doomed = findNodeByElementId(asObjects(schema), target);
1655
+ if (doomed)
1656
+ collectElementIds(doomed.children, removed);
1657
+ }
1658
+ const readded = new Set();
1659
+ for (const op of opList.filter(op => op.op === 'add_component'))
1660
+ collectElementIds(op.component, readded);
1661
+ const warnings = [];
1662
+ const affected = [];
1663
+ const seen = new Set();
1664
+ for (const flow of asObjects(flows)) {
1665
+ const flowName = String(flow.name ?? flow.slug ?? flow.id ?? 'adsız akış');
1666
+ for (const step of [...asObjects(flow.steps), ...asObjects(flow.errorSteps)].filter(step => step.type === 'setter')) {
1667
+ const args = (step.args ?? {});
1668
+ for (const iteratee of asObjects(args.iteratees)) {
1669
+ const element = typeof iteratee.element === 'string' ? iteratee.element : '';
1670
+ if (!removed.has(element))
1671
+ continue;
1672
+ const key = `${flow.id ?? flowName}|${step.name ?? step.stepId}|${element}`;
1673
+ if (seen.has(key))
1674
+ continue;
1675
+ seen.add(key);
1676
+ const stepName = String(step.name ?? step.stepId ?? 'adsız adım');
1677
+ 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.`
1679
+ : `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
+ affected.push(`${flowName} · ${stepName} → ${element}`);
1681
+ }
1682
+ }
1683
+ }
1684
+ // Özet satırı BAŞA gelir: yama işlenmeden önce kaç iteratee'nin ve hangi akışların etkileneceği
1685
+ // tek bakışta görünsün (2026-09-06 · S-5, P0-2 Step 7). Tek tek satırlar altında kalır.
1686
+ if (affected.length > 0) {
1687
+ const flowNames = [...new Set(affected.map(entry => entry.split(' · ')[0]))];
1688
+ warnings.unshift(`FLOW-018 (özet): bu yama ${affected.length} setter iteratee'sini hedefsiz bırakıyor — ${flowNames.length} akış etkileniyor (${flowNames.join(' · ')}). Etkilenenler: ${affected.join(' | ')}. Silmeden önce flow_get ile akışları yedekleyin.`);
1689
+ }
1690
+ return warnings;
1691
+ }
1692
+ /**
1693
+ * FLOW-017'nin kör noktası: o kural yalnız BOŞ `element`/`property` görür. 2026-09-09 koşusunda
1694
+ * (S-14) setter dolu ama HENÜZ VAR OLMAYAN bir elementId'ye bağlandı — platform alanı doğrulamaz,
1695
+ * 200 döner ve `element`i null kaydeder; adım koşar, hata vermez, hiçbir şeye yazmaz. Tek belirti
1696
+ * boş komponent. Yakalayan şey yazma SONRASI `silentlyDropped` diff'iydi; bu işlev aynı şeyi
1697
+ * yazmadan önce söyler. Barındırıcı kaydını okumak gerektiği için `policyRules` içine giremez
1698
+ * (check senkron ve yalnız gövdeyi görür) — yeri FLOW-003/FLOW-013 ile aynı, `tools/flows.ts`.
1699
+ */
1700
+ export function unknownSetterTargetWarnings(steps, errorSteps, hostNodes) {
1701
+ const known = new Set();
1702
+ collectElementIds(hostNodes, known);
1703
+ // Küme boşsa barındırıcı okunamamış ya da şema henüz kurulmamıştır: yanlış pozitif üretmektense
1704
+ // sessiz kalırız — uyarının değeri "şemada VAR olanı da söylüyor" olmasından geliyor.
1705
+ if (known.size === 0)
1706
+ return [];
1707
+ const warnings = [];
1708
+ const seen = new Set();
1709
+ for (const step of [...asObjects(steps), ...asObjects(errorSteps)].filter(step => step.type === 'setter')) {
1710
+ const args = (step.args ?? {});
1711
+ for (const iteratee of asObjects(args.iteratees)) {
1712
+ const element = typeof iteratee.element === 'string' ? iteratee.element.trim() : '';
1713
+ if (element.length === 0 || known.has(element))
1714
+ continue;
1715
+ const stepName = String(step.name ?? step.stepId ?? 'adsız adım');
1716
+ const key = `${stepName}|${element}`;
1717
+ if (seen.has(key))
1718
+ continue;
1719
+ seen.add(key);
1720
+ warnings.push(`FLOW-024 (uyarı): '${stepName}' setter adımının hedefi '${element}' barındırıcının şemasında YOK. Platform bu alanı doğrulamaz: istek 200 döner, element null kaydedilir ve adım koşunca HİÇBİR ŞEY yazmaz — tek belirti boş komponenttir (FLOW-017 yalnız BOŞ element'i görür, dolu ama var olmayan id'yi değil). Şemadaki elementId'ler: ${[...known].join(', ')}. Düğümü henüz kurmadıysanız sıra şudur: page_schema_patch add_component ile düğümü kurun, sonra akışı flow_update ile AYNI gövdeyle yeniden kaydedin — kaydedilen element null olduğu için flow_patch'in [element=…] seçicisi onu bulamaz.`);
1721
+ }
1722
+ }
1723
+ return warnings;
1724
+ }
1725
+ function collectElementIds(node, into) {
1726
+ if (Array.isArray(node)) {
1727
+ for (const entry of node)
1728
+ collectElementIds(entry, into);
1729
+ return;
1730
+ }
1731
+ if (!node || typeof node !== 'object')
1732
+ return;
1733
+ const record = node;
1734
+ if (typeof record.elementId === 'string')
1735
+ into.add(record.elementId);
1736
+ collectElementIds(record.children, into);
1737
+ }
1254
1738
  function collectFlowRefs(node) {
1255
1739
  const found = new Set();
1256
1740
  const visitProps = (value) => {