@appaflytech/zeyrek-ai-mcp 0.0.37 → 0.0.41

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 (53) hide show
  1. package/dist/knowledge/componentCatalog.js +457 -156
  2. package/dist/knowledge/componentEvents.js +49 -8
  3. package/dist/knowledge/connectDataBind.js +10 -2
  4. package/dist/knowledge/expressionPrelude.js +1 -0
  5. package/dist/knowledge/mobileComponentCatalog.js +21 -9
  6. package/dist/knowledge/mobileComponentEvents.js +21 -1
  7. package/dist/knowledge/mobileStore.js +75 -0
  8. package/dist/knowledge/normalize.js +52 -17
  9. package/dist/knowledge/platform/mobile.js +1 -1
  10. package/dist/knowledge/platform/mobileRules.js +1 -1
  11. package/dist/knowledge/platform/web.js +1 -1
  12. package/dist/knowledge/rules.js +244 -25
  13. package/dist/knowledge/styleResolve.js +27 -8
  14. package/dist/knowledge/styleResolveNative.js +4 -2
  15. package/dist/knowledge/themeResolve.js +1 -1
  16. package/dist/knowledge/themeSchema.js +2 -1
  17. package/dist/observability/trace.js +7 -2
  18. package/dist/platform/capture/actions.js +24 -1
  19. package/dist/platform/capture/summary.js +4 -1
  20. package/dist/platform/secrets.js +50 -2
  21. package/dist/resources/examples/mobile-app-shell-drawer.json +1 -0
  22. package/dist/resources/examples/mobile-list-detail-form.json +3 -0
  23. package/dist/resources/guides/component.md +1 -1
  24. package/dist/resources/guides/entity.md +16 -0
  25. package/dist/resources/guides/expressions.md +7 -6
  26. package/dist/resources/guides/merge.md +5 -4
  27. package/dist/resources/guides/microservice.md +2 -2
  28. package/dist/resources/guides/middleware.md +3 -1
  29. package/dist/resources/guides/mobile-publish.md +24 -0
  30. package/dist/resources/guides/mobile.md +50 -25
  31. package/dist/resources/guides/page.md +61 -23
  32. package/dist/resources/guides/theme.md +1 -0
  33. package/dist/resources/guides/translation.md +41 -1
  34. package/dist/resources/guides/workflow.md +31 -1
  35. package/dist/resources/schema-fingerprint.json +3 -3
  36. package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +2 -1
  37. package/dist/resources/schemas/OrganizationProjectDto.json +8 -0
  38. package/dist/resources/schemas/UpsertMobileStoreSettingsCommand.json +59 -0
  39. package/dist/tools/applications.js +6 -3
  40. package/dist/tools/common.js +9 -5
  41. package/dist/tools/entities.js +41 -4
  42. package/dist/tools/environments.js +1 -1
  43. package/dist/tools/flows.js +5 -1
  44. package/dist/tools/layouts.js +22 -7
  45. package/dist/tools/mobile.js +15 -1
  46. package/dist/tools/mobileStore.js +21 -5
  47. package/dist/tools/pages.js +31 -45
  48. package/dist/tools/queries.js +1 -1
  49. package/dist/tools/rows.js +14 -3
  50. package/dist/tools/screenWrite.js +2 -2
  51. package/dist/tools/screens.js +39 -0
  52. package/dist/tools/translations.js +148 -6
  53. package/package.json +1 -1
@@ -5,6 +5,7 @@ import { headersAccessNames, isHeadersAccessGuarded } from './flowGuards.js';
5
5
  import { callsItself, functionCallRefs, isValidParameterName, mentionsAwait, nameProblem, sourceOf, callPrefix } from './functionGuards.js';
6
6
  import { backendPolicyRules } from './backendConstraints.js';
7
7
  import { accessPolicyRules } from './access.js';
8
+ import { isGuid } from '../platform/refs.js';
8
9
  import { themePolicyRules } from './themeRules.js';
9
10
  import { processPolicyRules } from './process.js';
10
11
  import { mobileStorePolicyRules } from './mobileStore.js';
