@appaflytech/zeyrek-ai-mcp 0.0.4 → 0.0.6

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 (101) hide show
  1. package/.env.example +8 -3
  2. package/.env.production.example +2 -0
  3. package/README.md +229 -193
  4. package/dist/config.js +20 -2
  5. package/dist/knowledge/backendConstraints.js +897 -0
  6. package/dist/knowledge/componentCatalog.js +196 -42
  7. package/dist/knowledge/coverage.js +43 -0
  8. package/dist/knowledge/flowGuards.js +30 -0
  9. package/dist/knowledge/guideSections.js +49 -0
  10. package/dist/knowledge/hydrate.js +97 -0
  11. package/dist/knowledge/languageCodes.js +36 -0
  12. package/dist/knowledge/languages.js +105 -0
  13. package/dist/knowledge/layoutSummary.js +10 -0
  14. package/dist/knowledge/localization.js +51 -0
  15. package/dist/knowledge/normalize.js +449 -16
  16. package/dist/knowledge/outlines.js +136 -12
  17. package/dist/knowledge/propTypes.js +80 -0
  18. package/dist/knowledge/rules.js +618 -43
  19. package/dist/knowledge/schemaGuard.js +199 -0
  20. package/dist/knowledge/styleResolve.js +63 -4
  21. package/dist/observability/trace.js +27 -3
  22. package/dist/platform/errors.js +46 -4
  23. package/dist/platform/http.js +33 -4
  24. package/dist/platform/patch.js +27 -0
  25. package/dist/platform/refs.js +104 -0
  26. package/dist/platform/responses.js +9 -2
  27. package/dist/platform/screenshot.js +527 -16
  28. package/dist/platform/secrets.js +40 -0
  29. package/dist/platform/tokenVault.js +89 -0
  30. package/dist/platform/writeDiff.js +281 -0
  31. package/dist/resources/examples/layout-app-shell.json +1 -1
  32. package/dist/resources/examples/microservice-table-list.json +2 -4
  33. package/dist/resources/examples/page-dynamic-form.json +2 -2
  34. package/dist/resources/examples/page-flex-sections.json +99 -0
  35. package/dist/resources/examples/page-sign-in.json +3 -2
  36. package/dist/resources/guides/component.md +69 -8
  37. package/dist/resources/guides/entity.md +404 -16
  38. package/dist/resources/guides/expressions.md +44 -2
  39. package/dist/resources/guides/external-component.md +9 -0
  40. package/dist/resources/guides/getting-started.md +77 -7
  41. package/dist/resources/guides/layout.md +53 -10
  42. package/dist/resources/guides/microservice.md +226 -13
  43. package/dist/resources/guides/middleware.md +89 -1
  44. package/dist/resources/guides/page.md +383 -46
  45. package/dist/resources/guides/translation.md +126 -8
  46. package/dist/resources/guides/view.md +16 -1
  47. package/dist/resources/guides/workflow.md +198 -27
  48. package/dist/resources/knowledge.js +5 -0
  49. package/dist/resources/rules.js +27 -0
  50. package/dist/resources/schema-fingerprint.json +8 -0
  51. package/dist/resources/schemas/CreateBulkRowCommand.json +33 -0
  52. package/dist/resources/schemas/CreateDatasourceCommand.json +15 -6
  53. package/dist/resources/schemas/CreateDiscoveredEntitiesCommand.json +19 -8
  54. package/dist/resources/schemas/CreateEntityCommand.json +52 -12
  55. package/dist/resources/schemas/CreateMicroserviceCommand.json +26 -2
  56. package/dist/resources/schemas/CreateQueryCommand.json +56 -3
  57. package/dist/resources/schemas/CreateRowCommand.json +38 -0
  58. package/dist/resources/schemas/CreateViewCommand.json +13 -10
  59. package/dist/resources/schemas/CreateWorkflowCommand.json +95 -14
  60. package/dist/resources/schemas/GetRowsQuery.json +3 -0
  61. package/dist/resources/schemas/PreviewMicroserviceCommand.json +16 -0
  62. package/dist/resources/schemas/PreviewQueryCommand.json +5 -2
  63. package/dist/resources/schemas/PreviewWorkflowCommand.json +34 -9
  64. package/dist/resources/schemas/StepDto.json +23 -3
  65. package/dist/resources/schemas/UpdateBulkRowCommand.json +33 -0
  66. package/dist/resources/schemas/UpdateDatasourceCommand.json +15 -6
  67. package/dist/resources/schemas/UpdateEntityCommand.json +49 -10
  68. package/dist/resources/schemas/UpdateMicroserviceCommand.json +28 -3
  69. package/dist/resources/schemas/UpdateQueryCommand.json +13 -3
  70. package/dist/resources/schemas/UpdateRowCommand.json +40 -0
  71. package/dist/resources/schemas/UpdateViewCommand.json +15 -10
  72. package/dist/resources/schemas/UpdateWorkflowCommand.json +95 -14
  73. package/dist/server.js +4 -0
  74. package/dist/tools/access.js +3 -3
  75. package/dist/tools/applications.js +1 -1
  76. package/dist/tools/automations.js +63 -30
  77. package/dist/tools/common.js +39 -0
  78. package/dist/tools/components.js +16 -7
  79. package/dist/tools/datasources.js +83 -21
  80. package/dist/tools/deployments.js +34 -2
  81. package/dist/tools/dtos.js +25 -12
  82. package/dist/tools/entities.js +113 -28
  83. package/dist/tools/externalComponents.js +9 -3
  84. package/dist/tools/files.js +20 -6
  85. package/dist/tools/flows.js +110 -11
  86. package/dist/tools/folders.js +7 -2
  87. package/dist/tools/globalFunctions.js +63 -0
  88. package/dist/tools/layouts.js +103 -29
  89. package/dist/tools/microservices.js +229 -38
  90. package/dist/tools/organizations.js +5 -6
  91. package/dist/tools/pages.js +365 -100
  92. package/dist/tools/platformInfo.js +152 -11
  93. package/dist/tools/projects.js +183 -33
  94. package/dist/tools/queries.js +32 -10
  95. package/dist/tools/rows.js +159 -45
  96. package/dist/tools/screenWrite.js +59 -0
  97. package/dist/tools/screens.js +28 -0
  98. package/dist/tools/translations.js +24 -8
  99. package/dist/tools/views.js +20 -10
  100. package/dist/tools/workflows.js +33 -16
  101. package/package.json +6 -3
