@appaflytech/zeyrek-ai-mcp 0.0.7 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/knowledge/backendConstraints.js +3 -0
  2. package/dist/knowledge/componentCatalog.js +59 -15
  3. package/dist/knowledge/normalize.js +459 -23
  4. package/dist/knowledge/propTypes.js +5 -4
  5. package/dist/knowledge/rules.js +110 -5
  6. package/dist/knowledge/styleResolve.js +6 -1
  7. package/dist/platform/http.js +10 -2
  8. package/dist/platform/screenshot.js +80 -8
  9. package/dist/platform/strictArguments.js +32 -0
  10. package/dist/platform/writeDiff.js +6 -3
  11. package/dist/resources/examples/page-sign-in.json +1 -1
  12. package/dist/resources/guides/component.md +1 -1
  13. package/dist/resources/guides/expressions.md +6 -0
  14. package/dist/resources/guides/getting-started.md +2 -0
  15. package/dist/resources/guides/layout.md +6 -0
  16. package/dist/resources/guides/microservice.md +105 -5
  17. package/dist/resources/guides/page.md +81 -18
  18. package/dist/resources/guides/translation.md +11 -10
  19. package/dist/resources/guides/workflow.md +50 -1
  20. package/dist/resources/schema-fingerprint.json +2 -2
  21. package/dist/resources/schemas/CreateMicroserviceCommand.json +4 -1
  22. package/dist/resources/schemas/CreateWorkflowCommand.json +4 -1
  23. package/dist/resources/schemas/PreviewMicroserviceCommand.json +4 -1
  24. package/dist/resources/schemas/PreviewWorkflowCommand.json +4 -1
  25. package/dist/resources/schemas/StepDto.json +4 -1
  26. package/dist/resources/schemas/UpdateMicroserviceCommand.json +4 -1
  27. package/dist/resources/schemas/UpdateWorkflowCommand.json +4 -1
  28. package/dist/server.js +2 -1
  29. package/dist/tools/flows.js +9 -3
  30. package/dist/tools/layouts.js +4 -4
  31. package/dist/tools/microservices.js +70 -6
  32. package/dist/tools/pages.js +127 -57
  33. package/dist/tools/queries.js +72 -6
  34. package/dist/tools/workflows.js +9 -5
  35. package/package.json +1 -1
@@ -125,7 +125,7 @@ const conditionRuleIdPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}
125
125
  // Runtime.svelte'de HAM verilir (`argument: step.args`), yani bu alanlarda ifade değerlendirilmez.
126
126
  const flowLiteralTextFields = new Map([
127
127
  ['alert', ['message', 'description']],
128
- ['confirm', ['message']]
128
+ ['confirm', ['message', 'title', 'confirmText', 'cancelText']]
129
129
  ]);