@@ -153,11 +154,13 @@ export function readsBeforeGuard(source, root) {
153
154
  * yaşatılır (microservice.ts:174 `isHasSuccess`). 'error' dalı 2026-09-26'dan beri motorun
154
155
  * KENDİSİ tanır (ADR 0004, ZM-07/g-02): bağlıysa hata anında `always` hiç koşmaz, sürer
155
156
  * (microservice.ts:182 `hasBranch`) — eski motor tanımadığı dalı hiç aramadığı için bu ekleme
156
- * geriye dönük zararsızdır.
157
+ * geriye dönük zararsızdır. `validation` tüm kurallar geçince 'success', biri düşünce 'error' dalına,
158
+ * ardından her durumda 'always'e gider (validation.ts; kök ADR 0014, g-18).
157
159
  */
158
160
  const flowBranchNames = {
159
161
  start: ['always'],
160
162
  microservice: ['always', 'success', 'error'],
163
+ validation: ['always', 'success', 'error'],
161
164
  confirm: ['yes', 'no', 'always'],
162
165
  function: ['always'],
163
166
  haptic: ['always'],
@@ -168,6 +171,8 @@ const flowBranchNames = {
168
171
  bpm: ['always', 'success', 'fail'],
169
172
  permission: ['always'],
170
173
  'open-settings': ['always'],
174
+ 'ad-show': ['always', 'error'],
175
+ back: ['always'],
171
176
  end: []
172
177
  };
173
178
  /**
@@ -180,7 +185,7 @@ export const knownFlowStepTypes = new Set([
180
185
  'start', 'end', 'query', 'setter', 'download', 'condition', 'redirect', 'setcookie', 'alert', 'confirm',
181
186
  'microservice', 'validation', 'flow-trigger', 'storage', 'navigate', 'code', 'function', 'share',
182
187
  'haptic', 'location', 'notification-permission', 'notification-schedule', 'notification-cancel', 'bpm',
183
- 'permission', 'open-settings'
188
+ 'permission', 'open-settings', 'ad-show', 'back'
184
189
  ]);
185
190
  /**
186
191
  * Cihaz yeteneği adımlarının kapalı kümeleri — `@ng-apps/flow` `tasks/device/device.functions.ts`
@@ -189,8 +194,19 @@ export const knownFlowStepTypes = new Set([
189
194
  */
190
195
  export const hapticStyles = new Set(['success', 'warning', 'error', 'light', 'medium', 'heavy', 'selection']);
191
196
  export const locationAccuracies = new Set(['low', 'balanced', 'high']);
197
+ /**
198
+ * Reklam yerleşiminin adı — backend `MobileStoreSettingsValidator.AdUnitNamePattern`'ın aynası (kök ADR 0012); `ad-show.value`
199
+ * ve `adBanner.unit` bu adla mağaza ayarındaki yerleşimi anar.
200
+ */
201
+ export const adPlacementNamePattern = /^[a-z][A-Za-z0-9]{0,39}$/;
192
202
  /** `permission` adımının kipleri — `@ng-apps/flow` `PERMISSION_MODES`'un aynası (kök ADR 0010); tür adını gateway bilmez, backend doğrular. */
193
203
  export const permissionStepModes = new Set(['request', 'check']);
204
+ /**
205
+ * `validation` adımının kural tipleri — `@ng-apps/flow` `VALIDATION_RULE_TYPES`'ın aynası (`test/flow-step-parity.test.ts`
206
+ * bağlar). Backend kümeyi doğrulamaz (kök ADR 0014); tanınmayan tip runtime'da `validate()` default dalında GEÇER sayılır.
207
+ */
208
+ export const validationRuleTypes = new Set(['required', 'email', 'regex', 'min-length', 'max-length', 'min-value', 'max-value']);
209
+ const validationParameterlessTypes = new Set(['required', 'email']);
194
210
  const flowFreeBranchTypes = new Set(['condition']);
195
211
  /** `condition.types.ts` ConditionArgumentRuleType — motorun tanıdığı üçlü. */
196
212
  const conditionRuleTypes = new Set(['if', 'else-if', 'else']);
@@ -198,16 +214,22 @@ const conditionRuleTypes = new Set(['if', 'else-if', 'else']);
198
214
  // backend'de bir Guid'dir (StepManagementSchema.cs `public Guid Id { get; set; }`), yani
199
215
  // okunabilir bir ad iki uçta birden tutmaz: istek System.Guid çevrimiyle reddedilir.
200
216
  const conditionRuleIdPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
201
- // alert.ts ve confirm.ts `task.argument.message`'ı doğrudan toast/dialog'a geçirir; argüman
202
- // Runtime.svelte'de HAM verilir (`argument: step.args`), yani bu alanlarda ifade değerlendirilmez.
203
- const flowLiteralTextFields = new Map([
217
+ // Metin şablonu alanları (kök ADR 0003 §Metin şablonu, K4 2026-10-08): iki barındırıcı (web alert.ts/confirm.ts,
218
+ // cihaz runtime-tasks.ts + device.functions.ts) metni @ng-apps/flow `resolveStepText` → `runPropertyCode`'dan
219
+ // geçirir — yalnız `{{ }}` içi çözülür, dışındaki çıplak ifade ekranda harfi harfine görünür.
220
+ const flowTemplateTextFields = new Map([
204
221
  ['alert', ['message', 'description']],
205
222
  ['confirm', ['message', 'title', 'confirmText', 'cancelText']],
206
- ['share', ['title']],
207
- ['notification-schedule', ['name', 'title', 'path']],
223
+ ['notification-schedule', ['name', 'title']],
208
224
  ['notification-cancel', ['name']]
209
225
  ]);
226
+ // Düz metin alanları: ne ifade ne şablon çözülür (`share.title` resolveShareContent'te, bildirim `path`'i K4 kapsamı dışında).
227
+ const flowLiteralTextFields = new Map([
228
+ ['share', ['title']],
229
+ ['notification-schedule', ['path']]
230
+ ]);
210
231
  const flowExpressionLooking = /\$\{|\b(?:Steps|Page|Row|Widget|LocalStorage|state|self|item|event|error|cookie)\s*(?:\?\.|\.|\[)/;
232
+ const flowTemplateSegments = /\{\{[\s\S]*?\}\}/g;
211
233
  // Barındırıcıya özel tipler BURADA kural üretmez. `navigate`i gateway normalizasyonu zaten
212
234
  // `redirect`e çeviriyor (normalize.ts `normalizeFlowHostGaps`) — `redirect` iki barındırıcıda da
213
235
  // koşar, yani vaka uyarılmaz, ORTADAN KALKAR. Gateway uygulamanın tipini platform profilinden bilir
@@ -1837,7 +1859,7 @@ export const policyRules = [
1837
1859
  id: 'ENT-006',
1838
1860
  module: 'entity',
1839
1861
  appliesTo: ['create', 'update'],
1840
- sourceRef: 'sms oturumu: kullanıcının intprimarykey/guidprimarykey referans tabloları — PK identity + properties.generationType:0 (Always) ile otomatik üretilir; dbType↔commonType eşleşmesi zorunlu',
1862
+ sourceRef: 'sms oturumu: kullanıcının intprimarykey/guidprimarykey referans tabloları — PK identity + properties.generationType:0 (Always) ile otomatik üretilir; dbType↔commonType eşleşmesi zorunlu. 38 S-1: PostgreSqlEntityConnector.ValidateEntityAsync identity alanında GenerationType 0/1 değilse InvalidGenerationTypeException (400) atar — create ve update',
1841
1863
  check: definition => {
1842
1864
  const primaryKeys = asArray(definition.fields)
1843
1865
  .filter((field) => Boolean(field) && typeof field === 'object')
@@ -1847,7 +1869,15 @@ export const policyRules = [
1847
1869
  const properties = (key.properties ?? {});
1848
1870
  const dbType = String(key.dbType ?? '');
1849
1871
  const expected = dbType === 'UUID' ? 'guid' : ['INT2', 'INT4', 'INT8'].includes(dbType) ? 'number' : null;
1850
- if (key.identity !== true || properties.generationType === undefined) {
1872
+ if (key.identity === true && properties.generationType !== 0 && properties.generationType !== 1) {
1873
+ findings.push({
1874
+ ruleId: 'ENT-006',
1875
+ severity: 'block',
1876
+ message: `'${key.slug ?? key.name}' primary key'i identity ama properties.generationType ${properties.generationType === undefined ? 'yok' : `'${String(properties.generationType)}'`} — platform 400 "Please select a valid generation type for '${key.slug ?? key.name}'" döner. Alana properties: { generationType: 0 } (Always) ya da 1 (ByDefault) ekleyin.`,
1877
+ guide: 'ngapps://guides/entity'
1878
+ });
1879
+ }
1880
+ else if (key.identity !== true) {
1851
1881
  findings.push({
1852
1882
  ruleId: 'ENT-006',
1853
1883
  severity: 'warn',
@@ -2501,7 +2531,7 @@ export const policyRules = [
2501
2531
  : [{
2502
2532
  ruleId: 'FLOW-021',
2503
2533
  severity: 'warn',
2504
- message: "errorSteps yalnız 'start' adımı taşıyor — motor bunu hata yolu SAYMAZ (flow-engine.ts hasErrorFlow: start dışında en az bir adım arar). Hata yolu kurulmuş görünür ama uç 4xx dönünce onError false döner, akış 'always' dalından hata gövdesiyle devam eder ve kullanıcıya hiçbir mesaj ulaşmaz. start'ın ardına en az bir adım bağlayın: ucun kendi mesajını göstereceksen setter (value.type:'code' → Steps.error.message), sabit bir uyarı yetiyorsa alert — alert'in 'message' alanı DÜZ METİNDİR (alert.ts task.argument.message'ı doğrudan toast'a geçirir), oraya ifade yazarsanız kullanıcı ifadenin kendisini görür.",
2534
+ message: "errorSteps yalnız 'start' adımı taşıyor — motor bunu hata yolu SAYMAZ (flow-engine.ts hasErrorFlow: start dışında en az bir adım arar). Hata yolu kurulmuş görünür ama uç 4xx dönünce onError false döner, akış 'always' dalından hata gövdesiyle devam eder ve kullanıcıya hiçbir mesaj ulaşmaz. start'ın ardına en az bir adım bağlayın: alert yeter — 'message' alanı metin şablonudur (FLOW-022), ucun kendi mesajı '{{ Steps.error.message }}' ile gösterilir; {{ }} dışında yazılan ifadeyi kullanıcı harfi harfine görür. Mesajı bir bileşene yazacaksan setter (value.type:'code' → Steps.error.message).",
2505
2535
  guide: 'ngapps://guides/page'
2506
2536
  }];
2507
2537
  }
@@ -2510,26 +2540,32 @@ export const policyRules = [
2510
2540
  id: 'FLOW-022',
2511
2541
  module: 'flow',
2512
2542
  appliesTo: ['create', 'update'],
2513
- 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). share adımında (2026-09-25) yalnız `title` düz metindir: @ng-apps/flow resolveShareContent onu olduğu gibi verir, `message` ve `path` ifadedir",
2543
+ sourceRef: "Metin şablonu (kök docs/adr/0003-cihaz-yetenegi-adimlari.md §Metin şablonu, plans/mobil-acik-isler K4, 2026-10-08): alert.ts/confirm.ts (web) ve ui-native runtime-tasks.ts + @ng-apps/flow device.functions.ts (cihaz, önizleme) `alert.message/description`, `confirm.message/title/confirmText/cancelText`, `notification-schedule.name/title`, `notification-cancel.name`'i @ng-apps/flow `resolveStepText` → `runPropertyCode`'dan geçirir: yalnız `{{ }}` içi çözülür (çeviri `t('anahtar')`, `state`, `Steps`, `item`), dışındaki çıplak ifade ekranda harfi harfine görünür (2026-09-06·2 koşusu S-13'ün ikinci turu; 38 S-12, 37 S-11). Düz metin kalanlar: share `title` (resolveShareContent olduğu gibi verir; `message` ve `path` ifadedir) ve notification-schedule `path` (K4 kapsamı dışı) — orada `{{ }}` de çözülmez",
2514
2544
  check: definition => flowAllSteps(definition)
2515
2545
  .flatMap((step) => {
2516
2546
  const type = String(step.type ?? '');
2517
- const fields = flowLiteralTextFields.get(type);
2518
- if (!fields)
2519
- return [];
2520
2547
  const args = (step.args ?? {});
2521
- return fields
2522
- .filter(field => typeof args[field] === 'string' && flowExpressionLooking.test(args[field]))
2548
+ const label = flowStepLabel(step);
2549
+ const text = (field) => (typeof args[field] === 'string' ? args[field] : '');
2550
+ const templateFindings = (flowTemplateTextFields.get(type) ?? [])
2551
+ .filter(field => flowExpressionLooking.test(text(field).replace(flowTemplateSegments, '')))
2552
+ .map(field => ({
2553
+ ruleId: 'FLOW-022',
2554
+ severity: 'warn',
2555
+ message: `'${label}' ${type} adımının '${field}' alanı {{ }} dışında ifade içeriyor: bu alan METİN ŞABLONUDUR (kök ADR 0003 §Metin şablonu) — yalnız {{ }} içi çözülür, dışındaki ifade kullanıcıya harfi harfine görünür. İfadeyi sarın: '{{ Steps.error.message }}', 'ilac-{{ item.id }}', '{{ t('anahtar') }}' (çeviri).`,
2556
+ guide: 'ngapps://guides/page'
2557
+ }));
2558
+ const literalFindings = (flowLiteralTextFields.get(type) ?? [])
2559
+ .filter(field => flowExpressionLooking.test(text(field)) || text(field).includes('{{'))
2523
2560
  .map(field => ({
2524
2561
  ruleId: 'FLOW-022',
2525
2562
  severity: 'warn',
2526
- message: type.startsWith('notification-')
2527
- ? `'${flowStepLabel(step)}' ${type} adımının '${field}' alanı ifade içeriyor ama bu alan DÜZ METİNDİR (kök ADR 0003): ${field === 'name' ? 'bildirimin kimliği' : field === 'path' ? 'açılacak sayfanın slug\'ı' : 'bildirim başlığı'} olduğu gibi kullanılır, ifade değerlendirilmez. Değişen içeriği ifade olan 'message' (gövde) ya da 'query' (sayfanın sorgusu) alanına yazın.`
2528
- : type === 'share'
2529
- ? `'${flowStepLabel(step)}' share adımının '${field}' alanı ifade içeriyor ama bu alan DÜZ METİNDİR: başlık paylaşım penceresine olduğu gibi gider (iOS'ta e-posta konusu, Android'de seçici başlığı), ifade değerlendirilmez. Değişen içeriği ifade olan 'message' alanına yazın — ör. message: "'Şuna bak: ' + Page.properties.name"; bağlantı 'path' alanındadır.`
2530
- : `'${flowStepLabel(step)}' ${type} adımının '${field}' alanı ifade içeriyor ama bu alan DÜZ METİNDİR: ${type}.ts task.argument.${field}'i doğrudan ekrana geçirir ve args hiçbir yerde değerlendirilmez (Runtime.svelte \`argument: step.args\`). Kullanıcı ifadenin sonucunu değil ifadenin KENDİSİNİ görür. Ucun mesajını göstermek için bir setter (ya da code) adımı kullanın — value { type: 'code', ref: 'Steps.error?.message' }; hata metninin nerede durduğu page.md §hata yolu'nda.`,
2563
+ message: type === 'share'
2564
+ ? `'${label}' share adımının '${field}' alanı ifade ya da {{ }} içeriyor ama bu alan DÜZ METİNDİR: başlık paylaşım penceresine olduğu gibi gider (iOS'ta e-posta konusu, Android'de seçici başlığı), ne ifade ne şablon çözülür. Değişen içeriği ifade olan 'message' alanına yazın — ör. message: "'Şuna bak: ' + Page.properties.name"; bağlantı 'path' alanındadır.`
2565
+ : `'${label}' ${type} adımının '${field}' alanı ifade ya da {{ }} içeriyor ama bu alan DÜZ METİNDİR (kök ADR 0003): açılacak sayfanın slug'ı olduğu gibi kullanılır, ne ifade ne şablon çözülür. Değişen kısmı ifade olan 'query' (sayfanın sorgusu) alanına yazın.`,
2531
2566
  guide: 'ngapps://guides/page'
2532
2567
  }));
2568
+ return [...templateFindings, ...literalFindings];
2533
2569
  })
2534
2570
  },
2535
2571
  {
@@ -2626,6 +2662,123 @@ export const policyRules = [
2626
2662
  return findings;
2627
2663
  })
2628
2664
  },
2665
+ {
2666
+ id: 'FLOW-039',
2667
+ module: 'flow',
2668
+ appliesTo: ['create', 'update'],
2669
+ sourceRef: "Tam ekran reklam adımının argümanı StepManagementArgument.value'dur (kök docs/adr/0012-reklam-yerlesimleri-magaza-ayari-ve-kosullu-sdk.md): mağaza ayarındaki reklam yerleşiminin ADI, DÜZ METİN — AdMob birim kimliği değil (kimlik derlemenin platformuna göre manifestten çözülür). Backend FlowDtoValidator HasAdPlacement adı ^[a-z][A-Za-z0-9]{0,39}$ deseniyle denetler, uymayan 422 alır; unit/placement/adUnitId gibi anahtarlar sözleşmede YOKTUR ve 200 ile sessizce düşer. Yerleşimin mağaza ayarında tanımlı olduğu burada denetlenemez: mobile_store_get",
2670
+ check: definition => flowAllSteps(definition)
2671
+ .filter(step => step.type === 'ad-show')
2672
+ .flatMap((step) => {
2673
+ const args = (step.args ?? {});
2674
+ const unit = typeof args.value === 'string' ? args.value : '';
2675
+ const unquoted = unit.replace(/^['"`](.*)['"`]$/, '$1');
2676
+ const stray = ['unit', 'placement', 'adUnitId', 'unitId', 'format'].filter(key => args[key] !== undefined && args[key] !== null && args[key] !== '');
2677
+ const findings = [];
2678
+ if (!unit.trim()) {
2679
+ findings.push({
2680
+ ruleId: 'FLOW-039',
2681
+ severity: 'block',
2682
+ message: `'${flowStepLabel(step)}' ad-show adımının yerleşimi yok: args.value mağaza ayarındaki tam ekran yerleşimin ADINI ister (platform 422 döner). Yerleşimler: mobile_store_get — ads.units[] (format interstitial, rewarded ya da rewardedInterstitial).`,
2683
+ guide: 'ngapps://guides/mobile'
2684
+ });
2685
+ }
2686
+ else if (unquoted !== unit) {
2687
+ findings.push({
2688
+ ruleId: 'FLOW-039',
2689
+ severity: 'block',
2690
+ message: `'${flowStepLabel(step)}' ad-show adımının yerleşimi tırnaklı yazılmış: ${unit}. Bu alan DÜZ METİNDİR, ifade değil — args.value: '${unquoted}' yazın.`,
2691
+ guide: 'ngapps://guides/mobile'
2692
+ });
2693
+ }
2694
+ else if (!adPlacementNamePattern.test(unit)) {
2695
+ findings.push({
2696
+ ruleId: 'FLOW-039',
2697
+ severity: 'block',
2698
+ message: `'${flowStepLabel(step)}' ad-show adımının yerleşimi bir yerleşim adı değil: '${unit}'. Değer ifade ya da AdMob birim kimliği olamaz; mağaza ayarındaki yerleşimin adıdır (${adPlacementNamePattern.source}). Platform 422 döner. Yerleşimler: mobile_store_get — ads.units[].name.`,
2699
+ guide: 'ngapps://guides/mobile'
2700
+ });
2701
+ }
2702
+ if (stray.length > 0) {
2703
+ findings.push({
2704
+ ruleId: 'FLOW-039',
2705
+ severity: unit.trim() ? 'warn' : 'block',
2706
+ message: `'${flowStepLabel(step)}' ad-show adımında args.${stray.join('/args.')} alanı var: bu anahtar StepManagementArgument sözleşmesinde YOK, platform onu 200 ile sessizce düşürür. Yerleşimin adı args.value'ya düz metin olarak yazılır — ör. args: { value: '<yerleşim adı>' }.`,
2707
+ guide: 'ngapps://guides/mobile'
2708
+ });
2709
+ }
2710
+ return findings;
2711
+ })
2712
+ },
2713
+ {
2714
+ id: 'FLOW-041',
2715
+ module: 'flow',
2716
+ appliesTo: ['create', 'update'],
2717
+ sourceRef: "validation adımının kuralları StepManagementArgument.validations listesindedir (kök docs/adr/0014-validation-kurallari-cocuk-tabloda.md §5, 37 S-13): { id?, component (tasarım bileşeninin elementId'si; boş = null, sayfada olmayan 404), property, type, errorMessage (düz metin ya da t: anahtarı), settings (tek parametre, runtime Object.values(settings)[0]'ı okur) }. Backend type kümesini doğrulamaz; @ng-apps/flow validation.ts validate() tanımadığı tipte true döner, yani yazım hatalı kural sessizce GEÇER. Liste boşsa her çalıştırma success dalına gider. Komponent doğrulamasının { message, value } şekli bu sözleşmede YOKTUR ve 200 ile sessizce düşer",
2718
+ check: definition => flowAllSteps(definition)
2719
+ .filter(step => step.type === 'validation')
2720
+ .flatMap((step) => {
2721
+ const label = flowStepLabel(step);
2722
+ const validations = (step.args ?? {}).validations;
2723
+ if (validations !== undefined && validations !== null && !Array.isArray(validations)) {
2724
+ return [{
2725
+ ruleId: 'FLOW-041',
2726
+ severity: 'block',
2727
+ message: `'${label}' validation adımının args.validations alanı bir DİZİ olmalı: [{ component, property, type, errorMessage, settings }] (kök ADR 0014). Ör. [{ component: 'email', property: 'value', type: 'email', errorMessage: 't:validation.email', settings: {} }].`,
2728
+ guide: 'ngapps://guides/page'
2729
+ }];
2730
+ }
2731
+ const rules = (Array.isArray(validations) ? validations : []);
2732
+ if (rules.length === 0) {
2733
+ return [{
2734
+ ruleId: 'FLOW-041',
2735
+ severity: 'warn',
2736
+ message: `'${label}' validation adımında kural yok: args.validations boş olduğu için adım her çalıştırmada 'success' dalına gider, hiçbir alan denetlenmez. Kuralları args.validations'a yazın (kök ADR 0014).`,
2737
+ guide: 'ngapps://guides/page'
2738
+ }];
2739
+ }
2740
+ return rules.flatMap((rule, index) => {
2741
+ const type = typeof rule?.type === 'string' ? rule.type : '';
2742
+ const settings = rule?.settings && typeof rule.settings === 'object' ? Object.values(rule.settings) : [];
2743
+ const stray = ['message', 'value', 'name', 'isActive'].filter(key => rule?.[key] !== undefined);
2744
+ const at = `args.validations[${index}]`;
2745
+ const findings = [];
2746
+ if (!validationRuleTypes.has(type)) {
2747
+ findings.push({
2748
+ ruleId: 'FLOW-041',
2749
+ severity: 'block',
2750
+ message: `'${label}' validation adımında ${at}.type '${type}' tanınmıyor — beklenen: '${[...validationRuleTypes].join("' | '")}'. Platform tipi doğrulamadan kaydeder ve runtime tanımadığı tipi GEÇTİ sayar: kural hiçbir şeyi denetlemez.`,
2751
+ guide: 'ngapps://guides/page'
2752
+ });
2753
+ }
2754
+ else if (!validationParameterlessTypes.has(type) && (settings.length === 0 || settings[0] === null || settings[0] === '')) {
2755
+ findings.push({
2756
+ ruleId: 'FLOW-041',
2757
+ severity: 'block',
2758
+ message: `'${label}' validation adımında ${at} ('${type}') parametresiz: runtime eşiği settings'in İLK değerinden okur (${type === 'regex' ? "{ pattern: '^\\\\d+$' }" : '{ value: 8 }'}); değer yoksa karşılaştırma undefined ile yapılır ve kural her çalıştırmada düşer.`,
2759
+ guide: 'ngapps://guides/page'
2760
+ });
2761
+ }
2762
+ if (typeof rule?.property !== 'string' || rule.property.trim() === '') {
2763
+ findings.push({
2764
+ ruleId: 'FLOW-041',
2765
+ severity: 'block',
2766
+ message: `'${label}' validation adımında ${at}.property yok: runtime bileşenin hangi prop'unu okuyacağını bu alandan bilir (çoğunlukla 'value'). Yoksa okunan değer undefined olur.`,
2767
+ guide: 'ngapps://guides/page'
2768
+ });
2769
+ }
2770
+ if (stray.length > 0) {
2771
+ findings.push({
2772
+ ruleId: 'FLOW-041',
2773
+ severity: 'warn',
2774
+ message: `'${label}' validation adımında ${at}.${stray.join('/')} alanı var: bu komponent doğrulamasının şeklidir, adım kuralının sözleşmesinde YOK ve platform onu 200 ile sessizce düşürür. Mesaj errorMessage'a, eşik settings'e yazılır — ör. { component: 'sifre', property: 'value', type: 'min-length', errorMessage: 'En az 8 karakter', settings: { value: 8 } }.`,
2775
+ guide: 'ngapps://guides/page'
2776
+ });
2777
+ }
2778
+ return findings;
2779
+ });
2780
+ })
2781
+ },
2629
2782
  {
2630
2783
  id: 'FLOW-038',
2631
2784
  module: 'flow',
@@ -2687,7 +2840,7 @@ export const policyRules = [
2687
2840
  id: 'FLOW-030',
2688
2841
  module: 'flow',
2689
2842
  appliesTo: ['create', 'update'],
2690
- sourceRef: "Yerel bildirim adımlarının argümanı StepManagementArgument'ın mevcut alanlarındadır (kök docs/adr/0003-cihaz-yetenegi-adimlari.md): notification-schedule name = kimlik (düz metin, aynı kimlik eskisinin yerini alır), title düz metin, message ve value İFADE, path sayfa; notification-cancel name = kimlik ('*' hepsi). Zamanın türü value'nun sonucunun şeklinden okunur (@ng-apps/flow notificationTrigger): 'HH:mm' her gün, { weekday: 1-7, time: 'HH:mm' } her hafta (1 = Pazartesi), ISO tarih-saat bir kez. Eksik name/value backend FlowDtoValidator'da 400 alır; id/at/daily/weekly/body gibi anahtarlar sözleşmede YOKTUR ve platform onları 200 ile sessizce düşürür",
2843
+ sourceRef: "Yerel bildirim adımlarının argümanı StepManagementArgument'ın mevcut alanlarındadır (kök docs/adr/0003-cihaz-yetenegi-adimlari.md): notification-schedule name = kimlik ve title = başlık METİN ŞABLONU (yalnız {{ }} içi çözülür — 2026-10-08, FLOW-022; aynı kimlik eskisinin yerini alır), message ve value İFADE, path sayfa (düz metin); notification-cancel name = kimlik, metin şablonu ('*' hepsi). Zamanın türü value'nun sonucunun şeklinden okunur (@ng-apps/flow notificationTrigger): 'HH:mm' her gün, { weekday: 1-7, time: 'HH:mm' } her hafta (1 = Pazartesi), ISO tarih-saat bir kez. Eksik name/value backend FlowDtoValidator'da 400 alır; id/at/daily/weekly/body gibi anahtarlar sözleşmede YOKTUR ve platform onları 200 ile sessizce düşürür. GUID değerli args.id platformun kendi StepArgument kimliğidir (okunan akıştan geri yazım) ve işaretlenmez — 37 S-21",
2691
2844
  check: definition => flowAllSteps(definition)
2692
2845
  .filter(step => step.type === 'notification-schedule' || step.type === 'notification-cancel')
2693
2846
  .flatMap((step) => {
@@ -2695,13 +2848,16 @@ export const policyRules = [
2695
2848
  const text = (key) => typeof args[key] === 'string' ? String(args[key]).trim() : '';
2696
2849
  const findings = [];
2697
2850
  const schedule = step.type === 'notification-schedule';
2851
+ // 37 S-21: StepArgument bir AggregateRoot<Guid>'dir; okunan akış args.id'yi GUID olarak
2852
+ // taşır ve geri yazımda o platformun kimliğidir. Yalnız GUID olmayan id ajanın uydurmasıdır.
2698
2853
  const stray = ['id', 'identifier', 'at', 'daily', 'weekly', 'time', 'body', 'text']
2699
- .filter(key => args[key] !== undefined && args[key] !== null && args[key] !== '');
2854
+ .filter(key => args[key] !== undefined && args[key] !== null && args[key] !== '')
2855
+ .filter(key => key !== 'id' || !isGuid(String(args.id)));
2700
2856
  if (!text('name')) {
2701
2857
  findings.push({
2702
2858
  ruleId: 'FLOW-030',
2703
2859
  severity: 'block',
2704
- message: `'${flowStepLabel(step)}' ${step.type} adımının kimliği yok: args.name düz metin olarak yazılmalı (ör. 'sulama')${schedule ? ' — aynı kimlikle yeniden planlama eskisinin yerini alır' : " — '*' bütün planlı bildirimleri siler"}. Platform eksik kimliği 400 ile reddeder.`,
2860
+ message: `'${flowStepLabel(step)}' ${step.type} adımının kimliği yok: args.name metin olarak yazılmalı (ör. 'sulama'; kayıt başına 'ilac-{{ item.id }}')${schedule ? ' — aynı kimlikle yeniden planlama eskisinin yerini alır' : " — '*' bütün planlı bildirimleri siler"}. Platform eksik kimliği 400 ile reddeder.`,
2705
2861
  guide: 'ngapps://guides/mobile'
2706
2862
  });
2707
2863
  }
@@ -3086,6 +3242,29 @@ export const policyRules = [
3086
3242
  }];
3087
3243
  })
3088
3244
  },
3245
+ {
3246
+ id: 'FLOW-027',
3247
+ module: 'flow',
3248
+ appliesTo: ['create', 'update'],
3249
+ sourceRef: "2026-10-01 kanban ölçüm turu: steps ve errorSteps'te AYNI stepId ('start') flow_create'te 500 `An item with the same key has already been added. Key: start [ArgumentException]` verdi — platform adımları tek sözlüğe stepId anahtarıyla koyuyor, hata mesajı çakışmanın stepId olduğunu söylemiyor",
3250
+ check: definition => {
3251
+ const counts = new Map();
3252
+ for (const step of flowAllSteps(definition)) {
3253
+ if (!step.stepId)
3254
+ continue;
3255
+ counts.set(step.stepId, (counts.get(step.stepId) ?? 0) + 1);
3256
+ }
3257
+ const repeated = [...counts].filter(([, count]) => count > 1).map(([stepId]) => stepId);
3258
+ return repeated.length === 0
3259
+ ? []
3260
+ : [{
3261
+ ruleId: 'FLOW-027',
3262
+ severity: 'block',
3263
+ message: `stepId'ler akışta (steps + errorSteps birlikte) benzersiz olmalı; tekrar edenler: ${repeated.map(id => `'${id}'`).join(', ')}. Platform bunları tek sözlüğe anahtar olarak koyar ve "An item with the same key has already been added" ile 500 verir (mesaj stepId'den söz etmez). errorSteps'te de kendi 'start' adımı gerekiyorsa ona farklı bir stepId verin (örn. 'err_start').`,
3264
+ guide: 'ngapps://guides/page'
3265
+ }];
3266
+ }
3267
+ },
3089
3268
  {
3090
3269
  id: 'MS-013',
3091
3270
  module: 'microservice',
@@ -3185,6 +3364,46 @@ export const policyRules = [
3185
3364
  return findings;
3186
3365
  }
3187
3366
  },
3367
+ /**
3368
+ * WF-035 — 38 S-10: `query.fail` gateway'in `gatewayOnError` adımına bağlanınca bozuk id'li
3369
+ * istek 500 `JavaScriptException: error is not defined` döndü. Hedef adım global-error dalının
3370
+ * da hedefi olabilir; o yoldan gelince `error` kurulur, bağlı fail yolundan gelince kurulmaz —
3371
+ * bu yüzden hedefin global-error hedefi olması uyarıyı susturmaz.
3372
+ */
3373
+ {
3374
+ id: 'WF-035',
3375
+ module: 'workflow',
3376
+ appliesTo: ['create', 'update', 'preview'],
3377
+ sourceRef: "IStep.cs FailedOutput: global-error dalına yalnız fail dalı BAĞLANMAMIŞSA geçilir ve `error` değişkenini yalnız ActivityContext.EnterGlobalErrorHandling kurar. Bağlı fail dalında sonraki adımlar patlayan adımın hatasını kendi değişkeninden okur: ActivityContext.CreateJavascriptEngine her adımı adıyla { data, meta, error: { code, message, detail, type, cause } } olarak koyar. Ölçüm 38 S-10 (2026-09-30): query.fail → gatewayOnError, bozuk id → 500 'error is not defined'",
3378
+ check: (definition, context) => {
3379
+ const steps = workflowSteps(workflowBody(definition, context.workflowKind));
3380
+ const byId = new Map(steps.filter(step => step.id).map(step => [String(step.id), step]));
3381
+ const readsGlobalError = /(?<![\w$.])error\s*(?:\?\.|\.(?=[A-Za-z_$])|\[)/;
3382
+ const strings = (value) => {
3383
+ if (typeof value === 'string')
3384
+ return [value];
3385
+ if (Array.isArray(value))
3386
+ return value.flatMap(strings);
3387
+ if (value && typeof value === 'object')
3388
+ return Object.values(value).flatMap(strings);
3389
+ return [];
3390
+ };
3391
+ return steps.flatMap((step) => asArray(step.branches)
3392
+ .map(branch => branch)
3393
+ .filter(branch => branch?.type === 'fail' && Boolean(branch.nextStepId))
3394
+ .map(branch => byId.get(String(branch.nextStepId)))
3395
+ .filter((target) => Boolean(target) && strings(target.rule).some(text => readsGlobalError.test(text)))
3396
+ .map(target => {
3397
+ const name = stepLabel(step);
3398
+ return {
3399
+ ruleId: 'WF-035',
3400
+ severity: 'warn',
3401
+ message: `'${name}' adımının fail dalı '${stepLabel(target)}' adımına bağlı ve o adım 'error.*' okuyor. 'error' YALNIZ global-error dalında (fail dalı bağlanmamışken) kurulur; bağlı fail dalından gelindiğinde tanımsızdır ve adım 500 'JavaScriptException: error is not defined' ile düşer. Patlayan adımın hatasını kendi değişkeninden okuyun: ${name}.error.message · ${name}.error.code (ayrıca detail, type, cause; çıktı ${name}.data). Ya da fail dalını bağlamayın: hata global-error dalına düşer ve 'error' orada kurulur. Global-error hedefiyle aynı adımı paylaşmak yerine fail dalına ayrı bir response adımı bağlayın (ör. 404).`,
3402
+ guide: 'ngapps://guides/workflow'
3403
+ };
3404
+ }));
3405
+ }
3406
+ },
3188
3407
  {
3189
3408
  id: 'WF-021',
3190
3409
  module: 'workflow',
@@ -3309,7 +3528,7 @@ export const policyRules = [
3309
3528
  id: 'MW-003',
3310
3529
  module: 'workflow',
3311
3530
  appliesTo: ['create', 'update', 'preview'],
3312
- sourceRef: "set-user değişkeni istek kapsamındaki WorkflowContext'e yazılır (SetUserStep → AddVariable('user')) ve sonraki middleware'in motoru onu görür (WorkflowRunner her koşuda JavascriptEngineInstance'ı _variables'tan kurar; sıra ExecuteMicroserviceBehavior:96 / ExecutePageBehavior:121 → OrderBy(m => m.Name)). Ama kuran middleware set-user'a ULAŞMADAN biterse (çerez yok, koşul dalı) değişken hiç doğmaz ve okuyan middleware ham 'user is not defined' ile 500 verir — 401 değil (crm koşumu B-3)",
3531
+ sourceRef: "set-user değişkeni istek kapsamındaki WorkflowContext'e yazılır (SetUserStep → AddVariable('user')) ve sonraki middleware'in motoru onu görür (WorkflowRunner her koşuda CreateJavascriptEngine() ile motoru _variables'tan kurar; sıra ExecuteMicroserviceBehavior:96 / ExecutePageBehavior:121 → OrderBy(m => m.Name)). Ama kuran middleware set-user'a ULAŞMADAN biterse (çerez yok, koşul dalı) değişken hiç doğmaz ve okuyan middleware ham 'user is not defined' ile 500 verir — 401 değil (crm koşumu B-3)",
3313
3532
  check: (definition, context) => {
3314
3533
  if (context.workflowKind !== 'middleware')
3315
3534
  return [];
@@ -164,6 +164,8 @@ export const TRANSFORM_RANGES = {
164
164
  translateX: [-1000, 1000],
165
165
  translateY: [-1000, 1000]
166
166
  };
167
+ /** Frontend `TRANSFORM_FIELDS`: transform'lu ata `position: fixed` torununu hapsettiği için katmanlar ve kabuklar bu alanları `surfaceStyleGroup({ omit })` ile dışlar. */
168
+ const TRANSFORM_FIELDS = Object.keys(TRANSFORM_RANGES);
167
169
  /** Sayı ya da sayısal metin, aralığın içinde; aksi `undefined` — frontend `transformValue`. */
168
170
  export const transformValue = (value, field) => {
169
171
  if (typeof value !== 'number' && (typeof value !== 'string' || value.trim() === ''))
@@ -434,7 +436,9 @@ const STATCARD_FLEX_ALIGN = {
434
436
  left: 'flex-start',
435
437
  center: 'center',
436
438
  right: 'flex-end',
437
- justify: 'stretch'
439
+ justify: 'stretch',
440
+ start: 'flex-start',
441
+ end: 'flex-end'
438
442
  };
439
443
  function composeStatCardParts(input, root) {
440
444
  const value = input.valueStyle;
@@ -507,22 +511,26 @@ export const TYPED_STYLE_COMPONENTS = {
507
511
  // onun altında kalıyordu (LAYOUT-006, 2026-09-29). Yüzey ve taşma köke.
508
512
  // navigationStyle menüye gider, resolvedStyle'a girmez.
509
513
  groups: ['surfaceStyle'],
510
- ext: { overflowTarget: ROOT_TARGET }
514
+ ext: { overflowTarget: ROOT_TARGET },
515
+ omit: { surfaceStyle: TRANSFORM_FIELDS }
511
516
  },
512
517
  'layout-shell': {
513
518
  // Aynı kök: zemin `.ng-layout-shell` KÖKÜNDE (SHELL-006, 2026-09-29).
514
519
  groups: ['surfaceStyle'],
515
- ext: { overflowTarget: ROOT_TARGET }
520
+ ext: { overflowTarget: ROOT_TARGET },
521
+ omit: { surfaceStyle: TRANSFORM_FIELDS }
516
522
  },
517
523
  'layout-region': {
518
524
  // Zemin `.ng-layout-region` KÖKÜNDE, taşma kaydırılan gövdede (REG-002, 2026-09-29).
519
525
  groups: ['surfaceStyle'],
520
- ext: { overflowTarget: '&__body' }
526
+ ext: { overflowTarget: '&__body' },
527
+ omit: { surfaceStyle: TRANSFORM_FIELDS }
521
528
  },
522
529
  'auth-layout': {
523
530
  // Zemin `.ng-auth` KÖKÜNDE (AUTH-006, 2026-09-29); yüzey ve taşma köke.
524
531
  groups: ['surfaceStyle'],
525
- ext: { overflowTarget: ROOT_TARGET }
532
+ ext: { overflowTarget: ROOT_TARGET },
533
+ omit: { surfaceStyle: TRANSFORM_FIELDS }
526
534
  },
527
535
  header: {
528
536
  // Şeridin zemini ve alt çizgisi kökte (HDR-007, 2026-09-29).
@@ -535,6 +543,20 @@ export const TYPED_STYLE_COMPONENTS = {
535
543
  groups: ['surfaceStyle'],
536
544
  ext: { overflowTarget: ROOT_TARGET }
537
545
  },
546
+ modal: {
547
+ // Panel portal ile düğüm kutusunun DIŞINA çizilir; yüzey panelin KÖKÜNDE
548
+ // derlenir, taşma da panelin köküne iner (modal ölçümü, 2026-10-05).
549
+ groups: ['surfaceStyle'],
550
+ ext: { overflowTarget: ROOT_TARGET },
551
+ omit: { surfaceStyle: TRANSFORM_FIELDS }
552
+ },
553
+ drawer: {
554
+ // Panel portal ile düğüm kutusunun DIŞINA çizilir; yüzey panelin KÖKÜNDE
555
+ // derlenir, taşma da panelin köküne iner (drawer ölçümü, 2026-10-05).
556
+ groups: ['surfaceStyle'],
557
+ ext: { overflowTarget: ROOT_TARGET },
558
+ omit: { surfaceStyle: TRANSFORM_FIELDS }
559
+ },
538
560
  icon: {
539
561
  // Yalnız hover: surface çipin (chipShape/chipColor), typography da boyutun
540
562
  // (size) ve rengin (color) çift kanalı olurdu — görsel kararlar semantik
@@ -553,11 +575,8 @@ export const TYPED_STYLE_COMPONENTS = {
553
575
  * eski birleşik kaskadla (tema+preset+own) çözülmeye devam eder.
554
576
  */
555
577
  export const NODE_SURFACE_TYPES = new Set([
556
- 'table',
557
578
  'datatable',
558
579
  'collapsible-container',
559
- 'drawer',
560
- 'modal',
561
580
  'popover',
562
581
  'tabs',
563
582
  'stepper',
@@ -456,8 +456,10 @@ const composeTypography = (typography, text, theme, context) => {
456
456
  text.fontStyle = String(typography.fontStyle);
457
457
  if (isSet(typography.textDecoration))
458
458
  text.textDecorationLine = String(typography.textDecoration);
459
- if (isSet(typography.textAlign))
460
- text.textAlign = String(typography.textAlign);
459
+ if (isSet(typography.textAlign)) {
460
+ const align = String(typography.textAlign);
461
+ text.textAlign = align === 'start' ? 'left' : align === 'end' ? 'right' : align;
462
+ }
461
463
  const letterSpacing = parsePanelLength(typography.letterSpacing, context);
462
464
  if (typeof letterSpacing === 'number')
463
465
  text.letterSpacing = letterSpacing;
@@ -29,7 +29,7 @@
29
29
  * `theme_get` bunları `unknownKeys` olarak gösterir, `theme_update` şeması reddeder.
30
30
  */
31
31
  export const THEME_TOP_LEVEL_KEYS = [
32
- 'mode', 'palette', 'paletteDark', 'fontFamily', 'headingFontFamily', 'typeScale',
32
+ 'mode', 'direction', 'palette', 'paletteDark', 'fontFamily', 'headingFontFamily', 'typeScale',
33
33
  'fontSize', 'fontWeight', 'borderRadius', 'radiusScale', 'spacing', 'spacingScale',
34
34
  'shadow', 'defaultInputSize', 'colors', 'actions', 'presets', 'componentStyles',
35
35
  'componentPresets'
@@ -30,7 +30,7 @@ export function summarizeTheme(theme) {
30
30
  if (entries.length > 0)
31
31
  overrides[key] = entries;
32
32
  }
33
- for (const key of ['fontFamily', 'headingFontFamily', 'fontSize', 'fontWeight', 'spacing', 'borderRadius', 'defaultInputSize']) {
33
+ for (const key of ['fontFamily', 'headingFontFamily', 'fontSize', 'fontWeight', 'spacing', 'borderRadius', 'defaultInputSize', 'direction']) {
34
34
  if (record[key] !== undefined)
35
35
  overrides[key] = record[key];
36
36
  }
@@ -91,6 +91,7 @@ const ratioScale = (levels) => z.object(Object.fromEntries(levels.map(level => [
91
91
  */
92
92
  export const themeInput = z.object({
93
93
  mode: z.enum(['light', 'dark']),
94
+ direction: z.enum(['ltr', 'rtl', 'auto']),
94
95
  palette: paletteSchema,
95
96
  paletteDark: paletteSchema,
96
97
  fontFamily: z.string().min(1),
@@ -1,7 +1,7 @@
1
1
  import { AsyncLocalStorage } from 'node:async_hooks';
2
2
  import { appendFileSync, existsSync, mkdirSync } from 'node:fs';
3
3
  import { dirname, join } from 'node:path';
4
- import { secretKeyPattern } from '../platform/secrets.js';
4
+ import { maskSecretLeaf, secretKeyPattern, secretPairName } from '../platform/secrets.js';
5
5
  const defaultDirectory = join(process.cwd(), 'logs');
6
6
  const activeTrace = new AsyncLocalStorage();
7
7
  const activeRequest = new AsyncLocalStorage();
@@ -37,11 +37,16 @@ function clip(text, limit) {
37
37
  function redact(value, depth = 0) {
38
38
  if (depth > 8)
39
39
  return '…';
40
+ // Günlük, yanıt maskesinin gördüğü ad/değer çiftini ve yaprak jetonu da görmeli: debug gövdesi ve
41
+ // `asUser` argümanı buraya ham gelir (g-01, 37 S-24).
42
+ if (typeof value === 'string')
43
+ return maskSecretLeaf(value);
40
44
  if (Array.isArray(value))
41
45
  return value.map(entry => redact(entry, depth + 1));
42
46
  if (value && typeof value === 'object') {
47
+ const pairName = secretPairName(value);
43
48
  return Object.fromEntries(Object.entries(value).map(([key, entry]) => {
44
- if (secretKeyPattern.test(key))
49
+ if (secretKeyPattern.test(key === 'value' && pairName !== undefined ? pairName : key))
45
50
  return [key, '***'];
46
51
  if (/^stack(Trace)?$/i.test(key) && typeof entry === 'string') {
47
52
  return [key, `${clip(entry.split(/\r?\n/)[0] ?? '', 240)} … (kalan stack kırpıldı)`];
@@ -24,7 +24,13 @@ export function actionScript(action, captureTarget = studioWebTarget) {
24
24
  var count = performance.getEntriesByType('resource').length;
25
25
  var node = document.querySelector(${selector});
26
26
  if (!node) return { found: false, applied: false, requests: count };
27
- var target = node.matches('button, a, [role="button"]') ? node : (node.querySelector('button, a, [role="button"]') || node);
27
+ // g-04 sırası: düğümün kendisi basılabilirse o → <id>__press → içteki ilk basılabilir öğe.
28
+ // Mobil çoklu seçim tetikleyicisi kökte role="combobox" taşır ve içindeki ilk role="button"
29
+ // çipin "kaldır" düğmesidir — eskiden ona basılıp seçim siliniyordu (37 S-18). Tek basışlı
30
+ // denetimin yüzeyi <id>__press'tir (ui-native press-role-guard), anahtarınki role="switch".
31
+ var press = document.querySelector(${JSON.stringify(`[data-element-id="${String(action.target ?? '')}${INTERNAL_ROLE_SEPARATOR}press"]`)});
32
+ var target = node.matches('button, a, [role="button"], [role="switch"], [role="combobox"]') ? node
33
+ : (press || node.querySelector('button, a, [role="button"], [role="switch"]') || node);
28
34
  // S-2: sekme grubunun KÖKÜNE click, DOM sırasındaki ilk sekmeye gider ve "uygulandı" der —
29
35
  // ajan ikinci sekmeyi hiç seçemiyordu (2026-09-16 raporu madde 6). Sekme seçmek 'tab'in işi.
30
36
  if (target.closest && target.closest('[role="tablist"]') && !node.matches('[role="tab"]')) {
@@ -354,15 +360,32 @@ export function actionScript(action, captureTarget = studioWebTarget) {
354
360
  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 });
355
361
  var proto = field instanceof HTMLSelectElement ? HTMLSelectElement.prototype
356
362
  : (field instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype);
363
+ // K1 (g-04): gerçek kullanıcı alana odaklanır, yazar ve çıkar. Blur'suz yazımda odak kaybına
364
+ // bağlı akış (ui-native girişlerin 'changed'i) hiç koşmuyordu (37 S-19). focus() odağı alamazsa
365
+ // (görünmez alan, odaksız belge) olaylar elle atılır: Svelte 'blur'u, React 'focusout'u dinler.
366
+ var focusField = function () {
367
+ field.focus();
368
+ if (document.activeElement === field) return;
369
+ field.dispatchEvent(new FocusEvent('focus'));
370
+ field.dispatchEvent(new FocusEvent('focusin', { bubbles: true }));
371
+ };
372
+ var blurField = function () {
373
+ if (document.activeElement === field) { field.blur(); return; }
374
+ field.dispatchEvent(new FocusEvent('blur'));
375
+ field.dispatchEvent(new FocusEvent('focusout', { bubbles: true }));
376
+ };
377
+ focusField();
357
378
  if (field instanceof HTMLInputElement && (field.type === 'checkbox' || field.type === 'radio')) {
358
379
  var shouldCheck = wanted === 'true' || wanted === '1' || wanted === 'on';
359
380
  if (field.checked !== shouldCheck) field.click();
381
+ blurField();
360
382
  return finish({ found: true, applied: true, requests: count });
361
383
  }
362
384
  var setter = Object.getOwnPropertyDescriptor(proto, 'value').set;
363
385
  setter.call(field, ${value});
364
386
  field.dispatchEvent(new Event('input', { bubbles: true }));
365
387
  field.dispatchEvent(new Event('change', { bubbles: true }));
388
+ blurField();
366
389
  return finish({ found: true, applied: true, requests: count });
367
390
  })()`;
368
391
  }