@@ -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' };
@@ -194,9 +195,70 @@ export function normalizeWorkflowDefinition(definition) {
194
195
  * '<adım adı>.data'dır; verilmemiş isRaw'ı buna eşitliyoruz ki editörde bir kez açılan akış
195
196
  * sessizce başka bir şeye dönüşmesin. Kalan tutarsızlığı FLOW-009 yakalar.
196
197
  */
197
- export function normalizeFlowSteps(steps) {
198
+ /**
199
+ * Adım tipi sözlüğü backend'in, KOŞAN tipler barındırıcının: motor yedi tip register eder, web
200
+ * `Runtime.svelte` altı, mobil `ScreenRuntime.tsx` beş tip ekler ve aynı sayfa kaydı ikisinde de
201
+ * koşar. Register edilmemiş bir tipe gelen akış sessizce ölür (`flow-engine.ts` `process()`
202
+ * callback bulamazsa next/completed/stopped hiç çağrılmaz), üstelik `run()` promise'i
203
+ * çözülmediği için `__ngAppsFlowActivity.running` sıfıra inmez. İki hâl tamamen türetilebilir
204
+ * olduğu için lint değil NORMALİZASYON — kalanı (`confirm`/`download` mobilde yok) yalnız
205
+ * uygulama tarafında kapanabilir, `FLOW-023` onları uyarır.
206
+ */
207
+ function normalizeFlowHostGaps(steps, list) {
198
208
  const fixes = [];
199
- for (const step of asObjects(steps)) {
209
+ const label = (step) => String(step.stepId ?? step.name ?? '?');
210
+ // 'end' adımını hiçbir barındırıcı register etmez ve Studio yeni akışı YALNIZ bir start adımıyla
211
+ // tohumlar (`event-actions.context.ts` initFlow) — yani End düğümü hiçbir yerde gerekmiyor.
212
+ // Ona işaret eden dal `nextStepId: null` olur (motor completed/stopped'ı düzgün çağırır) ve ölü
213
+ // adım gövdeden çıkar; bırakılsaydı FLOW-005 onu haklı olarak erişilemez adım diye bloklardı.
214
+ const endStepIds = new Set(list
215
+ .filter(step => step.type === 'end')
216
+ .map(step => String(step.stepId ?? ''))
217
+ .filter(id => id.length > 0));
218
+ for (const step of list) {
219
+ // navigate ile redirect'in argümanı AYNI ({ path, pageId, query, targetBlank }) ama navigate'i
220
+ // yalnız mobil register eder; redirect ise iki barındırıcıda da koşar (mobilde aynı navigate
221
+ // uygulamasına eşlenir), yani redirect kesin üstün.
222
+ if (step.type === 'navigate') {
223
+ step.type = 'redirect';
224
+ 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`);
225
+ }
226
+ if (endStepIds.size === 0)
227
+ continue;
228
+ const branches = step.branches;
229
+ if (Array.isArray(branches)) {
230
+ for (const branch of asObjects(branches)) {
231
+ if (!endStepIds.has(String(branch.nextStepId ?? '')))
232
+ continue;
233
+ branch.nextStepId = null;
234
+ 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`);
235
+ }
236
+ }
237
+ else if (branches && typeof branches === 'object') {
238
+ const dictionary = branches;
239
+ for (const name of Object.keys(dictionary)) {
240
+ if (!endStepIds.has(String(dictionary[name] ?? '')))
241
+ continue;
242
+ dictionary[name] = null;
243
+ 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`);
244
+ }
245
+ }
246
+ }
247
+ if (Array.isArray(steps) && endStepIds.size > 0) {
248
+ for (let index = steps.length - 1; index >= 0; index--) {
249
+ const step = steps[index];
250
+ if (!step || typeof step !== 'object' || step.type !== 'end')
251
+ continue;
252
+ steps.splice(index, 1);
253
+ 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`);
254
+ }
255
+ }
256
+ return fixes;
257
+ }
258
+ export function normalizeFlowSteps(steps) {
259
+ const list = asObjects(steps);
260
+ const fixes = normalizeFlowHostGaps(steps, list);
261
+ for (const step of list) {
200
262
  if (step.type !== 'microservice')
201
263
  continue;
202
264
  const args = (step.args ?? {});
@@ -223,7 +285,10 @@ const REQUIRED_COLLECTIONS = ['events', 'validations', 'stateBindings', 'childre
223
285
  * girmez) — doğrulayıcı "valid" der, yazma patlar. Tamamen türetilebilir olduğu için lint
224
286
  * değil normalizasyon.
225
287
  */
226
- const PROP_VALUE_TYPES = new Set(['static', 'file', 'flow', 'widget', 'element', 'page', 'code']);
288
+ // PropValueType (DesignComponentProperty.cs:421) — 'translation' listeden düşünce çeviriye bağlı
289
+ // prop'un KENDİSİ veri sanılıp static Prop'a sarılıyordu: istek 200 dönüyor, prop bozuk kuruluyordu
290
+ // (09-04 Y-22). Enum burada eksiksiz tutulur.
291
+ const PROP_VALUE_TYPES = new Set(['static', 'file', 'flow', 'widget', 'element', 'page', 'code', 'translation']);
227
292
  function isPropShaped(value) {
228
293
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value) && PROP_VALUE_TYPES.has(String(value.type));
229
294
  }
@@ -387,6 +452,52 @@ export function schemaOpValidationErrors(ops) {
387
452
  }
388
453
  return errors;
389
454
  }
455
+ /**
456
+ * F-06 — `style` prop'una kebab-case CSS anahtarı yazmak sessizce etkisiz kalıyor: platform
457
+ * değeri kabul ediyor, `page_schema_get` aynen geri veriyor ve runtime hiç uygulamıyor. Tek işaret
458
+ * tarayıcı konsolundaki tek satır ("Using kebab-case for css properties in objects is not
459
+ * supported. Did you mean minHeight?") ve `page_screenshot` konsolu döndürmediği için yalnız MCP
460
+ * ile çalışan bir ajan bunu HİÇ göremiyor. 09-05 koşusunun en pahalı yanlış teşhisi buydu (~15 dk)
461
+ * ve yanlış teşhis düğümleri silip yeniden kurmaya, o da 20 setter'ın kopmasına yol açtı.
462
+ *
463
+ * Seçici anahtarlara (`&__body`, `:hover`, `--token`) DOKUNULMAZ; yalnız `a-b` biçimli düz CSS
464
+ * özellik adları camelCase'e çevrilir ve çevrim `gatewayNormalized` ile bildirilir.
465
+ */
466
+ const styleKeyPattern = /^[a-z][a-z0-9]*(-[a-z0-9]+)+$/;
467
+ export function isStylePropName(name) {
468
+ return name === 'style' || /Style$/.test(name);
469
+ }
470
+ export function normalizeStyleKeys(value, label, fixed) {
471
+ if (Array.isArray(value)) {
472
+ for (const entry of value)
473
+ normalizeStyleKeys(entry, label, fixed);
474
+ return;
475
+ }
476
+ if (!value || typeof value !== 'object')
477
+ return;
478
+ const record = value;
479
+ for (const key of Object.keys(record)) {
480
+ normalizeStyleKeys(record[key], label, fixed);
481
+ if (!styleKeyPattern.test(key))
482
+ continue;
483
+ const camel = key.replace(/-([a-z0-9])/g, (_, character) => character.toUpperCase());
484
+ if (Object.hasOwn(record, camel)) {
485
+ delete record[key];
486
+ }
487
+ else {
488
+ record[camel] = record[key];
489
+ delete record[key];
490
+ }
491
+ fixed.push(`${label}: '${key}' → '${camel}'`);
492
+ }
493
+ }
494
+ export function styleKeyNote(fixed) {
495
+ if (fixed.length === 0)
496
+ return [];
497
+ return [`stil anahtarları camelCase'e çevrildi (${fixed.join(' · ')}) — runtime kebab-case anahtarı OKUMAZ, `
498
+ + 'değer kaydedilir ama hiç uygulanmaz ve tek işareti tarayıcı konsoludur (F-06). '
499
+ + "Seçici anahtarlara ('&__body', ':hover', '--token') dokunulmadı."];
500
+ }
390
501
  export function normalizeComponentDefaults(nodes) {
391
502
  const fixed = [];
392
503
  for (const node of asObjects(nodes)) {
@@ -402,7 +513,14 @@ export function normalizeComponentDefaults(nodes) {
402
513
  missing.push(key);
403
514
  }
404
515
  if (node.props && typeof node.props === 'object' && !Array.isArray(node.props)) {
405
- const { wrapped, deepened } = wrapNodeProps(node.props);
516
+ const props = node.props;
517
+ const styleKeys = [];
518
+ for (const name of Object.keys(props).filter(isStylePropName)) {
519
+ normalizeStyleKeys(props[name], `${String(node.elementId ?? node.name ?? '?')}.${name}`, styleKeys);
520
+ }
521
+ if (styleKeys.length > 0)
522
+ missing.push(`stil anahtarları camelCase'e çevrildi: ${styleKeys.join(' · ')}`);
523
+ const { wrapped, deepened } = wrapNodeProps(props);
406
524
  if (wrapped.length > 0)
407
525
  missing.push(`prop'lar Prop sarmalına alındı: ${wrapped.join('+')}`);
408
526
  if (deepened.length > 0)
@@ -459,7 +577,9 @@ function normalizeReplacePropValue(op) {
459
577
  op.expression = expression;
460
578
  return [`${label}: replace_prop code prop'u 'expression' alanına taşındı — kod prop'u value ile değil expression ile verilir`];
461
579
  }
462
- return [];
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);
463
583
  }
464
584
  export function normalizeSchemaOps(ops) {
465
585
  const components = [];
@@ -467,6 +587,9 @@ export function normalizeSchemaOps(ops) {
467
587
  const validations = [];
468
588
  for (const op of asObjects(ops)) {
469
589
  if (op.op === 'replace_prop') {
590
+ if (isStylePropName(String(op.prop ?? ''))) {
591
+ normalizeStyleKeys(op.value, `'${String(op.target ?? '?')}.${String(op.prop)}'`, props);
592
+ }
470
593
  props.push(...normalizeReplacePropValue(op));
471
594
  continue;
472
595
  }
@@ -501,6 +624,8 @@ export const PAGE_CONTAINER_ELEMENT_ID = 'page_container';
501
624
  * `CreatePageWhenApplicationCreatedEventHandler` + `SeedData`). Renderer varsayılanı
502
625
  * BİLMEZ — `isFlexLayout` yalnız düğümün kendi prop'una bakar — bu yüzden gateway'in kurduğu
503
626
  * kapsayıcı da seti açıkça taşır; yoksa Studio'dan açılan sayfa flex, MCP'den açılan ızgara olurdu.
627
+ * 2026-09-01: `wrap` varsayılanı `true` oldu (`DEFAULT_CONTAINER_LAYOUT_PROPS`) ve sete eklendi —
628
+ * `row` + `wrap:true` altında w:12 çocuklar alt satıra sararak dikey akar (eski tek-satır sıkışması bitti).
504
629
  */
505
630
  export function pageContainerNode() {
506
631
  return {
@@ -515,7 +640,8 @@ export function pageContainerNode() {
515
640
  horizontalAlign: { type: 'static', data: 'left' },
516
641
  verticalAlign: { type: 'static', data: 'top' },
517
642
  size: { type: 'static', data: 'shrink' },
518
- gap: { type: 'static', data: 'medium' }
643
+ gap: { type: 'static', data: 'medium' },
644
+ wrap: { type: 'static', data: true }
519
645
  },
520
646
  blocks: [{ breakpoint: 1024, size: { h: 24, w: 12 }, position: { x: 0, y: 0 } }],
521
647
  states: { isSticky: false, isDroppable: true, isScrollable: true, isWrapper: true },
@@ -632,6 +758,109 @@ export function heightTypeNote(stamped) {
632
758
  : stamped.join(', ');
633
759
  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
760
  }
761
+ /**
762
+ * Flex çocuğunun boyut modeli (2026-09-01, `AddSizeStringsToComponentAppearance`):
763
+ * `appearance.widthType` beş değer alır — `cover`/`contain` medya sığdırma, `auto`/`fixed`/`fill`
764
+ * flex-çocuk boyut modu. `fixed` iken serbest CSS uzunluğu `widthString`/`heightString`'den okunur;
765
+ * `fill`/`auto` string taşımaz (Studio paneli mod değişiminde string'i SİLER —
766
+ * `appearance-size.functions.ts` `buildFlexWidthMode`). Öncelik: fill > fixed(+string) > auto >
767
+ * eski 12 kolon span (`widthType` hiç yoksa). Eski backend bilinmeyen `widthType`'ı sessizce
768
+ * düşürür (`ComponentWidthType` enum'una `Fixed`/`Fill` 2026-09-01'de eklendi).
769
+ */
770
+ const WIDTH_TYPES = new Set(['cover', 'contain', 'auto', 'fixed', 'fill']);
771
+ /**
772
+ * `packages/ng-apps-screen-builder/src/flex-layout.ts` `normalizeCssSize`'ın birebir portu:
773
+ * çıplak sayı → px; birimler px/%/vw/vh/vmin/vmax/rem/em/ch; `calc(...)` aynen geçer; gerisi
774
+ * (negatif, `pt`, `fr`, `auto`…) undefined — platform kaydeder ama panel/runtime değeri düşürür.
775
+ */
776
+ export function normalizeCssSize(raw) {
777
+ if (typeof raw === 'number')
778
+ return Number.isFinite(raw) && raw > 0 ? `${raw}px` : undefined;
779
+ const value = String(raw ?? '').trim();
780
+ if (!value)
781
+ return undefined;
782
+ if (/^\d+(\.\d+)?$/.test(value))
783
+ return `${value}px`;
784
+ if (/^\d+(\.\d+)?(px|%|vw|vh|vmin|vmax|rem|em|ch)$/.test(value))
785
+ return value;
786
+ if (/^calc\(.+\)$/.test(value))
787
+ return value;
788
+ return undefined;
789
+ }
790
+ function appearanceOf(node) {
791
+ return (node.appearance && typeof node.appearance === 'object' && !Array.isArray(node.appearance)
792
+ ? node.appearance
793
+ : {});
794
+ }
795
+ /**
796
+ * Studio paritesi: panel çıplak sayıyı `px`e çevirerek yazar (`buildFlexSizeValue`), gateway de
797
+ * aynısını yapar ki iki yoldan kurulan şema aynı görünsün. Yalnız türetilebilir olan çevrilir;
798
+ * geçersiz değerler burada DEĞİL `flexSizeWarnings`'te raporlanır (uyarı, silme değil — platform
799
+ * da silmiyor, yalnız okumuyor).
800
+ */
801
+ export function normalizeSizeStrings(nodes) {
802
+ const fixed = [];
803
+ for (const node of asObjects(nodes)) {
804
+ const appearance = appearanceOf(node);
805
+ for (const field of ['widthString', 'heightString']) {
806
+ const raw = appearance[field];
807
+ if (raw === undefined || raw === null || raw === '')
808
+ continue;
809
+ const normalized = normalizeCssSize(raw);
810
+ if (normalized !== undefined && normalized !== raw) {
811
+ appearance[field] = normalized;
812
+ node.appearance = appearance;
813
+ fixed.push(`${String(node.elementId ?? node.name ?? node.type ?? '?')}.${field}: ${String(raw)} → ${normalized}`);
814
+ }
815
+ }
816
+ fixed.push(...normalizeSizeStrings(node.children));
817
+ }
818
+ return fixed;
819
+ }
820
+ export function sizeStringNote(fixed) {
821
+ if (fixed.length === 0)
822
+ return [];
823
+ 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
+ }
825
+ /**
826
+ * İki uyarı tek yürüyüşte:
827
+ *
828
+ * - **mod uyumsuzluğu** (`mismatchCode`): `widthString`/`heightString` var ama ilgili
829
+ * `*Type` `'fixed'` değil — string yalnız `fixed` modda okunur (`getFlexChildStyle`;
830
+ * `fill` her şeyi ezer), Studio paneli mod değişiminde string'i siler. Yazan ajan
831
+ * büyük olasılıkla modu yazmayı unutmuştur.
832
+ * - **geçersiz değer / bilinmeyen mod** (`invalidCode`): `normalizeCssSize`'dan geçmeyen
833
+ * string ya da `WIDTH_TYPES` dışı `widthType` — platform 200 döner ama değer sessizce
834
+ * düşer (eski backend bilinmeyen enum'u da düşürür).
835
+ */
836
+ export function flexSizeWarnings(nodes, mismatchCode, invalidCode) {
837
+ const warnings = [];
838
+ const walk = (list) => {
839
+ for (const node of list) {
840
+ const appearance = appearanceOf(node);
841
+ 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);
844
+ if (widthType !== undefined && !WIDTH_TYPES.has(widthType)) {
845
+ 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
+ }
847
+ for (const [field, type, mode] of [['widthString', widthType, 'widthType'], ['heightString', heightType, 'heightType']]) {
848
+ const raw = appearance[field];
849
+ if (raw === undefined || raw === null || raw === '')
850
+ continue;
851
+ if (normalizeCssSize(raw) === undefined) {
852
+ 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.`);
853
+ }
854
+ if (type !== 'fixed') {
855
+ 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.`);
856
+ }
857
+ }
858
+ walk(asObjects(node.children));
859
+ }
860
+ };
861
+ walk(asObjects(nodes));
862
+ return warnings;
863
+ }
635
864
  const GRID_COLUMNS = 12;
636
865
  const DESKTOP_BREAKPOINT = 1024;
637
866
  const DEFAULT_RECT = { x: 0, y: 0, w: 12, h: 6 };
@@ -732,7 +961,7 @@ function moveActive(items, active) {
732
961
  ? item
733
962
  : moved.find(entry => entry.elementId === item.elementId) ?? item));
734
963
  }
735
- function reorderSiblings(nodes) {
964
+ function reorderSiblings(nodes, causes) {
736
965
  const laidOut = nodes
737
966
  .map((node, index) => ({ node, elementId: typeof node.elementId === 'string' && node.elementId !== '' ? node.elementId : `#${index}` }))
738
967
  .filter(entry => !ROOT_LAYER_TYPES.has(String(entry.node.type ?? '')));
@@ -741,8 +970,17 @@ function reorderSiblings(nodes) {
741
970
  let items = laidOut.map(entry => ({ elementId: entry.elementId, rect: rectOf(desktopBlock(entry.node)) }));
742
971
  for (const entry of laidOut) {
743
972
  const active = items.find(item => item.elementId === entry.elementId);
744
- if (active)
745
- items = moveActive(items, active);
973
+ if (!active)
974
+ continue;
975
+ // Kimin ittiğini burada yakalıyoruz: bildirim "y değişti" demekle kalmayıp hangi kardeşle
976
+ // hücre paylaşıldığını da söylesin (G-31). Aynı düğüm birden çok turda itilirse SON iten
977
+ // kalır — düzeni açıklayan da odur.
978
+ const beforeMove = new Map(items.map(item => [item.elementId, item.rect.y]));
979
+ items = moveActive(items, active);
980
+ for (const item of items) {
981
+ if (beforeMove.get(item.elementId) !== item.rect.y)
982
+ causes.set(item.elementId, entry.elementId);
983
+ }
746
984
  }
747
985
  let moved = 0;
748
986
  for (const [index, entry] of laidOut.entries()) {
@@ -764,11 +1002,11 @@ function reorderSiblings(nodes) {
764
1002
  }
765
1003
  return moved;
766
1004
  }
767
- function reorderPass(nodes) {
1005
+ function reorderPass(nodes, causes) {
768
1006
  const siblings = asObjects(nodes);
769
- let moved = reorderSiblings(siblings);
1007
+ let moved = reorderSiblings(siblings, causes);
770
1008
  for (const node of siblings) {
771
- moved += reorderPass(node.children);
1009
+ moved += reorderPass(node.children, causes);
772
1010
  }
773
1011
  return moved;
774
1012
  }
@@ -811,12 +1049,17 @@ const MAX_REORDER_PASSES = 8;
811
1049
  */
812
1050
  export function reorderSchemaBlocks(nodes) {
813
1051
  const before = [];
1052
+ const causes = new Map();
814
1053
  collectLayout(nodes, before);
815
- for (let pass = 0; pass < MAX_REORDER_PASSES && reorderPass(nodes) > 0; pass++)
1054
+ for (let pass = 0; pass < MAX_REORDER_PASSES && reorderPass(nodes, causes) > 0; pass++)
816
1055
  continue;
817
1056
  return before
818
1057
  .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ı)"}`);
1058
+ .map(entry => {
1059
+ const cause = causes.get(entry.label);
1060
+ const reason = cause && cause !== entry.label ? ` — '${cause}' ile aynı hücreleri paylaşıyordu` : '';
1061
+ return `${entry.label} y:${entry.y}→${rectOf(desktopBlock(entry.node)).y}${entry.hasBlock ? '' : " (desktop block'u yazıldı)"}${reason}`;
1062
+ });
820
1063
  }
821
1064
  export function reorderNote(moved) {
822
1065
  if (moved.length === 0)
@@ -1177,6 +1420,9 @@ export function stringStyleWarning(ids) {
1177
1420
  * kapsayıcıya `direction: 'column'` yazmak SESSİZCE hiçbir şey yapmaz — doğrulama duvarı da
1178
1421
  * susar, çünkü prop katalogda VARDIR (2026-08-25 ölçümü: `page_schema_validate` yedi prop'a da
1179
1422
  * `valid: true` döndü). Katalog varsayılanına eşit değerler elenir; onlar niyet taşımaz.
1423
+ * `wrap` varsayılanı 2026-09-01'de beş kapsayıcı şemasında ve Studio
1424
+ * `DEFAULT_CONTAINER_LAYOUT_PROPS`'ta `false` → `true` oldu; buradaki küme onu izler,
1425
+ * yoksa varsayılanla kurulmuş her düğüm yanlış pozitif üretir.
1180
1426
  */
1181
1427
  const FLEX_LAYOUT_DEFAULTS = {
1182
1428
  direction: 'row',
@@ -1184,16 +1430,34 @@ const FLEX_LAYOUT_DEFAULTS = {
1184
1430
  verticalAlign: 'top',
1185
1431
  size: 'shrink',
1186
1432
  gap: 'medium',
1187
- wrap: false
1433
+ wrap: true
1188
1434
  };
1189
1435
  /** Kapsayıcı düzenini kuran prop kümesi (`ContainerComponent` şeması): `layout` + ona bağlı altı ayar. */
1190
1436
  export const FLEX_LAYOUT_PROP_NAMES = new Set(['layout', ...Object.keys(FLEX_LAYOUT_DEFAULTS)]);
1437
+ /**
1438
+ * Düzen prop grubunu taşıyan komponent tipleri: `packages/ng-apps-ui/src/components` altında
1439
+ * kendi `schema.ts` dosyasında `horizontalAlign` tanımlayanlar. Kural yalnız bunlara uygulanır — `button` · `avatar` ·
1440
+ * `icon` gibi komponentlerin KENDİ `size` prop'u flex `size`'ı değildir ve dokuz komponent için
1441
+ * yanlış uyarı üretiyordu (09-04 Y-08).
1442
+ */
1443
+ const FLEX_LAYOUT_HOST_TYPES = new Set(['aibox', 'box', 'box-section', 'container', 'page-container', 'form', 'layout', 'widget-area']);
1444
+ /**
1445
+ * Eski `cssgrid` (box/aibox'ın akan gerçek `display:grid` kipi; 2026-09-02'de seçenek olmaktan
1446
+ * çıktı, kayıtlı düğümlerde çalışmaya devam ediyor) düzen prop'larının HEPSİNİ ölü yapmaz:
1447
+ * `getGridChildrenStyle` boşluğu (`gap`/`gapCustom` → `resolveFlexGap`) ve `verticalAlign`'ı
1448
+ * ('stretch' ise `align-items`) okur. Bu ikisini ölü diye bildirmek, çalışan bir ayarı
1449
+ * sildirirdi. Kaynak: `packages/ng-apps-screen-builder/src/grid-layout.ts`.
1450
+ */
1451
+ const CSS_GRID_READ_PROPS = new Set(['gap', 'verticalAlign']);
1191
1452
  export function inertFlexPropNodes(nodes) {
1192
1453
  const found = [];
1193
1454
  const walk = (list) => {
1194
1455
  for (const node of list) {
1195
- if (!isFlexContainer(node)) {
1456
+ if (FLEX_LAYOUT_HOST_TYPES.has(String(node.type ?? '')) && !isFlexContainer(node)) {
1457
+ const isCssGrid = staticPropValue(node, 'layout') === 'cssgrid';
1196
1458
  const inert = Object.keys(FLEX_LAYOUT_DEFAULTS).filter(name => {
1459
+ if (isCssGrid && CSS_GRID_READ_PROPS.has(name))
1460
+ return false;
1197
1461
  const value = staticPropValue(node, name);
1198
1462
  return value !== undefined && value !== null && value !== FLEX_LAYOUT_DEFAULTS[name];
1199
1463
  });
@@ -1214,6 +1478,69 @@ export function inertFlexPropWarning(entries) {
1214
1478
  + "sessizce yok sayılır (istek reddedilmez, prop katalogda vardır). Akış düzeni istiyorsan aynı düğüme "
1215
1479
  + "layout:'flex' de yaz, istemiyorsan bu prop'ları kaldır — platform_guide('page') Kural 1f-2."];
1216
1480
  }
1481
+ /**
1482
+ * Bir yamanın SİLECEĞİ akışlar. Platform referanssız kalan akışı KALICI siler; uyarı yamadan sonra
1483
+ * geldiğinde "önce yedekle" tavsiyesi uygulanamaz hâle geliyordu (09-05 koşusu: sekiz adımlı giriş
1484
+ * akışı sıfırdan yeniden yazıldı). Bu yüzden plan yamadan ÖNCE hesaplanır — `remove_component`
1485
+ * kadar `set_event`/`remove_event` de akış öldürür ve o yolda hiç uyarı yoktu.
1486
+ */
1487
+ export function orphanedFlowPlan(schema, pageEvents, ops) {
1488
+ const nodes = asObjects(schema);
1489
+ const opList = asObjects(ops);
1490
+ const plan = [];
1491
+ const survivingRefs = (skipElementId, skipEvent) => {
1492
+ const surviving = new Set();
1493
+ for (const event of asObjects(pageEvents)) {
1494
+ if (typeof event.value === 'string')
1495
+ surviving.add(event.value);
1496
+ }
1497
+ collectFlowRefsExcept(nodes, skipElementId, surviving);
1498
+ if (skipEvent !== undefined) {
1499
+ const node = findNodeByElementId(nodes, skipElementId);
1500
+ for (const event of asObjects(node?.events)) {
1501
+ if (String(event.name ?? '') !== skipEvent && typeof event.value === 'string')
1502
+ surviving.add(event.value);
1503
+ }
1504
+ for (const flowId of collectFlowRefs({ ...(node ?? {}), events: [] }))
1505
+ surviving.add(flowId);
1506
+ }
1507
+ return surviving;
1508
+ };
1509
+ for (const op of opList) {
1510
+ const target = String(op.target ?? '');
1511
+ if (target === '')
1512
+ continue;
1513
+ if (op.op === 'remove_component') {
1514
+ const doomed = findNodeByElementId(nodes, target);
1515
+ if (!doomed)
1516
+ continue;
1517
+ const losing = collectFlowRefs(doomed);
1518
+ const surviving = survivingRefs(target);
1519
+ const orphaned = [...losing].filter(flowId => !surviving.has(flowId));
1520
+ if (orphaned.length > 0)
1521
+ plan.push({ target, op: 'remove_component', flowIds: orphaned });
1522
+ continue;
1523
+ }
1524
+ if (op.op !== 'set_event' && op.op !== 'remove_event')
1525
+ continue;
1526
+ const eventName = String(op.event ?? '');
1527
+ const node = findNodeByElementId(nodes, target);
1528
+ const current = asObjects(node?.events).find(event => String(event.name ?? '') === eventName);
1529
+ const currentValue = typeof current?.value === 'string' ? current.value : '';
1530
+ if (currentValue === '' || currentValue === String(op.eventValue ?? ''))
1531
+ continue;
1532
+ const surviving = survivingRefs(target, eventName);
1533
+ if (!surviving.has(currentValue))
1534
+ plan.push({ target, op: String(op.op), flowIds: [currentValue] });
1535
+ }
1536
+ return plan;
1537
+ }
1538
+ export function orphanedFlowMessage(plan) {
1539
+ const lines = plan.map(entry => `'${entry.target}' (${entry.op}) → ${entry.flowIds.join(', ')}`);
1540
+ 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(' · ')}. `
1541
+ + 'Akışı korumak için önce flow_get ile gövdesini yedekleyin ya da set_event ile başka bir komponente bağlayın. '
1542
+ + 'Silinmesini bilerek istiyorsanız aynı çağrıyı allowFlowDeletion: true ile tekrarlayın; yanıt hangi akışların silindiğini listeler.';
1543
+ }
1217
1544
  export function orphanedFlowWarnings(schema, pageEvents, ops) {
1218
1545
  const removals = asObjects(ops).filter(op => op.op === 'remove_component');
1219
1546
  if (removals.length === 0)
@@ -1251,6 +1578,112 @@ function findNodeByElementId(nodes, elementId) {
1251
1578
  }
1252
1579
  return undefined;
1253
1580
  }
1581
+ /**
1582
+ * `orphanedFlowWarnings` silinen düğümün REFERANS VERDİĞİ akışları bulur; burası ters yönü kapsar:
1583
+ * silinen düğümü HEDEFLEYEN setter adımlarını. Aynı `elementId` ile hemen yeniden eklense bile
1584
+ * platform komponent kimliğini yeniden üretir ve setter sessizce ölür — akış koşar, istek 200
1585
+ * döner, komponente hiçbir şey yazılmaz ve tek belirti boş komponenttir. ÖLÇÜLDÜ 2026-09-04
1586
+ * (benchmark 4. koşu, `S-9`): teşhis ~8 dakika sürdü, uç doğru veri döndürdüğü ve şema geçerli
1587
+ * olduğu için hatayı yalnız ekran görüntüsü gösterdi. Kural `platform_guide('page')` Kural 1f'de
1588
+ * yazılı ama düğüm silinmeden ÖNCE okunması gerekiyor; bu uyarı tek erken sinyaldir.
1589
+ */
1590
+ export function staleSetterWarnings(flows, ops, schema) {
1591
+ const opList = asObjects(ops);
1592
+ const removed = new Set(opList
1593
+ .filter(op => op.op === 'remove_component')
1594
+ .map(op => String(op.target ?? ''))
1595
+ .filter(target => target.length > 0));
1596
+ if (removed.size === 0)
1597
+ return [];
1598
+ // Silinen KAPSAYICININ altındaki düğümler de gider. Eskiden yalnız op'un kendi target'ına
1599
+ // bakılıyordu; 09-05 koşusunda `sign_in_form` silinince ona değil ÇOCUĞU `btn_sign_in`'e yazan
1600
+ // setter sessizce koptu ve uyarı hiç çıkmadı (G-21). Komşu işlev `orphanedFlowWarnings` alt ağacı
1601
+ // zaten geziyordu — iki işlev aynı olaya bakıp farklı derinlik kullanıyordu.
1602
+ for (const target of [...removed]) {
1603
+ const doomed = findNodeByElementId(asObjects(schema), target);
1604
+ if (doomed)
1605
+ collectElementIds(doomed.children, removed);
1606
+ }
1607
+ const readded = new Set();
1608
+ for (const op of opList.filter(op => op.op === 'add_component'))
1609
+ collectElementIds(op.component, readded);
1610
+ const warnings = [];
1611
+ const affected = [];
1612
+ const seen = new Set();
1613
+ for (const flow of asObjects(flows)) {
1614
+ const flowName = String(flow.name ?? flow.slug ?? flow.id ?? 'adsız akış');
1615
+ for (const step of [...asObjects(flow.steps), ...asObjects(flow.errorSteps)].filter(step => step.type === 'setter')) {
1616
+ const args = (step.args ?? {});
1617
+ for (const iteratee of asObjects(args.iteratees)) {
1618
+ const element = typeof iteratee.element === 'string' ? iteratee.element : '';
1619
+ if (!removed.has(element))
1620
+ continue;
1621
+ const key = `${flow.id ?? flowName}|${step.name ?? step.stepId}|${element}`;
1622
+ if (seen.has(key))
1623
+ continue;
1624
+ seen.add(key);
1625
+ const stepName = String(step.name ?? step.stepId ?? 'adsız adım');
1626
+ 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.`
1628
+ : `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
+ affected.push(`${flowName} · ${stepName} → ${element}`);
1630
+ }
1631
+ }
1632
+ }
1633
+ // Özet satırı BAŞA gelir: yama işlenmeden önce kaç iteratee'nin ve hangi akışların etkileneceği
1634
+ // tek bakışta görünsün (2026-09-06 · S-5, P0-2 Step 7). Tek tek satırlar altında kalır.
1635
+ if (affected.length > 0) {
1636
+ const flowNames = [...new Set(affected.map(entry => entry.split(' · ')[0]))];
1637
+ 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.`);
1638
+ }
1639
+ return warnings;
1640
+ }
1641
+ /**
1642
+ * FLOW-017'nin kör noktası: o kural yalnız BOŞ `element`/`property` görür. 2026-09-09 koşusunda
1643
+ * (S-14) setter dolu ama HENÜZ VAR OLMAYAN bir elementId'ye bağlandı — platform alanı doğrulamaz,
1644
+ * 200 döner ve `element`i null kaydeder; adım koşar, hata vermez, hiçbir şeye yazmaz. Tek belirti
1645
+ * boş komponent. Yakalayan şey yazma SONRASI `silentlyDropped` diff'iydi; bu işlev aynı şeyi
1646
+ * yazmadan önce söyler. Barındırıcı kaydını okumak gerektiği için `policyRules` içine giremez
1647
+ * (check senkron ve yalnız gövdeyi görür) — yeri FLOW-003/FLOW-013 ile aynı, `tools/flows.ts`.
1648
+ */
1649
+ export function unknownSetterTargetWarnings(steps, errorSteps, hostNodes) {
1650
+ const known = new Set();
1651
+ collectElementIds(hostNodes, known);
1652
+ // Küme boşsa barındırıcı okunamamış ya da şema henüz kurulmamıştır: yanlış pozitif üretmektense
1653
+ // sessiz kalırız — uyarının değeri "şemada VAR olanı da söylüyor" olmasından geliyor.
1654
+ if (known.size === 0)
1655
+ return [];
1656
+ const warnings = [];
1657
+ const seen = new Set();
1658
+ for (const step of [...asObjects(steps), ...asObjects(errorSteps)].filter(step => step.type === 'setter')) {
1659
+ const args = (step.args ?? {});
1660
+ for (const iteratee of asObjects(args.iteratees)) {
1661
+ const element = typeof iteratee.element === 'string' ? iteratee.element.trim() : '';
1662
+ if (element.length === 0 || known.has(element))
1663
+ continue;
1664
+ const stepName = String(step.name ?? step.stepId ?? 'adsız adım');
1665
+ const key = `${stepName}|${element}`;
1666
+ if (seen.has(key))
1667
+ continue;
1668
+ seen.add(key);
1669
+ 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.`);
1670
+ }
1671
+ }
1672
+ return warnings;
1673
+ }
1674
+ function collectElementIds(node, into) {
1675
+ if (Array.isArray(node)) {
1676
+ for (const entry of node)
1677
+ collectElementIds(entry, into);
1678
+ return;
1679
+ }
1680
+ if (!node || typeof node !== 'object')
1681
+ return;
1682
+ const record = node;
1683
+ if (typeof record.elementId === 'string')
1684
+ into.add(record.elementId);
1685
+ collectElementIds(record.children, into);
1686
+ }
1254
1687
  function collectFlowRefs(node) {
1255
1688
  const found = new Set();
1256
1689
  const visitProps = (value) => {