130
130
  const flowExpressionLooking = /\$\{|\b(?:Steps|Page|Row|Widget|LocalStorage|state|self|item|event|error|cookie)\s*(?:\?\.|\.|\[)/;
131
131
  // Barındırıcıya özel tipler (`navigate` web'de, `confirm`/`download` mobilde register edilmez)
@@ -235,7 +235,7 @@ export function flowExpressionSites(step) {
235
235
  }
236
236
  return sites;
237
237
  }
238
- const writeOperations = new Set(['create', 'update', 'delete', 'upsert']);
238
+ const writeOperations = new Set(['create', 'update', 'delete', 'upsert', 'bulk-create', 'bulk-update', 'bulk-delete']);
239
239
  /** Built-in `roles` entity'si ve slug alanı — SeedData her projeye SABİT id'lerle açar (EntityConstants.cs). */
240
240
  const rolesEntityId = '01917ede-88a2-72ea-8397-2619e5d5dca0';
241
241
  const rolesSlugFieldId = '0199c35d-cfc8-77d4-9c20-29b857153147';
@@ -346,6 +346,38 @@ function unreadSelectFields(step, steps) {
346
346
  .map(field => String(field.fieldSlug ?? field.slug ?? ''))
347
347
  .filter(slug => slug.length > 0 && !new RegExp(`\\b${escapeForPattern(slug)}\\b`).test(usage));
348
348
  }
349
+ const SQL_COMMENT_PATTERN = /--[^\n]*|\/\*[\s\S]*?\*\//g;
350
+ const QUERY_PARAMETER_TOKEN = String.raw `(?:\{\{\s*([A-Za-z_]\w*)\s*\}\}|(?<![:@\w'])@([A-Za-z_]\w*))(?:\s*::\s*\w+)*`;
351
+ const EMPTY_STRING_COMPARISON_PATTERN = new RegExp(`${QUERY_PARAMETER_TOKEN}\\s*=\\s*(?<!')''(?!')|(?<!')''\\s*=\\s*${QUERY_PARAMETER_TOKEN}`, 'g');
352
+ /**
353
+ * `rawQuery`'de bir parametreyi boş string'le EŞİTLİK üzerinden karşılaştıran yerlerin parametre
354
+ * adları (`@p = ''`, `@p::text = ''`, `'' = {{p}}`). Yorumlar önce atılır — platform da yorum
355
+ * içindeki belirteci parametre saymıyor. Yalnız `=` aranır: `@p <> ''` boş değerde NULL (yanlış)
356
+ * verir ve "doluysa" niyetiyle yazıldığında sonuç zaten doğrudur.
357
+ */
358
+ function emptyStringComparisons(rawQuery) {
359
+ if (typeof rawQuery !== 'string')
360
+ return [];
361
+ const names = new Set();
362
+ for (const match of rawQuery.replace(SQL_COMMENT_PATTERN, ' ').matchAll(EMPTY_STRING_COMPARISON_PATTERN)) {
363
+ const name = match[1] ?? match[2] ?? match[3] ?? match[4];
364
+ if (name !== undefined)
365
+ names.add(name);
366
+ }
367
+ return [...names];
368
+ }
369
+ /** `rawQuery`'nin başvurduğu parametre adları (`@p`, `{{p}}`), yorumlar atılarak; ilk görülme sırasıyla. */
370
+ export function rawQueryParameterNames(rawQuery) {
371
+ if (typeof rawQuery !== 'string')
372
+ return [];
373
+ const names = new Set();
374
+ for (const match of rawQuery.replace(SQL_COMMENT_PATTERN, ' ').matchAll(new RegExp(QUERY_PARAMETER_TOKEN, 'g'))) {
375
+ const name = match[1] ?? match[2];
376
+ if (name !== undefined)
377
+ names.add(name);
378
+ }
379
+ return [...names];
380
+ }
349
381
  const isRecord = (value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value);
350
382
  const functionsGuide = 'ngapps://guides/functions';
351
383
  export const policyRules = [
@@ -418,7 +450,7 @@ export const policyRules = [
418
450
  id: 'MS-016',
419
451
  module: 'microservice',
420
452
  appliesTo: ['create', 'update', 'preview'],
421
- sourceRef: "İK koşusu M-3: operation 'upsert' olan uçta expression'la yazılan alan UPDATE yolunda yeniden değerlendiriliyor; Incremental sicil alanı EMP-0005 → EMP-0006 diye sessizce değişti. Platformda alan bazlı writeOn (create|update|always) YOK — bu kural o gelene kadarki tek uyarı",
453
+ sourceRef: "İK koşusu M-3: operation 'upsert' olan uçta expression'la yazılan alan UPDATE yolunda yeniden değerlendiriliyor; Incremental sicil alanı EMP-0005 → EMP-0006 diye sessizce değişti. Alan bazlı writeOn (create|update|always) 2026-09-04'te eklendi (B-12), bu yüzden kural artık çözümü söyler ve writeOn:'create' taşıyan alanda susar",
422
454
  check: definition => {
423
455
  const parameters = (definition.sourceParameters ?? {});
424
456
  if (String(parameters.operation ?? '') !== 'upsert')
@@ -426,13 +458,14 @@ export const policyRules = [
426
458
  const generated = asArray(parameters.fields)
427
459
  .map(entry => entry)
428
460
  .filter(field => String(field.source ?? '') === 'expression')
461
+ .filter(field => String(field.writeOn ?? 'always').toLowerCase() !== 'create')
429
462
  .map(field => String(field.alias ?? field.fieldId ?? '?'));
430
463
  if (generated.length === 0)
431
464
  return [];
432
465
  return [{
433
466
  ruleId: 'MS-016',
434
467
  severity: 'warn',
435
- message: `upsert ucunda expression kaynaklı alan var (${generated.join(', ')}): upsert UPDATE yolunda da bu ifadeyi yeniden değerlendirir, yani sicil/kod gibi ÜRETİLEN bir değer güncellemede sessizce değişir. Alanı yalnız oluşturmada yazmak istiyorsanız create ve update için iki ayrı uç kurun — platformda alan bazlı 'yalnız create'te yaz' ifadesi henüz yok.`,
468
+ message: `upsert ucunda expression kaynaklı alan var (${generated.join(', ')}): upsert UPDATE yolunda da bu ifadeyi yeniden değerlendirir, yani sicil/kod gibi ÜRETİLEN bir değer güncellemede sessizce değişir. Alan yalnız oluşturmada yazılacaksa fields[] öğesine writeOn: 'create' koyun — upsert'in UPDATE dalı o alanı satıra hiç koymaz ve bu uyarı susar. writeOn yalnız operation:'upsert' ucunda geçerlidir (MS-017) ve bir 'kısmi güncelleme' bayrağı DEĞİLDİR: listelenen alan gövdede gelmezse yine NULL yazılır.`,
436
469
  guide: 'ngapps://guides/microservice'
437
470
  }];
438
471
  }
@@ -451,6 +484,23 @@ export const policyRules = [
451
484
  guide: 'ngapps://guides/microservice'
452
485
  }]
453
486
  },
487
+ {
488
+ id: 'QRY-007',
489
+ module: 'query',
490
+ appliesTo: ['create', 'update', 'preview'],
491
+ sourceRef: "2026-09-15 bulgusu: pano sorgusu '(@tech = '' OR w.technician_id::text = @tech)' ile 0 satır döndü, hata yok. Kök neden Query.PrepareQueryParameters / NormalizePreviewParameters: boş ya da yalnız boşluktan oluşan değer NULL bağlanır, bu yüzden '@p = ''' hiçbir değerle doğru olmaz. Bulgunun 'dokunulmamış dropdown null gönderiyor' teşhisi yanlıştı — '' gönderen form da aynı sonucu alır. warn: kalıp hatalı ama yazmayı durdurmak gerekmiyor",
492
+ check: definition => {
493
+ const names = emptyStringComparisons(definition.rawQuery);
494
+ if (names.length === 0)
495
+ return [];
496
+ return [{
497
+ ruleId: 'QRY-007',
498
+ severity: 'warn',
499
+ message: `rawQuery parametreyi boş string'le karşılaştırıyor (${names.map(name => `@${name} = ''`).join(', ')}): platform boş ya da yalnız boşluktan oluşan parametre değerini NULL bağlar, NULL = '' hiçbir zaman doğru olmaz ve 'filtre boşsa hepsini getir' dalı ASLA çalışmaz — filtre boş geldiğinde sorgu hatasız 0 satır döner. Değerin form bileşeninden null ya da '' gelmesi fark etmez. Şöyle yazın: (@${names[0]} IS NULL OR kolon::text = @${names[0]}).`,
500
+ guide: 'ngapps://guides/microservice'
501
+ }];
502
+ }
503
+ },
454
504
  {
455
505
  id: 'MS-018',
456
506
  module: 'microservice',
@@ -2215,7 +2265,7 @@ export const policyRules = [
2215
2265
  id: 'FLOW-022',
2216
2266
  module: 'flow',
2217
2267
  appliesTo: ['create', 'update'],
2218
- sourceRef: "alert.ts `const { message, alertType, description } = task.argument` → toast.error(message, { description }) / dialog.alert(message); confirm.ts `dialog.confirm(task.argument.message)`. Argüman Runtime.svelte'de HAM geçer (`argument: step.args`) — bu alanlarda hiçbir ifade değerlendirilmez, yazılan metin ekranda harfi harfine görünür (2026-09-06·2 koşusu S-13'ün ikinci turu: rehber hata mesajını göstermek için alert adımını öneriyordu)",
2268
+ sourceRef: "alert.ts `const { message, alertType, description } = task.argument` → toast.error(message, { description }) / dialog.alert(message); confirm.ts `dialog.confirm(message, { title, confirmText, cancelText })` (boş çerçeve metni dialog.functions.ts resolveDialogFrame'de sabit metne düşer). Argüman Runtime.svelte'de HAM geçer (`argument: step.args`) — bu alanlarda hiçbir ifade değerlendirilmez, yazılan metin ekranda harfi harfine görünür (2026-09-06·2 koşusu S-13'ün ikinci turu: rehber hata mesajını göstermek için alert adımını öneriyordu)",
2219
2269
  check: definition => flowAllSteps(definition)
2220
2270
  .flatMap((step) => {
2221
2271
  const type = String(step.type ?? '');
@@ -2821,3 +2871,58 @@ export async function microfunctionStepFindings(platform, project, environment,
2821
2871
  }
2822
2872
  return applyPolicyMode(platform.config, findings, policyOverride);
2823
2873
  }
2874
+ const partialWriteOperations = new Set(['create', 'update', 'upsert']);
2875
+ const inputReferencePattern = /\binput\.([A-Za-z_$][\w$]*)/g;
2876
+ function inputReferencesOf(rule) {
2877
+ return asArray(rule.fields)
2878
+ .filter((field) => Boolean(field) && typeof field === 'object')
2879
+ .flatMap(field => [...String(field.value ?? '').matchAll(inputReferencePattern)].map(match => match[1]))
2880
+ .filter((name) => typeof name === 'string');
2881
+ }
2882
+ /**
2883
+ * WF-031 — yazma adımı, akışın giriş DTO'sunda OLMAYAN bir `input.x` alanını yazıyorsa uyarır.
2884
+ * Böyle bir ifade çalışma anında `undefined` döner, `Parse` onu `null` yapar ve platform kolonu
2885
+ * NULL yazar: satırdaki mevcut değer silinir, yanıt yine 200'dür (risk uygulaması Kanban taşıma
2886
+ * bulgusu, 2026-09-15 — `{ id, status }` gövdesi `title`/`owner_id`/`due_date`'i sildi).
2887
+ *
2888
+ * Kural yalnız DTO'da HİÇ olmayan alanı sayar: "DTO'da var ama çağıran göndermedi" vakası
2889
+ * tanımdan görülemez, onu rehber anlatır. DTO şeması okunamazsa (404/erişim) kural susar —
2890
+ * bir okuma hatası yazma çağrısını engellememelidir.
2891
+ */
2892
+ export async function undefinedInputFieldFindings(platform, project, environment, definition, kind, policyOverride = false) {
2893
+ if (platform.config.policyMode === 'off')
2894
+ return [];
2895
+ const body = workflowBody(definition, kind);
2896
+ if (!hasText(body.parameterDtoId))
2897
+ return [];
2898
+ const candidates = workflowSteps(body)
2899
+ .filter(step => step.type === 'table' && step.rule)
2900
+ .filter(step => partialWriteOperations.has(String(step.rule.operation ?? '').toLowerCase()))
2901
+ .map(step => ({ step, references: [...new Set(inputReferencesOf(step.rule))] }))
2902
+ .filter(candidate => candidate.references.length > 0);
2903
+ if (candidates.length === 0)
2904
+ return [];
2905
+ let dtoFieldNames;
2906
+ try {
2907
+ const payload = await platform.tenant('GET', project, `/datatransferobjects/${String(body.parameterDtoId)}/scheme`, { environment });
2908
+ const items = asArray(payload?.data ?? payload).filter((item) => Boolean(item) && typeof item === 'object');
2909
+ dtoFieldNames = new Set(items.map(item => String(item.name ?? '')));
2910
+ }
2911
+ catch (error) {
2912
+ if (error instanceof PlatformApiError)
2913
+ return [];
2914
+ throw error;
2915
+ }
2916
+ const findings = candidates.flatMap(({ step, references }) => {
2917
+ const missing = references.filter(name => !dtoFieldNames.has(name));
2918
+ return missing.length > 0
2919
+ ? [{
2920
+ ruleId: 'WF-031',
2921
+ severity: 'warn',
2922
+ message: `'${stepLabel(step)}' adımı ${missing.map(name => `input.${name}`).join(', ')} alanlarını yazıyor ama akışın giriş DTO'sunda bu alanlar YOK: ifade undefined döner, platform kolonu NULL yazar ve satırdaki mevcut değer SİLİNİR (yanıt yine 200). Yazma adımında SET listesi fields[]'tir, gövde değil — 'göndermediğim alan değişmez' (REST PATCH) sezgisi burada geçmez. İki doğru çıkış: (1) adımı EYLEM BAŞINA daraltın, fields[] yalnız bu eylemin gerçekten yazdığı alanları listelesin (kanban taşıma: yalnız status); (2) alanı DTO'ya ekleyin VE çağıranın her istekte gönderdiğinden emin olun — DTO'ya eklemek yalnız izin verir, göndermeyi garanti etmez. Alan kümesi ancak çağrı anında belliyse akışta oku-birleştir: önce table get, sonra "typeof input.x !== 'undefined' && input.x !== null ? input.x : read_row.data.x" (bedeli fazladan okuma ve son-yazan-kazanır).`,
2923
+ guide: 'ngapps://guides/workflow'
2924
+ }]
2925
+ : [];
2926
+ });
2927
+ return applyPolicyMode(platform.config, findings, policyOverride);
2928
+ }
@@ -419,6 +419,12 @@ export const TYPED_STYLE_COMPONENTS = {
419
419
  // Taşma hedefi olmayan komponentte contentOverflow ölü kural üretirdi.
420
420
  omit: { surfaceStyle: ['contentOverflow'] }
421
421
  },
422
+ form: {
423
+ // Çerçeve <form> KÖKÜNDE: yüzey köke, taşma gövdeye (S-19, 2026-09-15).
424
+ // Genel düğüm yüzeyi yolu değeri kökün dışındaki kutuya yazıp ikinci çerçeve çiziyordu.
425
+ groups: ['surfaceStyle'],
426
+ ext: { overflowTarget: '&__body' }
427
+ },
422
428
  icon: {
423
429
  // Yalnız hover: surface çipin (chipShape/chipColor), typography da boyutun
424
430
  // (size) ve rengin (color) çift kanalı olurdu — görsel kararlar semantik
@@ -439,7 +445,6 @@ export const TYPED_STYLE_COMPONENTS = {
439
445
  export const NODE_SURFACE_TYPES = new Set([
440
446
  'table',
441
447
  'datatable',
442
- 'form',
443
448
  'collapsible-container',
444
449
  'drawer',
445
450
  'modal',
@@ -42,8 +42,16 @@ function appendQuery(url, query) {
42
42
  if (value === undefined || value === null || value === '')
43
43
  continue;
44
44
  if (Array.isArray(value)) {
45
- for (const entry of value)
46
- url.searchParams.append(key, String(entry));
45
+ if (value.some(entry => entry !== null && typeof entry === 'object')) {
46
+ url.searchParams.append(key, JSON.stringify(value));
47
+ }
48
+ else {
49
+ for (const entry of value)
50
+ url.searchParams.append(key, String(entry));
51
+ }
52
+ }
53
+ else if (typeof value === 'object') {
54
+ url.searchParams.append(key, JSON.stringify(value));
47
55
  }
48
56
  else {
49
57
  url.searchParams.append(key, String(value));
@@ -33,11 +33,54 @@ export function actionScript(action) {
33
33
  return { found: true, applied: true, requests: count };
34
34
  })()`;
35
35
  }
36
+ // G-09 — kanban kartı ve takvim etkinliği HTML5 DnD ile taşınır. Olaylar sentetik atılır ve
37
+ // TEK DataTransfer'i paylaşır; koordinat bırakma hedefinin merkezidir, çünkü takvimin saat
38
+ // kolonu bırakılan saati `clientY`'den hesaplar (slot hedefi o saatin ortasına düşer).
39
+ // Playwright `dragTo` gerçek fare sürer ama ScreenshotPage bu yeteneği taşımaz; betik yolu
40
+ // öteki eylemlerle aynı ölçüm döngüsünden (requests · flowRuns) geçer.
41
+ if (action.action === 'drag') {
42
+ const destination = JSON.stringify(`[data-element-id="${String(action.to ?? '')}"]`);
43
+ const destinationName = JSON.stringify(String(action.to ?? ''));
44
+ return `(function () {
45
+ var count = performance.getEntriesByType('resource').length;
46
+ var node = document.querySelector(${selector});
47
+ if (!node) return { found: false, applied: false, requests: count };
48
+ var drop = document.querySelector(${destination});
49
+ if (!drop) return { found: false, applied: false, note: "sürükleme kaynağı bulundu ama bırakma hedefi ('to') sayfada yok: " + ${destinationName} + " — kanban kolonu <elementId>__col-<value>, takvim günü <elementId>__day-<yyyy-MM-dd>, saat dilimi <elementId>__slot-<yyyy-MM-dd>T<HH>", requests: count };
50
+ if (node.getAttribute('draggable') !== 'true') return { found: true, applied: false, note: "kaynak sürüklenebilir değil (draggable != true) — kanban isDraggable ya da eventcalendar isEditable kapalı olabilir", requests: count };
51
+ var transfer = new DataTransfer();
52
+ var center = function (element) { var rect = element.getBoundingClientRect(); return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }; };
53
+ var fire = function (element, type, at) { element.dispatchEvent(new DragEvent(type, { bubbles: true, cancelable: true, composed: true, dataTransfer: transfer, clientX: at.x, clientY: at.y })); };
54
+ var from = center(node);
55
+ var to = center(drop);
56
+ fire(node, 'dragstart', from);
57
+ fire(drop, 'dragenter', to);
58
+ fire(drop, 'dragover', to);
59
+ fire(drop, 'drop', to);
60
+ fire(node, 'dragend', to);
61
+ return { found: true, applied: true, requests: count };
62
+ })()`;
63
+ }
64
+ const target = String(action.target ?? '');
65
+ const root = rootElementId(target);
66
+ // G-14 — `applied` yalnız "DOM'a yazıldı" demekti: tarih seçici olayı dinlemiyordu, kutu dolu
67
+ // görünüyor, prop boş kalıyordu. Kökün prop parmak izi eylemden ÖNCE alınır; runActions
68
+ // yerleşmeden sonra yeniden okuyup `propWritten`'ı çıkarır. Çok alanlı bileşenin kökünde tek
69
+ // değer yoktur — ilk kutuya yazmak yarım değeri "uygulandı" diye raporluyordu, kök reddedilir.
36
70
  return `(function () {
37
71
  var count = performance.getEntriesByType('resource').length;
38
72
  var node = document.querySelector(${selector});
39
73
  if (!node) return { found: false, applied: false, requests: count };
40
74
  var wanted = String(${value});
75
+ var propsBefore = ${runtimePropsScript(root)};
76
+ var finish = function (result) { if (propsBefore !== undefined) result.propsBefore = propsBefore; return result; };
77
+
78
+ if (${JSON.stringify(root === target)}) {
79
+ var parts = Array.prototype.slice.call(node.querySelectorAll(${JSON.stringify(`[data-element-id^="${root}${INTERNAL_ROLE_SEPARATOR}"]`)}))
80
+ .filter(function (part) { return part.querySelector('input, select, textarea'); })
81
+ .map(function (part) { return part.getAttribute('data-element-id'); });
82
+ if (parts.length > 1) return finish({ found: true, applied: false, note: "çok alanlı bileşen: kökte tek değer yok, değeri kutulardan birine yazın — " + parts.join(' · '), requests: count });
83
+ }
41
84
 
42
85
  // Açılır kutu (dropdown / multi-select) NATIVE alan değildir: değeri yazmak
43
86
  // hiçbir şey yapmaz, seçeneğe TIKLANMASI gerekir. Liste ancak kutu açıkken
@@ -55,29 +98,46 @@ export function actionScript(action) {
55
98
  var match = options.filter(function (option) { return option.getAttribute('data-value') === wanted; })[0]
56
99
  || options.filter(function (option) { return (option.textContent || '').trim() === wanted; })[0];
57
100
  if (!match) {
58
- resolve({ found: true, applied: false, note: "açılır kutu açıldı ama bu değere karşılık gelen seçenek yok: " + wanted + " — options listesini page_runtime_get ile doğrulayın; seçenek sayısı: " + options.length, requests: count });
101
+ resolve(finish({ found: true, applied: false, note: "açılır kutu açıldı ama bu değere karşılık gelen seçenek yok: " + wanted + " — options listesini page_runtime_get ile doğrulayın; seçenek sayısı: " + options.length, requests: count }));
59
102
  return;
60
103
  }
61
104
  (match.querySelector('button') || match).click();
62
- resolve({ found: true, applied: true, requests: count });
105
+ resolve(finish({ found: true, applied: true, requests: count }));
63
106
  }, 300);
64
107
  });
65
108
  }
66
109
 
67
110
  var field = node.matches('input, select, textarea') ? node : node.querySelector('input, select, textarea');
68
- if (!field) return { found: true, applied: false, note: 'düğümde input/select/textarea yok — bu komponent native alan kullanmıyor olabilir, click ile deneyin', requests: count };
111
+ if (!field) return finish({ found: true, applied: false, note: 'düğümde input/select/textarea yok — bu komponent native alan kullanmıyor olabilir, click ile deneyin', requests: count });
69
112
  var proto = field instanceof HTMLSelectElement ? HTMLSelectElement.prototype
70
113
  : (field instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype);
71
114
  if (field instanceof HTMLInputElement && (field.type === 'checkbox' || field.type === 'radio')) {
72
115
  var shouldCheck = wanted === 'true' || wanted === '1' || wanted === 'on';
73
116
  if (field.checked !== shouldCheck) field.click();
74
- return { found: true, applied: true, requests: count };
117
+ return finish({ found: true, applied: true, requests: count });
75
118
  }
76
119
  var setter = Object.getOwnPropertyDescriptor(proto, 'value').set;
77
120
  setter.call(field, ${value});
78
121
  field.dispatchEvent(new Event('input', { bubbles: true }));
79
122
  field.dispatchEvent(new Event('change', { bubbles: true }));
80
- return { found: true, applied: true, requests: count };
123
+ return finish({ found: true, applied: true, requests: count });
124
+ })()`;
125
+ }
126
+ /** `rf_range__start` → `rf_range`: iç rol hedefinin prop'ları kök düğümde yaşar. */
127
+ export function rootElementId(target) {
128
+ const separator = target.indexOf(INTERNAL_ROLE_SEPARATOR);
129
+ return separator > 0 ? target.slice(0, separator) : target;
130
+ }
131
+ /**
132
+ * Düğümün runtime prop'larının parmak izi (`@ng-apps/flow` → flow-runtime-props.ts,
133
+ * `window.__ngAppsReadProps`). `undefined` okuyucu yayınlanmıyor (eski runtime) ya da prop'lar
134
+ * serileşmiyor; `null` hiçbir makine düğümü tanımıyor; dize karşılaştırılabilir iz.
135
+ */
136
+ export function runtimePropsScript(elementId) {
137
+ return `(function () { /* runtimePropsSnapshot */
138
+ var read = window.__ngAppsReadProps;
139
+ if (typeof read !== 'function') return undefined;
140
+ try { var props = read(${JSON.stringify(elementId)}); return props === null ? null : JSON.stringify(props); } catch (error) { return undefined; }
81
141
  })()`;
82
142
  }
83
143
  /**
@@ -428,19 +488,31 @@ export class StudioScreenshotter {
428
488
  : navigated
429
489
  ? activityAfter.completed
430
490
  : activityBefore === null ? undefined : Math.max(activityAfter.completed - activityBefore.completed, 0);
491
+ // G-14 — DOM'a yazmak değerin prop'a ulaştığını kanıtlamaz. İz yalnız okuyucu yayınlanıyor ve
492
+ // makine düğümü tanıyorsa (dize) karşılaştırılır; aksi hâlde propWritten bilinmez kalır.
493
+ const propsAfter = typeof before.propsBefore === 'string' && before.applied === true && !navigated
494
+ ? await page.evaluate(runtimePropsScript(rootElementId(String(action.target ?? '')))).catch(() => undefined)
495
+ : undefined;
496
+ const propWritten = typeof propsAfter === 'string' ? propsAfter !== before.propsBefore : undefined;
497
+ const applied = before.applied === true && propWritten !== false;
498
+ const note = propWritten === false
499
+ ? `değer DOM'a yazıldı ama '${rootElementId(String(action.target ?? ''))}' düğümünün runtime prop'ları DEĞİŞMEDİ — bileşen olayı kendi change yoluna çevirmedi ya da değer zaten aynıydı. Çok alanlı bileşende <elementId>__<rol> kutusunu hedefleyin (component_get → internalRoles).`
500
+ : before.note;
431
501
  outcomes.push({
432
502
  index,
433
503
  action: String(action.action),
434
504
  ...(action.target === undefined ? {} : { target: action.target }),
505
+ ...(action.to === undefined ? {} : { to: action.to }),
435
506
  found: before.found === true,
436
- applied: before.applied === true,
437
- ...(before.note === undefined ? {} : { note: before.note }),
507
+ applied,
508
+ ...(propWritten === undefined ? {} : { propWritten }),
509
+ ...(note === undefined ? {} : { note }),
438
510
  requests,
439
511
  ...(flowRuns === undefined ? {} : { flowRuns }),
440
512
  pageState: readiness.state
441
513
  });
442
514
  }
443
- logStep('tarayıcı ⤵', `${outcomes.length} etkileşim adımı koştu`, outcomes.map(outcome => `${outcome.index}. ${outcome.action}${outcome.target ? ` '${outcome.target}'` : ''} → ${outcome.found ? (outcome.applied ? 'uygulandı' : 'bulundu ama uygulanamadı') : 'hedef BULUNAMADI'} · ${outcome.requests} ağ isteği`));
515
+ logStep('tarayıcı ⤵', `${outcomes.length} etkileşim adımı koştu`, outcomes.map(outcome => `${outcome.index}. ${outcome.action}${outcome.target ? ` '${outcome.target}'` : ''}${outcome.to ? ` ⇒ '${outcome.to}'` : ''} → ${outcome.found ? (outcome.applied ? 'uygulandı' : 'bulundu ama uygulanamadı') : 'hedef BULUNAMADI'} · ${outcome.requests} ağ isteği`));
444
516
  return outcomes;
445
517
  }
446
518
  /**
@@ -0,0 +1,32 @@
1
+ import { z } from 'zod';
2
+ function isRawShape(value) {
3
+ if (!value || typeof value !== 'object' || '_def' in value)
4
+ return false;
5
+ return Object.values(value).every(entry => entry instanceof z.ZodType);
6
+ }
7
+ /**
8
+ * GW-005 — tanınmayan üst düzey argüman REDDEDİLİR.
9
+ *
10
+ * SDK ham şekli `z.object(shape)` ile sarar ve zod'un varsayılanı bilinmeyen anahtarı hata vermeden
11
+ * ATMAKTIR: şemada olmayan bir alan doğrulamadan geçer, handler'a hiç ulaşmaz ve çağrı başarıyla
12
+ * biter. 2026-09-15 G-13'te `microservice_execute`/`microservice_debug`/`query_preview`'a verilen
13
+ * `parameters` böyle kayboldu — dört farklı filtre aynı gövdeyi döndü ve ölçüm aracı çağıranı
14
+ * olmayan bir sorgu kusuruna yönlendirdi. Tek tek tool'lara alan eklemek sınıfı kapatmaz; sessizlik
15
+ * burada, SDK'ya şema verilmeden önce kaldırılır.
16
+ */
17
+ export function rejectUnknownArguments(server) {
18
+ const registerTool = server.registerTool.bind(server);
19
+ server.registerTool = ((name, config, callback) => {
20
+ if (!isRawShape(config.inputSchema))
21
+ return registerTool(name, config, callback);
22
+ const shape = config.inputSchema;
23
+ const accepted = Object.keys(shape).join(', ');
24
+ const inputSchema = z.object(shape, {
25
+ errorMap: (issue, context) => issue.code === 'unrecognized_keys'
26
+ ? { message: `${name} şu üst düzey argümanları tanımıyor: ${issue.keys.map(key => `'${key}'`).join(', ')}. Tanınmayan alan sessizce yok sayılmaz — çağrı hiç gönderilmedi. Bu tool'un okuduğu alanlar: ${accepted}.` }
27
+ : { message: context.defaultError }
28
+ }).strict();
29
+ return registerTool(name, { ...config, inputSchema }, callback);
30
+ });
31
+ return server;
32
+ }
@@ -1,3 +1,4 @@
1
+ import { PROP_VALUE_TYPES } from '../knowledge/normalize.js';
1
2
  import { logStep } from '../observability/trace.js';
