@appaflytech/zeyrek-ai-mcp 0.0.37 → 0.0.40

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 (37) hide show
  1. package/dist/knowledge/componentCatalog.js +389 -149
  2. package/dist/knowledge/componentEvents.js +16 -8
  3. package/dist/knowledge/connectDataBind.js +10 -2
  4. package/dist/knowledge/expressionPrelude.js +1 -0
  5. package/dist/knowledge/mobileComponentCatalog.js +16 -4
  6. package/dist/knowledge/mobileComponentEvents.js +21 -1
  7. package/dist/knowledge/mobileStore.js +75 -0
  8. package/dist/knowledge/normalize.js +22 -5
  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 +79 -2
  13. package/dist/knowledge/styleResolve.js +27 -7
  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/secrets.js +50 -2
  19. package/dist/resources/guides/component.md +1 -1
  20. package/dist/resources/guides/merge.md +5 -4
  21. package/dist/resources/guides/microservice.md +1 -1
  22. package/dist/resources/guides/middleware.md +1 -1
  23. package/dist/resources/guides/mobile-publish.md +24 -0
  24. package/dist/resources/guides/mobile.md +22 -6
  25. package/dist/resources/guides/page.md +14 -7
  26. package/dist/resources/guides/theme.md +1 -0
  27. package/dist/resources/schema-fingerprint.json +3 -3
  28. package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +2 -1
  29. package/dist/resources/schemas/OrganizationProjectDto.json +8 -0
  30. package/dist/resources/schemas/UpsertMobileStoreSettingsCommand.json +59 -0
  31. package/dist/tools/applications.js +6 -3
  32. package/dist/tools/common.js +9 -5
  33. package/dist/tools/environments.js +1 -1
  34. package/dist/tools/flows.js +5 -1
  35. package/dist/tools/mobileStore.js +21 -5
  36. package/dist/tools/pages.js +3 -3
  37. package/package.json +1 -1
@@ -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
@@ -556,8 +578,6 @@ export const NODE_SURFACE_TYPES = new Set([
556
578
  'table',
557
579
  'datatable',
558
580
  'collapsible-container',
559
- 'drawer',
560
- 'modal',
561
581
  'popover',
562
582
  'tabs',
563
583
  '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ı)`];
@@ -1,8 +1,12 @@
1
1
  /**
2
2
  * Desen `trace.ts`'in eski kopyasından DARALTILDI: `pass` parçası `passed`/`bypass` gibi
3
3
  * anahtarları da yakalıyordu. Günlükte zararsızdı, tool yanıtında veri kaybı olurdu.
4
+ *
5
+ * `cookie` yalnız TAM ad olarak eşleşir (g-01, 37 S-24): paketli şemalarda `StepRuleDto.cookieKey`
6
+ * çerezin ADINI taşır; parça eşleşmesi onu `***` yapıp workflow_get → update turunu bozardı (GW-001).
7
+ * `setcookie` de bilinçli dışarıda: workflow adım tipinin adıdır, sır değildir.
4
8
  */
5
- export const secretKeyPattern = /password|passwd|secret|token|authorization|api[-_]?key|credential|connection[-_]?string/i;
9
+ export const secretKeyPattern = /password|passwd|secret|token|authorization|api[-_]?key|credential|connection[-_]?string|^cookies?$/i;
6
10
  /**
7
11
  * Sırrın DEĞERİNİ değil adını, yerini ya da türünü taşıyan anahtarlar: `credentialRef`, `tokenUrl`,
8
12
  * `apiKeyHeader`, `secretFields`, `credentialKind`. Maskelenirlerse süreç tanımındaki
@@ -14,18 +18,62 @@ import { looksLikeJwt, rememberToken } from './tokenVault.js';
14
18
  const maskedValue = '***';
15
19
  const maxPaths = 20;
16
20
  const maxDepth = 12;
21
+ /**
22
+ * Ad/değer çiftinde `value`'nun yerine geçen ad (g-01, 37 S-24). Debug girdisi isteğin başlık ve
23
+ * çerezlerini `{ expression: 'Authorization', value: 'Bearer …', source, member }` olarak yazar; sırrın
24
+ * adı anahtarda değil kardeş alanda durduğu için anahtar kuralı hiçbirini görmüyordu.
25
+ *
26
+ * `type` ya da `order` taşıyan satır platform TANIMIDIR — `StepQueryParameterDto {name,value,type}`,
27
+ * `StepCustomParameterDto {name,value,order}`, `QueryParameter {name,type,value}`: değer bağlama
28
+ * ifadesidir (`input.token`), maskelenirse get → update turunda `***` geri yazılır (GW-001).
29
+ * `expression` + `value` hiçbir paketli şemada geçmez, o yüzden bu ayrımın dışındadır.
30
+ */
31
+ export function secretPairName(record) {
32
+ if (!('value' in record))
33
+ return undefined;
34
+ if (typeof record.expression === 'string')
35
+ return record.expression;
36
+ if ('type' in record || 'order' in record)
37
+ return undefined;
38
+ if (typeof record.name === 'string')
39
+ return record.name;
40
+ return typeof record.key === 'string' ? record.key : undefined;
41
+ }
42
+ /**
43
+ * Anahtarsız string yapraktaki kimlik (g-01): `eyJ…` ile başlayan JWT ve `Bearer <jeton>`.
44
+ * `maskSecretText`'ten DAR: o konsol metni içindir ve her üç parçalı noktalı adı JWT sayar —
45
+ * yanıtta `organization.something.localhost` gibi bir adı körleştirirdi. Bearer'dan sonra en az
46
+ * 16 karakter aranır; rehber metnindeki "Bearer olarak" maskelenmez.
47
+ */
48
+ export function maskSecretLeaf(text) {
49
+ return text
50
+ .replace(/Bearer\s+[A-Za-z0-9._~+/=-]{16,}/gi, `Bearer ${maskedValue}`)
51
+ .replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*/g, maskedValue);
52
+ }
17
53
  function walk(value, path, masked, refs, depth) {
18
54
  if (depth > maxDepth)
19
55
  return value;
56
+ if (typeof value === 'string') {
57
+ const safe = maskSecretLeaf(value);
58
+ if (safe === value)
59
+ return value;
60
+ if (masked.length < maxPaths)
61
+ masked.push(path);
62
+ if (looksLikeJwt(value) && Object.keys(refs).length < maxPaths)
63
+ refs[path] = rememberToken(value);
64
+ return safe;
65
+ }
20
66
  if (Array.isArray(value))
21
67
  return value.map((entry, index) => walk(entry, `${path}[${index}]`, masked, refs, depth + 1));
22
68
  if (value && typeof value === 'object') {
23
69
  const result = {};
70
+ const pairName = secretPairName(value);
24
71
  for (const [key, entry] of Object.entries(value)) {
25
72
  const child = path === '' ? key : `${path}.${key}`;
73
+ const name = key === 'value' && pairName !== undefined ? pairName : key;
26
74
  // Boole değer sır taşıyamaz: `hasExpoAccessToken: true` maskelenince ajan token'ın kayıtlı
27
75
  // olup olmadığını öğrenemiyordu (push bildirimleri g-01, 2026-10-02; GW-001 ailesi).
28
- if (secretKeyPattern.test(key) && !secretNameKeyPattern.test(key) && typeof entry !== 'boolean' && entry !== null && entry !== undefined && entry !== '') {
76
+ if (secretKeyPattern.test(name) && !secretNameKeyPattern.test(name) && typeof entry !== 'boolean' && entry !== null && entry !== undefined && entry !== '') {
29
77
  if (masked.length < maxPaths)
30
78
  masked.push(child);
31
79
  // Maskelenen değer bir JWT ise kimlik testinin yolu kapanmasın: jeton kasada tutulur ve
@@ -197,7 +197,7 @@ Her şema düğümünde iki sözlük vardır; ikisi de ortak render katmanında
197
197
 
198
198
  | `appearance` | Değerler | Anlamı |
199
199
  |---|---|---|
200
- | `heightType` | `auto` · `fixed` · `fill` | `auto`: içerik kadar (önerilen — Kural 1d); `fixed`: `heightString` varsa o, yoksa `blocks.h × 10px`; `fill`: **kalanı doldur** — ebeveynin bıraktığı yer (ızgarada `height:100%`, flex sütununda `flex:1 1 auto`, flex satırında `align-self:stretch`). Tam ekran yerleşim kabuğu bununla kurulur, bkz. `platform_guide('layout')` |
200
+ | `heightType` | `auto` · `fixed` · `fill` | `auto`: içerik kadar (önerilen — Kural 1d); `fixed`: `heightString` varsa o, yoksa `blocks.h × 10px`; `fill`: **kalanı doldur** — ebeveynin bıraktığı yer (ızgarada `height:100%`, flex sütununda `flex:1 1 auto`, flex satırında `align-self:stretch`). Tam ekran yerleşim kabuğu bununla kurulur, bkz. `platform_guide('layout')`. **Mobilde (≤ 767 px) mobil bloğu olmayan her düğüme `height: auto !important` yazılır**: yüksekliği `height` olarak yazılan `fixed` düğüm (flex ya da CSS grid ebeveyni, stretch yokken) içeriğine göre çizilir; stretch'li satırdaki `min-height` ve 12 kolonlu eski ebeveynin `grid-row` yüksekliği bundan bağımsızdır. Yazılı yüksekliği mobilde de istiyorsan (`heightType: 'fixed'` iken) `appearance.className`'e ayrılmış **`ng-keep-height-mobile`** belirtecini yaz (`className: "hero ng-keep-height-mobile"`) |
201
201
  | `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 |
202
202
  | `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ı |
203
203
  | `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) |
@@ -60,14 +60,15 @@ Studio'nun runtime rotasının üç şeridi vardır ve **ortamı URL belirler**:
60
60
  |---|---|
61
61
  | `production` | `/view/{proje}/{app}/{sayfa}` |
62
62
  | `development` | `/dev/{proje}/{app}/{sayfa}` |
63
- | dal | `/branch/{dal}/{proje}/{app}/{sayfa}` |
63
+ | dal | `/branch/{slug}/{proje}/{app}/{sayfa}` — slug, adın `/` yerine `-` taşıyan hâli (`feature/odeme` → `feature-odeme`) |
64
64
 
65
65
  Sayfanın **bütün** istekleri o dala gider: sayfa şeması da, akışların uç çağrıları da. Yani kare
66
66
  "yarı doğru" değildir — gördüğünüz ekran dalın gerçek ekranıdır.
67
67
 
68
- Reddedilen tek şey **dal adı olamayacak bir ad**: harfle başlamayan, küçük harf/rakam/alt çizgi
69
- dışında karakter taşıyan, 3-31 aralığının dışına çıkan ya da rezerve bir ad (`public`, `api`,
70
- `pg_*` …). Projedeki dalları `environment_list` verir.
68
+ Reddedilen tek şey **dal adı olamayacak bir ad**: harfle başlamayan, küçük harf/rakam/alt çizgi/tire
69
+ ve `/` ile ayrılmış parçalar dışında karakter taşıyan, 3-31 aralığının dışına çıkan ya da rezerve bir
70
+ ad (`public`, `api`, `pg_*` …). Projedeki dalları ve slug'larını `environment_list` verir; tenant
71
+ çağrılarına `environment` olarak adı da slug'ı da geçebilirsiniz.
71
72
 
72
73
  2026-09-09 öncesinde bu çağrı **reddediliyordu**: runtime rotası dal adı taşımıyordu, ortam
73
74
  modül içi bir store'dan geliyordu ve dal adıyla istenen kare development'ı gösterirdi. O günlerin
@@ -515,7 +515,7 @@ hem `joins` ile getirin hem `selectFields`'a koyun; ondan sonra `filters[0][fiel
515
515
  çalışır. m2m alanlar da desteklenir (`EXISTS` alt sorgusuna çevrilir).
516
516
 
517
517
  > **Expression filtresi istek parametresi TAŞIYAMAZ.** Microservice'te expression'lar
518
- > `workflowContext.JavascriptEngineInstance` ile eval edilir ve o bağlamda `input` global'i
518
+ > `workflowContext.CreateJavascriptEngine()` ile eval edilir ve o bağlamda `input` global'i
519
519
  > **yoktur** — `value: "input.doctor_id"` yazarsan uç `500 JavaScriptException: input is not
520
520
  > defined` verir (`input` workflow table step'inin sözlüğüdür, microservice'in değil).
521
521
  > Expression'ın okuyabildiği iki şey vardır: **sabit değerler** (`"'Aktif'"`, `new Date()`) ve
@@ -52,7 +52,7 @@ cevabı adımın ifadesinde gizlidir. Ayrıntı ve yazım ölçütü: `platform_
52
52
  3. **Değişkenler istek boyunca yaşar.** `set-user` / `variable` step'lerinin kurduğu
53
53
  değişkenler istek kapsamındaki `WorkflowContext`'e yazılır (`AddVariable`) ve her akış
54
54
  koşusunun JS motoru o anki değişkenlerden kurulur (`WorkflowRunner` →
55
- `JavascriptEngineInstance`). Hedef microservice'in **expression filtrelerinde/alanlarında**
55
+ `CreateJavascriptEngine()`). Hedef microservice'in **expression filtrelerinde/alanlarında**
56
56
  ve step ifadelerinde doğrudan okunur (`user.id`, `settings.auth_method`). Ayrıca microservice
57
57
  yolunda middleware'in response gövdesindeki `user` / `data` anahtarları da değişkene
58
58
  terfi eder (`ExecuteMicroserviceBehavior.cs:105` — sayfa yolunda bu terfi YOKTUR).
@@ -70,6 +70,7 @@ metni yeni numarayla yeniden dener.
70
70
  | P-10 | `manual declarations_pending` | kullanıcı onayıyla `mobile_store_update { declarations }` (engellemez) |
71
71
  | P-11 | push (yalnız `testflight` / `internal`): `warn push_incomplete` (kimlik eksik — derleme push'suz çıkar); `push_unreadable` (kimliğin şifresi çözülemiyor), `push_profile_stale` (profil APNs anahtarından önce üretilmiş), `push_firebase_mismatch` (`google-services.json` başka Firebase projesinin), `push_package_mismatch` (dosya yüklendikten sonra Android paket adı değişmiş), `push_shell_outdated` (kabuk paketi push taşımıyor) | Studio › Yayın › Hesaplar ve kimlik bilgileri (insan yükler); `push_profile_stale`: profili yeniden üret; `push_shell_outdated`: platform ayarı (destek). Push kapalı ya da hazırsa satır `ok`'tur ve dryRun'da görünmez — hazır olduğunu `mobile_store_get { manifest: true }` → `manifest.push` söyler |
72
72
  | P-12 | izinler: `warn permissions_shell_outdated` (yayındaki kabuk izin kümesini taşımıyor: sabit izinler bildirilir, kamera çalışmaz, kipler uygulanmaz — başka P-12 satırı çıkmaz); kabuk taşıyorsa `fail permission_off_in_use` (kip `off` ama production o izni yazılı olarak kullanıyor) ya da `warn launch_permissions_shell_outdated` (açılışta sorulan izin var, kabuk açılışta sormuyor; kabuk kümeyi de taşımıyorsa bu cümle ilk uyarının iletisine eklenir) | `mobile_store_update { settings: { permissionModes } }` ya da kullanımı kaldırıp dağıtım; açılış uyarısında daha yeni kabuk ya da izni akış adımından isteme |
73
+ | P-13 | reklam (yalnız `ads.enabled`): `warn ads_app_id_missing` (bu platformun AdMob App ID'si yok — derleme reklamsız çıkar), `warn ads_shell_outdated` (yayındaki kabuk reklam taşımıyor), yükleme hedefinde `manual ads_test_mode` (test kipi açık: mağazada test reklamı görünür), değilse `ok ads_ready` (yerleşim sayısıyla) | `mobile_store_update { settings: { ads } }`; test kipini yayından önce `testMode: false` |
73
74
 
74
75
  Satırın `message` ve `fixHint`'i backend'den, istek dilinde gelir.
75
76
 
@@ -98,9 +99,32 @@ sayfa · bileşen ya da akış adımı), `build` (production: `carried`, `reason
98
99
  taşımayan son mağaza derlemelerini (TestFlight / Play iç test) listeler; izin kullanılıyorsa yanıt
99
100
  `permissionWarnings` taşır — production kullanıyorsa yeni derleme şimdi, yalnız okunan ortam kullanıyorsa
100
101
  dağıtımdan sonra gerekir.
102
+ - **İzleme izni (`tracking`, iOS ATT, kök ADR 0011):** derleme taşıyorsa App Store Connect App Privacy'de "Device
103
+ ID — Used to Track You", Play Console Data safety'de "Advertising ID" beyanı insanın işidir; izin penceresinden önce
104
+ teşvik ya da kendi ön pencerenizi göstermeyin (App Store 5.1.2). Görünüm bunu `permissionWarnings`'te söyler
105
+ (`MOB-027`). Taşınmayan ikiliye `expo-tracking-transparency` hiç bağlanmaz. Reklam açıksa izin, App ID'si olan platformun derlemesinde taşınır (yalnız Android'de reklam varsa iOS ikilisi ATT taşımaz).
101
106
  - Tarama `setter` ile yazılan tetikleyici prop'u da sayar; `code` adımı, fonksiyon gövdesi, `webview` / `html`
102
107
  içeriği ve dış bileşen görünmez — onlar için kip `on`.
103
108
 
109
+ ## Reklamlar
110
+
111
+ Google Mobile Ads (kök ADR 0012) mağaza ayarıdır: `settings.ads = { enabled, iosAppId, androidAppId, testMode,
112
+ consentForm, appOpenUnit, units: [{ name, format, ios, android }] }`. App ID `ca-app-pub-…~…`, birim kimliği
113
+ `ca-app-pub-…/…`; `name` `^[a-z][A-Za-z0-9]{0,39}$` ve tekil (bileşen `adBanner.unit` ve adım `ad-show.value` bu adla
114
+ anar); `format` `banner` · `interstitial` · `rewarded` · `rewardedInterstitial` · `appOpen`; bir platformun kimliği boşsa
115
+ yerleşim o platformda yoktur. Biçim hatası `MOB-022` ile yazılmadan durur.
116
+
117
+ - SDK ikiliye yalnız `enabled` iken ve o platformun App ID'si varken bağlanır; o platformun derlemesine `tracking`'i de taşıtır (kip
118
+ `off` olamaz, 422).
119
+ - `testMode: true` (varsayılan) gerçek birim yerine Google test birimini gösterir — geliştirirken gerçek birime
120
+ tıklamak AdMob hesabını kapattırabilir. Yayından önce `false` (P-13 `manual`).
121
+ - `consentForm: true` (varsayılan) açılışta AdMob UMP rıza formunu gerekiyorsa gösterir; rıza mesajının kendisi AdMob
122
+ konsolunda kurulur (insan). Sıra: rıza → açılış izinleri (ATT) → SDK başlatma; rıza reklama izin vermezse uygulama
123
+ reklamsız sürer.
124
+ - `appOpenUnit` `appOpen` biçimli bir yerleşimin adı; soğuk açılışta bir kez gösterilir.
125
+ - `mobile_store_get` reklam açıkken `adsWarnings` döner (`MOB-026`): test kipi, App ID'si olan platformda birimi olmayan
126
+ yerleşim, konsol beyanları (App Privacy, Data safety, AdMob rıza mesajı — insanın işi).
127
+
104
128
  ## Hata kodları
105
129
 
106
130
  `mobile_build_status` → `error.code`.
@@ -37,7 +37,7 @@ Mobil uygulamanın bileşen kataloğu ayrıdır (backend satırları `(slug, typ
37
37
  mobil farkları: düşen prop, değişen etkileşim, mobile özgü prop notları) ve `mobileNotes` (bütün
38
38
  mobil bileşenlere geçen notlar). Web'de karşılığı olmayan bileşende (`app-bar`, `safe-area`,
39
39
  `scroll-view`, `choice-chips`, `tooltip`, `markdown`, `calendar`, `stack`, `list-item`, `fab`, `timer`,
40
- `audio`, `lottie`, `webview`, `map`, `grouped-list`, `floating-container`, `skeleton`, `photo-viewer`) bilgi yalnız
40
+ `audio`, `lottie`, `webview`, `map`, `adBanner`, `grouped-list`, `floating-container`, `skeleton`, `photo-viewer`) bilgi yalnız
41
41
  `mobileKnowledge`'tadır. Olay yükleri
42
42
  (`events[].payload`) önce mobile özgü sözleşmeden okunur (`app-bar.clickAction`, `repeater.swipe`,
43
43
  `choice-chips.change`…), yoksa web'inkinden.
@@ -91,6 +91,11 @@ mobil bileşenlere geçen notlar). Web'de karşılığı olmayan bileşende (`ap
91
91
  OpenStreetMap): görünüm `latitude`/`longitude`/`zoom` sayılarıdır ve akış yazınca harita oraya gider;
92
92
  kullanıcı haritayı hareket ettirince `regionChange { latitude, longitude, zoom }` düğüme geri yazılır,
93
93
  işaretçiye dokunmak `markerClick { markerId }` yayar.
94
+ `adBanner` mobile özgüdür (Google Mobile Ads banner'ı, kök ADR 0012): `unit` mağaza ayarındaki reklam
95
+ yerleşiminin **adıdır** (birim kimliği değil), `size` `banner` 320×50 · `largeBanner` · `mediumRectangle` 300×250 ·
96
+ `fullBanner` · `leaderboard` · `adaptive` · `inlineAdaptive`. Reklamı yalnız mağaza sürümü çizer; kanvas, önizleme ve
97
+ oynatıcı boyutlu yer tutucu çizer ve çalışma kipinde `fail { code: 'unavailable' }` yayar. Mağaza sürümünde reklam
98
+ hazır değilken (rıza, SDK) yer kaplamaz; yerleşim yoksa ya da yüklenemezse `fail` yayar ve yer kaplamaz.
94
99
  Dördüncü adım: `datepicker`, `timepicker` ve `datetimepicker` web ikizleridir — kutuya dokununca cihazda
95
100
  işletim sisteminin seçicisi (iOS alttan açılan sayfa, seçim Tamam'la yazılır; Android iletişim kutusu),
96
101
  önizlemede tarayıcının seçicisi açılır; değer ve `change` yükü web ile aynıdır. Sistem seçicisi tek tek
@@ -215,8 +220,8 @@ bölümleri bileşen render anında çıkarır.
215
220
  `Section` boş nesnedir (`Row` gibi). Öğe repeater satırıyla aynıdır: `Row`, `RowIndex`,
216
221
  `{{ item.<alan> }}`; akışta tıklanan satır `tasks.selectedItem`. Şablon çocukları setter hedefi
217
222
  değildir — satır dışına (sayfadaki bir metne, modala) yazın.
218
- 4. **Olaylar.** `itemPress { index, row }` (yalnız `itemPressable`; öğe şablonu `list-item`/`button`
219
- ise dokunuş onun `click`'ine gider), `swipe { key, index, row }` (`swipeActions`),
223
+ 4. **Olaylar.** `itemPress { index, row }` (yalnız `itemPressable`; öğe şablonu `button` ya da `click`'i
224
+ bağlı `list-item` ise dokunuş onun `click`'ine gider — bağsız `list-item` basılamaz), `swipe { key, index, row }` (`swipeActions`),
220
225
  `change { selectedIndex }`, `sectionPress` (yük `Section`). `index` `rows` içindeki DÜZ indekstir.
221
226
  5. **Yapışkan gün başlığı.** `stickySectionHeaders: true` + `appearance.heightType: 'fixed'` +
222
227
  `heightString`. Sayfanın kendisi kaydırıcıdır; `auto` ya da `fill` liste içerik kadar uzar ve başlık
@@ -418,8 +423,9 @@ Motorun kendi adımları (`setter`, `condition`, `microservice`, `code`, `functi
418
423
  | `notification-permission` | bildirim izni ister (sorulmuşsa yeniden sormaz, kalıcı red yeniden sorulmaz). Sonuç `Steps.<adım>.data.status`: `granted` · `denied` · `undetermined`; red hata değildir, hep `always` |
419
424
  | `notification-schedule` | yerel bildirim planlar (`expo-notifications`; uzak push değil). `args.name` kimlik (düz metin; aynı kimlik eskisinin yerini alır), `args.title` başlık (düz metin), `args.message` gövde (**ifade**), `args.value` zaman (**ifade**): sonucu `'HH:mm'` → her gün, `{ weekday: 1–7, time: 'HH:mm' }` → her hafta (1 = Pazartesi), ISO tarih-saat → bir kez. `args.path` dokununca açılacak sayfanın slug'ı (+ `args.query` ifade), boşsa uygulama açılır. İzin yoksa `error.status: 'denied'`, geçmiş ya da okunamayan zaman `invalid`: `error` dalı → hata akışı → `always`. Web'de desteklenmez (`unsupported`) — sayfa kapalıyken tarayıcı gösteremez |
420
425
  | `notification-cancel` | `args.name` kimlikli planlı bildirimi siler, `'*'` hepsini; hep `always` |
421
- | `permission` | bir cihaz iznine bakar ya da ister. `args.value` izin türü, **düz metin — ifade olamaz** (mağaza derlemesinin taşıyacağı izinler bu değerden, akış koşmadan hesaplanır; `FLOW-038`); `args.name` kip: `check` yalnız okur (sistem penceresi açılmaz), `request` verilmemiş ve hâlâ sorulabilen izni ister (boş → `request`). Sonuç `Steps.<adım>.data = { status, canAskAgain }`: `granted` · `denied` · `undetermined` · `unavailable` (derleme bu izni taşımıyor ya da web önizlemesi); `canAskAgain: false` → yalnız Ayarlar'dan değişir. Red hata değildir, hep `always`. Türler: `mobile_store_get { detail: ['permissions'] }` (`support: supported` satırlarının `key`'i) |
426
+ | `permission` | bir cihaz iznine bakar ya da ister. `args.value` izin türü, **düz metin — ifade olamaz** (mağaza derlemesinin taşıyacağı izinler bu değerden, akış koşmadan hesaplanır; `FLOW-038`); `args.name` kip: `check` yalnız okur (sistem penceresi açılmaz), `request` verilmemiş ve hâlâ sorulabilen izni ister (boş → `request`). Sonuç `Steps.<adım>.data = { status, canAskAgain }`: `granted` · `denied` · `undetermined` · `unavailable` (derleme bu izni taşımıyor ya da web önizlemesi); `canAskAgain: false` → yalnız Ayarlar'dan değişir. `tracking` (iOS App Tracking Transparency) sonucu ayrıca `advertisingId` taşır: izin `granted` iken IDFA (iOS) / AAID (Android), değilse `null`; oynatıcı `tracking`'i hiç taşımaz (`unavailable`). Red hata değildir, hep `always`. Türler: `mobile_store_get { detail: ['permissions'] }` (`support: supported` satırlarının `key`'i) |
422
427
  | `open-settings` | uygulamanın sistem ayar sayfasını açar; `Steps.<adım>.data.opened`. Argüman almaz |
428
+ | `ad-show` | tam ekran reklam (kök ADR 0012). `args.value` mağaza ayarındaki yerleşimin **adı**, düz metin (`FLOW-039`); biçimi `interstitial` · `rewarded` · `rewardedInterstitial` olmalı. Reklam yüklenip gösterilir, kullanıcı kapatınca `Steps.<adım>.data = { shown: true, reward }` ile sürer — `reward` ödüllü reklamı sonuna kadar izleyende `{ type, amount }`, değilse `null` (ödül ayrı dal değil, `condition` ile okunur). 10 sn'de reklam yoksa, SDK hata verirse, derleme reklam taşımıyorsa ya da rıza yoksa `error.status: 'unavailable'`; tanımsız ya da banner yerleşimi `invalid`: `error` dalı → hata akışı → `always`. Oynatıcı ve web önizlemesi her zaman `unavailable` — reklamsız dalı orada sınayın. Paletten yalnız mobilde |
423
429
 
424
430
  Bildirim adımları paletten yalnız mobil uygulamada sunulur. Tarif — S04 "her gün hatırlat": önce
425
431
  `notification-permission` (S03 düğmesi), sonra `timepicker`'ın değeriyle `notification-schedule`
@@ -434,6 +440,15 @@ kamera/galeri kaynağı izni kendileri de ister — `permission` adımı *ne zam
434
440
  okumak içindir. `unavailable` bir hata değil, derlemenin o izni taşımadığının haberidir
435
441
  (`platform_guide('mobile-publish')` §İzinler).
436
442
 
443
+ **Reklam tarifi** (kök ADR 0012): (1) `mobile_store_update { settings: { ads: { enabled: true, iosAppId, androidAppId,
444
+ testMode: true, units: [{ name, format, ios, android }] } } }` — reklam, App ID'si olan platformun derlemesine `tracking`'i de sokar (ATT metni
445
+ gider, kip `off` olamaz); (2) banner için sayfaya `adBanner` `unit: '<banner yerleşimi>'`; (3) geçiş/ödüllü reklam için
446
+ düğme akışına `ad-show` `value: '<yerleşim>'`, `error` dalında reklamsız devam; (4) ödül `condition`:
447
+ `Steps.<adım>.data?.reward` doluysa ödülü ver. Açılış reklamı adım değil ayardır (`ads.appOpenUnit`). Sıra cihazda:
448
+ rıza formu → açılış izinleri (ATT) → SDK; iOS'ta izleme izni yoksa reklam kişiselleştirilmeden istenir. Yerleşim adının
449
+ ayarda tanımlı olduğu sayfa/akış yazımında denetlenemez — `mobile_store_get` `settings.ads.units` ve `adsWarnings`
450
+ (`MOB-026`); yayın ayrıntısı `platform_guide('mobile-publish')` §Reklamlar.
451
+
437
452
  `style`, `accuracy`, `reverseGeocode` gibi anahtarlar sözleşmede yoktur ve platform onları sessizce düşürür
438
453
  (`FLOW-029`). "Haritada aç" için adım yoktur: `map`'in `press` olayı düğüme `mapsUrl` yazar, akış
439
454
  `redirect` `isRaw: true`, `path: '<harita>.mapsUrl'` ile açar. Tarif — L05 "mevcut konumumu kullan":
@@ -544,6 +559,7 @@ yaptıkları. Derlenen uygulama
544
559
  - Uygulama içi tema seçimi (Sistem / Açık / Koyu) bir ayardır, bileşen değil: akış `storage` adımıyla
545
560
  `zeyrek:theme-mode` anahtarına `system` · `light` · `dark` yazar (ör. segmentli `choice-chips`'in `change`'i,
546
561
  `value: '<çipler>.value'`); barındırıcı anahtarı dinler, tema anında döner ve cihazda kalıcıdır — durum çubuğu da
547
- izler. Web'in `theme-toggle`'ı mobilde yoktur. Seçili düğme saklanan seçimi `LocalStorage`'dan okur:
548
- `{{ LocalStorage['zeyrek:theme-mode'] || 'system' }}` (ARC-147).
562
+ izler. Seçim yazılmadıysa (ilk açılış) tema **açıktır**; cihazın şeması yalnız `system` açıkça seçilince izlenir.
563
+ Web'in `theme-toggle`'ı mobilde yoktur. Seçili düğme saklanan seçimi `LocalStorage`'dan okur:
564
+ `{{ LocalStorage['zeyrek:theme-mode'] || 'light' }}` (ARC-147).
549
565
  - Dar ekran kırılımı (`hideOn: 'mobile'`) mobil uygulamada anlamsızdır: ekran zaten tek boydur.
@@ -135,9 +135,9 @@ alanlar denetlenmez); katalog okunamazsa denetim susar ve yazma eskisi gibi geç
135
135
  **Düğümü silip yeniden kurmayın:** o yol ona yazan akış setter'larını sessizce öldürür
136
136
  (`FLOW-018`, aşağıda Kural 2).
137
137
  - **Aynı ızgara hücrelerini iki komponente veremezsin.** Studio'da komponent tuvale bırakıldığında
138
- çakışan kardeşleri aşağı itme işini studio-api'nin sayfa kurma servisi yapar
139
- (`SchemaHelper.reorderComponents` → `draggableElement.moveItem`); komple şema yazan `page_create`
140
- / `page_update` o servisten geçmez, yani verdiğin koordinatlar olduğu gibi kayda girer ve tuvalde
138
+ çakışan kardeşleri aşağı itme işini Studio'nun ızgara motoru yapar
139
+ (`@ng-apps/screen-builder` `moveItem`); komple şema yazan `page_create`
140
+ / `page_update` o motordan geçmez, yani verdiğin koordinatlar olduğu gibi kayda girer ve tuvalde
141
141
  komponentler üst üste biner. **Gateway aynı algoritmayı şemanın tamamına uyguluyor** (kök dizi +
142
142
  her `children`, yalnız desktop `blocks[]` girdisi; `x`/`w`/`h` ve dizi sırası korunur, çakışanın
143
143
  `position.y`'si aşağı kayar) ve yanıtta `gatewayNormalized: "çakışan komponentler ızgarada aşağı
@@ -174,10 +174,9 @@ alanlar denetlenmez); katalog okunamazsa denetim susar ve yazma eskisi gibi geç
174
174
  ## Kural 1b — `page_build` YOK
175
175
 
176
176
  Entity'den otomatik sayfa üreten `page_build` tool'u **kaldırıldı**. Platformun
177
- `POST /pages/{app}/build/{page}` ucu üretimi `PageBuilderApiOptions:Url` adresindeki **ayrı bir
178
- servise** delege ediyor (`PageBuilderApiClient`); dev'de `localhost:4000`, prod'da `studio-api…`.
179
- O servis bu ürünün parçası değil ve ayakta olmayabilir. Sayfa/komponent şemasının tam karşılığı
180
- platformda zaten var → doğru yol yukarıdaki micro-op hattıdır.
177
+ `POST /pages/{app}/build/{page}` ucu ve onun delege ettiği sayfa üretme servisi de 2026-09-18'de
178
+ kaldırıldı; otomatik sayfa üretimi yok. Sayfa/komponent şemasının tam karşılığı platformda zaten
179
+ var → doğru yol yukarıdaki micro-op hattıdır.
181
180
 
182
181
  ## Kural 1c — Komponente VERİ bağlama: `mount` olayı + flow (setter adımı)
183
182
 
@@ -879,6 +878,14 @@ Tire yerine `_` kullan.
879
878
  > `component_schema_patch` yamalarında), `layout_create`, `layout_update(views)` ve `layout_patch`;
880
879
  > `page_schema_validate` aynı kararı verir. Ad doldurulduktan sonra denetlenir: geçersiz bir
881
880
  > elementId adsız gelirse o da reddedilir.
881
+ >
882
+ > **`name` hiç verilmezse** gateway onu `elementId` ile tamamlar ve `gatewayNormalized`'da `name`
883
+ > diye bildirir: adsız düğümü platform kabul eder ama sayfada bir akış ya da `{{ }}` kod prop'u
884
+ > varsa FlowEngine `runCode` "Cannot read properties of undefined (reading 'catch')" ile düşer, akış
885
+ > `running:1`'de takılır ve hata adsız düğümü işaret etmez. Tamamlama PAGE-009 denetiminden ÖNCE
886
+ > koşar, yani `elementId` bu kuralın geçerli saydığı bir ad değilse (`'Ana Baslik'`, `'1abc'`,
887
+ > `'Functions'`) tamamlanan ad da reddedilir. Yine de her `add_component` gövdesine
888
+ > `name = elementId` yaz; tamamlama bir güvenlik ağıdır.
882
889
 
883
890
  ## Kural 1d — Sayfa iskeleti: `page_container` ve boyutlandırma
884
891
 
@@ -18,6 +18,7 @@ yazmalarının aksine burada "önce development'ta dene" diye bir ayrım YOKTUR.
18
18
  | Alan | Ne yapar | Ürettiği değişken(ler) |
19
19
  |---|---|---|
20
20
  | `mode` | `'light'` (varsayılan) \| `'dark'` — yayınlanan uygulamanın modu | palet tabanını seçer |
21
+ | `direction` | `'ltr'` \| `'rtl'` \| `'auto'` — sayfanın okuma yönü; `auto` uygulama dilinden türetir (`ar` `he` `fa` `ur` `ps` `sd` `ug` `yi` `dv` → rtl). Ayar YOKSA `dir` hiç yazılmaz (sayfa üst öğenin yönünü miras alır): `ar` ile kurulmuş mevcut proje bu ayar açılmadan aynalanmaz. Sayfa oluşturucu tuvali yönü uygulamaz. Grid yerleşimi kendiliğinden aynalanır; bileşenlerin ok simgeleri ve fiziksel `left/right` CSS'i bileşen bazında düzeltilir (ADR 0006) | CSS değişkeni üretmez, kap `dir` özniteliğine yazılır |
21
22
  | `palette` | AYDINLIK moddaki renk override'ları; `accent.onPrimary` isteğe bağlıdır — accent ÜSTÜNDEKİ metin, verilmezse kontrasttan türer (`#4787FA` gibi açık bir mavi siyah metin üretir, tasarımda beyazsa buraya yazın) | `--ng-accent-*` · `--ng-background-*` · `--ng-text-*` · `--ng-border-*` · `--ng-status-*` · `--ng-text-on-accent` |
22
23
  | `paletteDark` | KARANLIK moddaki renk override'ları | aynı küme |
23
24
  | `fontFamily` · `headingFontFamily` | Gövde ve başlık yazı tipi (Google Fonts adı) | `--ng-font` · `--ng-font-heading` |
@@ -1,7 +1,7 @@
1
1
  {
2
- "hash": "ca0c6a8ebec39612cfa74fe6e1cb170b141038c1fbf63804e3ae3dd0cc418ec9",
3
- "generatedAt": "2026-10-06T09:02:39.999Z",
4
- "types": 335,
2
+ "hash": "63d0b9efc3b8d0c0d8ef9c8b7478b3abf8cb482e4e80276202b3faed73cedddf",
3
+ "generatedAt": "2026-10-08T09:32:48.956Z",
4
+ "types": 337,
5
5
  "files": 167,
6
6
  "note": "Şemaları üreten backend kaynaklarının parmak izi. Elle düzenlemeyin: 'npm run generate-schemas' yazar, 'npm run schema:check' denetler.",
7
7
  "missing": [
@@ -273,7 +273,8 @@
273
273
  "authority-limit",
274
274
  "case-instance",
275
275
  "partner-relationship",
276
- "connector-credential"
276
+ "connector-credential",
277
+ "monitoring"
277
278
  ],
278
279
  "type": "string",
279
280
  "format": ""
@@ -194,6 +194,14 @@
194
194
  "type": "string",
195
195
  "nullable": true
196
196
  },
197
+ "slug": {
198
+ "type": "string",
199
+ "nullable": true
200
+ },
201
+ "displayName": {
202
+ "type": "string",
203
+ "nullable": true
204
+ },
197
205
  "isProduction": {
198
206
  "type": "boolean"
199
207
  },
@@ -117,6 +117,9 @@
117
117
  "review": {
118
118
  "$ref": "#/components/schemas/NgApps.Domain.Contexts.Organization.MobileStore.MobileStoreReviewModel"
119
119
  },
120
+ "ads": {
121
+ "$ref": "#/components/schemas/NgApps.Domain.Contexts.Organization.MobileStore.MobileStoreAdsModel"
122
+ },
120
123
  "expectedUpdatedAt": {
121
124
  "type": "string",
122
125
  "format": "date-time",
@@ -255,6 +258,40 @@
255
258
  },
256
259
  "additionalProperties": false
257
260
  },
261
+ "NgApps.Domain.Contexts.Organization.MobileStore.MobileStoreAdsModel": {
262
+ "type": "object",
263
+ "properties": {
264
+ "enabled": {
265
+ "type": "boolean"
266
+ },
267
+ "iosAppId": {
268
+ "type": "string",
269
+ "nullable": true
270
+ },
271
+ "androidAppId": {
272
+ "type": "string",
273
+ "nullable": true
274
+ },
275
+ "testMode": {
276
+ "type": "boolean"
277
+ },
278
+ "consentForm": {
279
+ "type": "boolean"
280
+ },
281
+ "appOpenUnit": {
282
+ "type": "string",
283
+ "nullable": true
284
+ },
285
+ "units": {
286
+ "type": "array",
287
+ "items": {
288
+ "$ref": "#/components/schemas/NgApps.Domain.Contexts.Organization.MobileStore.MobileStoreAdUnitModel"
289
+ },
290
+ "nullable": true
291
+ }
292
+ },
293
+ "additionalProperties": false
294
+ },
258
295
  "NgApps.Domain.Contexts.Organization.MobileStore.MobileStoreAppleCategoriesModel": {
259
296
  "type": "object",
260
297
  "properties": {
@@ -268,6 +305,28 @@
268
305
  }
269
306
  },
270
307
  "additionalProperties": false
308
+ },
309
+ "NgApps.Domain.Contexts.Organization.MobileStore.MobileStoreAdUnitModel": {
310
+ "type": "object",
311
+ "properties": {
312
+ "name": {
313
+ "type": "string",
314
+ "nullable": true
315
+ },
316
+ "format": {
317
+ "type": "string",
318
+ "nullable": true
319
+ },
320
+ "ios": {
321
+ "type": "string",
322
+ "nullable": true
323
+ },
324
+ "android": {
325
+ "type": "string",
326
+ "nullable": true
327
+ }
328
+ },
329
+ "additionalProperties": false
271
330
  }
272
331
  }
273
332
  }
@@ -3,11 +3,14 @@ import { handle } from '../platform/responses.js';
3
3
  import { forgetApplication, readApplicationPlatform, resolveApplication } from '../knowledge/platform/resolve.js';
4
4
  import { mobileTabsProblems, readMobileTabs } from '../knowledge/platform/mobileTabs.js';
5
5
  import { findingLine } from '../knowledge/platform/profile.js';
6
+ import { listComponents } from './components.js';
6
7
  import { z } from 'zod';
7
8
  /**
8
9
  * MOB-007 (sekme ayarı) — `properties.mobile.tabs` yazan mobil uygulama çağrısının denetimi. Yazım
9
10
  * platforma YİNE gider (D3; backend `properties`'i doğrulamaz): bulgu `platformWarnings`'e düşer.
10
- * Sayfa ve widget listesi okunamazsa o denetimler atlanır, biçim denetimi yine koşar.
11
+ * Sayfa ve widget listesi okunamazsa o denetimler atlanır, biçim denetimi yine koşar. Widget listesi
12
+ * `listComponents`'tan gelir: uç `type` verilmezse yalnız web bileşenlerini döner ve mobil widget'ı
13
+ * gösteren sekme ayarı yanlış MOB-007 alırdı (ARC-245).
11
14
  */
12
15
  export async function mobileTabsWarnings(platform, applicationId, projectSlug, homepageId, properties) {
13
16
  const tabs = readMobileTabs(properties);
@@ -19,8 +22,8 @@ export async function mobileTabsWarnings(platform, applicationId, projectSlug, h
19
22
  .catch(() => undefined)
20
23
  : undefined;
21
24
  const widgetIds = applicationId && projectSlug
22
- ? await platform.tenant('GET', projectSlug, `/components/${applicationId}`, { query: { pageIndex: 0, pageLength: 999 } })
23
- .then(response => new Set((response?.data ?? []).filter(entry => entry.kind === 'widget').map(entry => String(entry.id ?? ''))))
25
+ ? await listComponents(platform, projectSlug, undefined, applicationId)
26
+ .then(components => new Set(components.filter(entry => entry.kind === 'widget').map(entry => String(entry.id ?? ''))))
24
27
  .catch(() => undefined)
25
28
  : undefined;
26
29
  return mobileTabsProblems(tabs, {