2
3
  const maxDrops = 20;
3
4
  const maxDepth = 12;
@@ -214,9 +215,11 @@ export function unwrapProps(value) {
214
215
  const record = value;
215
216
  const isProp = typeof record.type === 'string' && (Object.hasOwn(record, 'data') || Object.hasOwn(record, 'ref'));
216
217
  if (isProp) {
217
- // `code` ve `translation` prop'larında taşıyıcı alan `ref`'tir; `data` yalnız tasarım zamanı
218
- // yedeğidir (platform_guide('translation')).
219
- const carrier = record.type === 'code' || record.type === 'translation'
218
+ // Platform `static` dışındaki her tipte değeri `ref`'e yazar (Prop ctor'u,
219
+ // DesignComponentManagementSchema.cs:666); `data` ya boştur ya türetilmiştir (file URL'i, page
220
+ // slug'ı, translation tasarım yedeği). `data`'yı taşıyıcı saymak bağlı akışı her yazmada
221
+ // "kaydedilen null" gösteriyordu (S-7).
222
+ const carrier = record.type !== 'static' && PROP_VALUE_TYPES.has(record.type)
220
223
  ? (Object.hasOwn(record, 'ref') ? record.ref : record.data)
221
224
  : (Object.hasOwn(record, 'data') ? record.data : record.ref);
222
225
  return unwrapProps(carrier);
@@ -13,7 +13,7 @@
13
13
  "rawRedirect": "redirect isRaw:true iken path ÇOK SATIRLI JS bloğudur ve son ifadenin değeri hedef adrestir. 'Korunan sayfaya gitmek isterken login'e düşen kullanıcıyı geri gönderme' deseni budur; yoksa sabit bir sayfaya gidilir (pageId + path).",
14
14
  "objectProp": "Prop değeri nesne olabilir: icon → { type:'static', ref:{ name:'progress_activity' } }.",
15
15
  "pageIsPublic": "Giriş sayfasına middlewareIds BAĞLANMAZ (guard'lı olsaydı kullanıcı giriş yapamazdı). Buna karşılık diğer sayfalar ve korunan uçlar middlewareIds ile bağlanır.",
16
- "notificationIsHidden": "notification'ın şemasında isHidden 2026-08-26'dan beri VARDIR: props.isHidden'ı doğrudan bildirime yazabilirsin. Bu örnekteki box_message sarmalayıcısı o dönemin kalıntısıdır (crm koşumu B-10) ve tek bir bildirim için ARTIK GEREKMEZ — burada duruyor çünkü şeridi bir kaba sarıp birden çok parçayı birlikte gösterip gizliyor. appearance.isHidden ise hiçbir zaman çözüm DEĞİLDİR — runtime onu hiç okumaz.",
16
+ "notificationIsHidden": "notification'ın şemasında isHidden 2026-08-26'dan beri VARDIR: props.isHidden'ı doğrudan bildirime yazabilirsin. Bu örnekteki box_message sarmalayıcısı o dönemin kalıntısıdır (crm koşumu B-10) ve tek bir bildirim için ARTIK GEREKMEZ — burada duruyor çünkü şeridi bir kaba sarıp birden çok parçayı birlikte gösterip gizliyor. appearance'ta ise isHidden alanı YOKTUR (2026-09-15'te kaldırıldı) — görünürlük yalnız props.isHidden'dır.",
17
17
  "validationShape": "Validation gövdesi DÖRT alan taşır: { type, message, value, isActive }. 'name' anahtarı JsonIgnore'dur — {name, value, message} yazarsan tip enum varsayılanına düşer ve alan sessizce REGEX doğrulamasına döner. Tip adları TİRELİDİR (max-length / min-length / max-value / min-value); 'maxLength' backend'e yazılır ama runtime doğrulayıcı onu tanımaz ve doğrulama hiç koşmaz. Gateway düzeltebildiğini düzeltir, kalanını PAGE-016 ile reddeder."
18
18
  },
19
19
  "pageCreateArgs": {
@@ -117,7 +117,7 @@ Her şema düğümünde iki sözlük vardır; ikisi de ortak render katmanında
117
117
  | `widthType` | `cover` · `contain` · `auto` · `fixed` · `fill` | İlk ikisi görsel/medya sığdırma. Kalanı **flex (ve eski cssgrid) kabın çocuğunun genişlik modu** (2026-09-01): `auto` içerik kadar, `fixed` `widthString`'den, `fill` kabın tüm genişliği (satırda `flex:1 1 100%`, sütunda `align-self:stretch`). **Yazılmazsa eski 12 kolon `blocks.size.w` payı geçerlidir** — öncelik: `fill` > `fixed` > `auto` > span |
118
118
  | `widthString` / `heightString` | CSS uzunluğu | Sabit ölçü; **yalnız ilgili `*Type: 'fixed'` iken okunur** (aksi PAGE-017 uyarısı). Kabul: çı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 sessizce düşürür (PAGE-018). Eski `widthPx` alanı 2026-09-01'de kaldırıldı |
119
119
  | `marginType` / `marginValue` | `normal` · `none` · `custom` + px | Dış boşluk: **yazılmazsa `normal` sayılır** = her kenara 4px (`spacing.ts` `CANVAS_NORMAL_MARGIN`); custom=`marginValue`. Bileşenleri dip dibe dizmek için `none` ŞART — `gap:'none'` olsa bile normal margin komşular arasında 8px bırakır (sıkı dizilim tarifi: `platform_guide('page')` Kural 1f-2) |
120
- | `paddingType` / `paddingValue` | `normal` · `none` · `custom` + px | Droppable kapların iç boşluğu: normal=10px |
120
+ | `paddingType` / `paddingValue` | `normal` · `none` · `custom` + px | İç boşluk: normal=10px. **Yalnız droppable kaplarda ve tipli stil modelindeki yapraklarda (box · text · container · statcard · icon · form) çizilir**; `slot`, kabuk kökleri (`layout`/`layout-shell`) ve `modal`/`drawer`/`auth-layout` hiç almaz — platform 200 döner, ekran değişmez (PAGE-029). Değerler piksel SAYISIDIR: `24`, `"24px"` değil (PAGE-030) |
121
121
  | `alignType` | `left` · `center` · `right` | İçerik hizalama (destekleyen komponentlerde) |
122
122
  | `aspectRatio` | sayı | En-boy oranı kilidi (medya komponentleri) |
123
123
 
@@ -103,6 +103,7 @@ dalından devam eder.
103
103
  | `event` | `{name, data, source: {id, props}}` |
104
104
  | `cookie` | `cookie.get(name)` / `cookie.set(name, value, {expiresInHours, path})` / `cookie.remove(name)` — tarayıcı cookie'si |
105
105
  | `setComponentProperty(name, key, value)` / `getComponentProperty(name, key)` | fonksiyon alternatifi |
106
+ | `resetForm(name)` | Formu sıfırlar: alanlar başlangıç değerine döner, form "gönderilmemiş" olur, doğrulama mesajları silinir. Kayıt sonrası "yeni kayıt" için alanları tek tek `null`'a çekme — gönderilmiş formda her biri "zorunlu alan" mesajı çıkarır. Ad ya da komponent değişkeni alır: `resetForm('risk_form')` / `resetForm(risk_form)`. **Yalnız UI flow kodunda** |
106
107
  | `LocalStorage` · `Widget` · `params` | yerel veri · widget bağlamı · olay parametreleri |
107
108
  | `t(key)` | Çeviri anahtarını çözer: aktif dil → projenin varsayılan dili → anahtarın kendisi. Boş string de "eksik" sayılır. Sözlük `machine.context.state.translations`'tan gelir; dil `language-selector` ile değişince bir sonraki çağrı yeni dili görür. **Yalnız UI flow kodunda vardır** — backend ifadelerinde karşılığı `Loc()`'tur (`platform_guide('workflow')`) | `t('home.welcome')` |
108
109
  | `log(...)` | console (proxy'leri temizler) |
@@ -130,6 +131,11 @@ prop `type: 'code'` olur, **ifade `ref` alanına `{{ }}` içinde** yazılır, `d
130
131
  değerini taşır:
131
132
  `"text": { "type": "code", "data": "Button text", "ref": "{{ state.page.MyTest.isActive ? 'Test' : 'FalseTest' }}" }`
132
133
 
134
+ > **Prop'ta parantezsiz ifade METİNDİR (PAGE-032).** Akış adımındaki `value.type: 'code'`
135
+ > ifadeyi doğrudan koşturur; prop'taki `code` ise bir şablondur ve yalnız `{{ }}` içini
136
+ > çalıştırır. `"ref": "new Date().getFullYear()"` prop'a harfi harfine o metni yazar, hata
137
+ > üretmez — `page_schema_patch` bu yüzden parantezsiz prop ifadesini reddeder.
138
+
133
139
  ## 3. Adım argümanlarında `value.type` çözümlemesi
134
140
 
135
141
  `microservice`/`setter` adımlarının `parameters[]`/`iteratees[]` değerlerinde:
@@ -141,6 +141,8 @@ koda baktığını gizlemek yerine raporlamak.
141
141
  - microservice → `microservice_execute`, beklenmedik sonuçta `microservice_debug`
142
142
  - workflow → `workflow_preview`; automation → `automation_trigger` + `automation_histories`
143
143
  - "API 200 döndü" tek başına bitti saymaz; politika bulgularını (`policyFindings`) oku ve gereğini yap
144
+ - Tool'un şemasında olmayan üst düzey argüman **reddedilir** ve hata okunan alanları listeler
145
+ (GW-005). Bir argüman kabul edildiyse gateway onu okuyor demektir.
144
146
 
145
147
 
146
148
  ## Yazdıktan sonra `silentlyDropped` alanına bak
@@ -92,6 +92,12 @@ container isWrapper:true heightType:'fill' layout:'flex' direction:'row' gap:
92
92
  uçludur (`y / auto`), yani sayfa uzadıkça slot uzar.
93
93
  - **Kaydırma:** `fill` + `states.isScrollable:true` verilen düğüm büyümek yerine İÇİNDE
94
94
  kaydırır — uzun içerikte kabuğu sabit tutmanın yolu budur.
95
+ - **Slot'un kenar boşluğu margin'dir, padding değil.** Slot droppable değildir; ona yazılan
96
+ `paddingType`/`paddingValue` hiçbir kutuya çizilmez (LAY-010). Her sayfanın kenardan uzak
97
+ durmasını tek yerden istiyorsan slot'a `appearance.marginType: 'custom'` + `marginValue: 24`
98
+ yaz — margin slot'un sarmalayıcısına iç boşluk olarak iner ve slot'un zemini olmadığı için
99
+ sonuç padding'le aynıdır. Studio iskeleti slot'a zaten 8px custom margin verir. Değer SAYIDIR
100
+ (`24`, `"24px"` değil — LAY-011). Her sayfaya sarmalayıcı container eklemek gerekmez.
95
101
  - **MOBİL UYARISI:** bu ağaç `flex` olduğu için telefonda YIĞILMAZ (Kural 7) — 375px'te yan
96
102
  menü ince bir şerite iner. Telefonda alt alta düşmesi gereken bir kabuk için kökü
97
103
  `layout:'grid'` kur; o zaman `fill`'in flex anlamı (`flex:1 1 auto`) devreye girmez,