@appaflytech/zeyrek-ai-mcp 0.0.41 → 0.0.43

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 (49) hide show
  1. package/dist/knowledge/componentCatalog.js +27 -97
  2. package/dist/knowledge/componentEvents.js +1 -18
  3. package/dist/knowledge/mobileComponentCatalog.js +114 -15
  4. package/dist/knowledge/mobileComponentEvents.js +41 -0
  5. package/dist/knowledge/motionResolve.js +252 -0
  6. package/dist/knowledge/normalize.js +87 -59
  7. package/dist/knowledge/pageDiscover.js +2 -2
  8. package/dist/knowledge/platform/batchOps.js +70 -0
  9. package/dist/knowledge/platform/heroTag.js +153 -0
  10. package/dist/knowledge/platform/lint.js +128 -6
  11. package/dist/knowledge/platform/mobile.js +1 -1
  12. package/dist/knowledge/platform/mobileLayoutLint.js +8 -1
  13. package/dist/knowledge/platform/mobileRules.js +18 -0
  14. package/dist/knowledge/platform/pageTransition.js +57 -0
  15. package/dist/knowledge/platform/web.js +1 -1
  16. package/dist/knowledge/rules.js +52 -1
  17. package/dist/knowledge/styleResolveNative.js +88 -4
  18. package/dist/platform/capture/actions.js +4 -1
  19. package/dist/platform/capture/capture.js +10 -13
  20. package/dist/platform/capture/persistent-browser.js +130 -0
  21. package/dist/resources/examples/flow-list-to-form.json +8 -10
  22. package/dist/resources/examples/flow-load-table.json +1 -1
  23. package/dist/resources/examples/mobile-list-detail-form.json +4 -3
  24. package/dist/resources/examples/mobile-scan-code.json +285 -0
  25. package/dist/resources/guides/component.md +3 -3
  26. package/dist/resources/guides/expressions.md +2 -2
  27. package/dist/resources/guides/merge.md +98 -18
  28. package/dist/resources/guides/microservice.md +2 -2
  29. package/dist/resources/guides/mobile-layout.md +2 -1
  30. package/dist/resources/guides/mobile-publish.md +1 -0
  31. package/dist/resources/guides/mobile-steps.md +67 -0
  32. package/dist/resources/guides/mobile.md +27 -55
  33. package/dist/resources/guides/page.md +182 -121
  34. package/dist/resources/guides/translation.md +26 -0
  35. package/dist/resources/schema-fingerprint.json +2 -2
  36. package/dist/resources/schemas/ResolveMergeConflictsCommand.json +30 -1
  37. package/dist/tools/activities.js +3 -1
  38. package/dist/tools/applications.js +16 -2
  39. package/dist/tools/components.js +12 -6
  40. package/dist/tools/designSchemaPatch.js +18 -13
  41. package/dist/tools/layouts.js +7 -2
  42. package/dist/tools/merges.js +29 -9
  43. package/dist/tools/pages.js +129 -127
  44. package/dist/tools/platformInfo.js +3 -2
  45. package/dist/tools/profileWrite.js +2 -2
  46. package/dist/tools/schemaOps.js +132 -34
  47. package/dist/tools/screenWrite.js +1 -1
  48. package/dist/tools/screens.js +3 -3
  49. package/package.json +2 -2
@@ -1,3 +1,35 @@
1
+ import { MOTION_DEFAULTS, MOTION_EASING, MOTION_ENTER, MOTION_EXIT, MOTION_LOOP, MOTION_OUTSIDE_SHELL, MOTION_PRESS, MOTION_RANGES, MOTION_TRANSITION } from './motionResolve.js';
2
+ /**
3
+ * `motionStyle` düğüm kabuğunun grubudur (frontend `catalog-schema.ts`, ADR 0018): not kayıt başına değil burada tek
4
+ * kez yazılır. Değer kümeleri, aralıklar ve grubu taşımayan tipler aynadan okunur (`motionResolve.ts`), elle
5
+ * kopyalanmaz. Kabuğun uygulaması: frontend `renderer/MotionShell.tsx` (ölçüldü 2026-10-08: oyun alanı, `/dev`, iOS).
6
+ */
7
+ const motionChoices = (values) => values.filter(value => value !== 'none').map(value => `\`${value}\``).join(', ');
8
+ const MOTION_STYLE_NOTE = `\`motionStyle\` (mobile özgü; panelde Motion bölümü; içeriğini kabuğun dışında çizen ${MOTION_OUTSIDE_SHELL.map(type => `\`${type}\``).join(', ')} dışında HER bileşende) düğümün hareketidir: \`enter\` (${motionChoices(MOTION_ENTER)}), ` +
9
+ `\`exit\` (${motionChoices(MOTION_EXIT)}), \`loop\` (${motionChoices(MOTION_LOOP)}), \`transition\` (${motionChoices(MOTION_TRANSITION)}), ` +
10
+ `\`press\` (${motionChoices(MOTION_PRESS)}), \`layout\` (true/false: kardeş eklenip çıkınca kayarak yer değiştirir), ` +
11
+ `\`duration\` ${MOTION_RANGES.duration.join('–')} ms, \`delay\` ${MOTION_RANGES.delay.join('–')} ms, \`easing\` (${MOTION_EASING.map(value => `\`${value}\``).join(', ')}). ` +
12
+ `Seçenekli alanlarda \`none\` hareket yok demektir. Yazılmayan alan yok sayılır (süre ${MOTION_DEFAULTS.duration} ms, eğri \`${MOTION_DEFAULTS.easing}\`) ve şema bu değerleri düğüme YAZMAZ — siz de yazmayın: ` +
13
+ `\`loop\` süre ve eğri yazılmamışsa kendi önayarını kullanır (dönme 1 sn doğrusal, nabız 2 sn, zıplama 1 sn), yazılmışsa onu. \`press\` süresi kendinindir (150 ms). ` +
14
+ `Yalnız süre/gecikme/eğri yazılmış grup hareket değildir. Tanınmayan alan ve küme/aralık dışı değer atlanır (MOB-028). ` +
15
+ `Düğüm kabuğu uygular; \`/dev\`'de ve cihazda oynar: \`enter\` düğüm çizilince, \`exit\` gizlenince (\`isHidden\`), \`layout\` bir kardeş gizlenip görününce, \`transition\` kabuğun stili değişince (ör. akış \`surfaceStyle\`'ı yazınca). ` +
16
+ `Giriş biter, döngü sonra başlar. Döngü ve \`press\`, \`surfaceStyle\`'ın döndürme/ölçek/kaydırma/opaklığının ÜSTÜNE biner. ` +
17
+ `Kanvasta donuktur (Studio araç çubuğundaki Oynat seçili düğümün girişini ya da döngüsünü bir kez oynatır); cihazın/tarayıcının 'hareketi azalt' ayarında ve \`page_screenshot\`'ın varsayılan (donuk) karesinde durur — görmek için \`motion: 'play'\`. ` +
18
+ `Çözümü \`page_runtime_get({ resolveStyles: true })\` \`nativeStyle.motion\`'da verir. Web şemasında yoktur.`;
19
+ /**
20
+ * `heroTag` taşıyan tipler. Kaynak: frontend `packages/ng-apps-ui-native/src/catalog-schema.ts` `HERO_TAG_TYPES`
21
+ * (parite: `test/mobile-component-parity.test.ts` — katalogda alanı taşıyan tiplerle aynı küme).
22
+ */
23
+ export const MOBILE_HERO_TAG_TYPES = ['container', 'image'];
24
+ /**
25
+ * `heroTag` düğüm kabuğunun alanıdır (frontend ADR 0022): not tek yerde yazılır, taşıyan tiplerin kaydına oradan girer.
26
+ * Ölçüm: iOS simülatörü 2026-10-10 (`zeyrek-frontend/docs/features/mobile-runtime.md` §Paylaşılan öğe).
27
+ */
28
+ const HERO_TAG_NOTE = "Paylaşılan öğe etiketi (mobile özgü; panelde 'Shared element' bölümü). Bu düğüme ya da içine dokunulup bir `redirect` adımıyla başka sayfaya gidilince hedef sayfa bu düğümün kutusundan BÜYÜYEREK açılır, geri dönüşte o kutuya küçülür (iOS'un yakınlaşma geçişi: sayfanın tamamı büyür, yalnız öğe uçmaz). " +
29
+ 'Hedef sayfada AYNI etiketi taşıyan düğüm açılışın hizalandığı kutudur — iki sayfaya aynı metni yazın; hedefte eş yoksa sayfa yine kaynaktan büyür, hizalanacak kutu olmaz. ' +
30
+ "Kaynak, dokunuşun içinde başladığı en içteki etiketli düğümdür; dokunuş etiketli bir düğümün dışındaysa dokunulan liste satırının (ya da `click`i bağlı kabın) ilk KURULAN etiketli düğümü (kardeşlerde belge sırası; iç içe iki etiketli düğümde İÇTEKİ — satıra tek etiket yazın). Yani satıra dokunmak satırdaki etiketli görselden açar ve etiketi satıra özgü yazmak GEREKMEZ; hedef sayfada birden çok etiketli düğüm varsa doğrusunu ifade seçtirir (satırda `{{ 'urun-' + item.id }}`, hedefte `{{ 'urun-' + Page.queryParams.id }}`). Hedef etiketini SORGU PARAMETRESİNDEN türetin: sonradan yüklenen veriye bağlı etiket ilk çizimde boştur, değer gelince düğüm yeniden kurulur (mount akışları ikinci kez koşar). " +
31
+ "YALNIZ iOS 18+ cihazda oynar: Android'de, `/dev` önizlemesinde, `page_screenshot`'ta ve kanvasta sayfa adımın geçişiyle açılır (`redirect.transition`, MOB-029). Etiketli öğeden açılan sayfada adımın geçişi oynamaz (yakınlaşma yerine geçer); `modal` sunumda da oynar; sekme değişiminde, `replace` sunumda ve cihazın 'hareketi azalt' ayarında oynamaz. " +
32
+ "Hedef düğümün `motionStyle.enter`'ı oynamaz (öncelik yakınlaşmanın); hedef olmayan düğümünki oynar. Aynı sayfada aynı statik etiketi iki düğüme yazmak KAYNAK sayfada olağandır (aynı etiketli kartlar); HEDEF sayfada hizalama kutusu tektir ve ilk çizilen düğüm hizalanır — yazım bunu bilgi notuyla söyler (MOB-030). Eşi olmayan etiket için uyarı yoktur: sayfanın etiketleri (layout'unkiler dahil) `page_runtime_get` özetinde `heroTags`'te okunur — iki sayfayı oradan karşılaştırın.";
1
33
  /**
2
34
  * Bütün mobil komponentler için geçerli kurallar. Kayıt başına tekrarlanmaz;
3
35
  * okuyucu (`component_get`) bu notları `mobileNotes`'a ekler
@@ -5,6 +37,7 @@
5
37
  * (MOB-002) notları kural koduyla birlikte orada durur, burada tekrarlanmaz.
6
38
  */
7
39
  export const MOBILE_COMPONENT_NOTES = [
40
+ MOTION_STYLE_NOTE,
8
41
  "Tasarım kipinde (Studio kanvası) alanlar yazılamaz ve düğmeler basılamaz: dokunuş kanvasın seçim jestidir.",
9
42
  "Olay yükleri web ile AYNI sözleşmedir (`componentEvents.ts`): alanlar `change`/`changed`/`enter`/`focus`/`blur`, seçim bileşenleri `change` + `checked`/`unchecked`, katmanlar kapanışta `change { isOpen: false }` ve `open`/`close` yayar; `type: submit` düğme `click` yaymaz, üstteki `form`un `submit`ini tetikler (`formId` varsa o formun). Form doğrulaması web ile AYNIDIR (2026-09-28): alanların `validations`'ı ve formun koşullu `validations`'ı gönderimde koşar, geçersiz form `submit` YAYMAZ, alan `isInvalid`/`invalidText` alır, hatalar `form-validation-errors`'a yazılır; ilk gönderimden sonra her değişimde alan yeniden doğrulanır. Doğrulamayı akışta elle kurmayın. Mobile özgü `form.showValidState` dolu ve geçerli alana ✓ verir. `required` işaretsiz onay kutusunu değer sayar (web kuralı) — 'kabul et' formun koşullu kuralıdır: `{ element: 'kosullar', condition: '!kosullar.value', message }`.",
10
43
  "Bileşenler shadcn/ui görünümünde çizilir (react-native-reusables ilkelleri, NativeWind); renk ve köşe yine PROJE TEMASINDAN gelir: `--ng-*` değişkenleri shadcn token'larına (`--primary`, `--secondary`, `--muted`, `--destructive`, `--border`, `--input`, `--ring`…) ve köşe ölçeğine köprüyle çevrilir (`shadcn-vars.ts`), koyu kip aynı köprünün ikinci çözümüdür. Tema değişince web ve mobil birlikte hareket eder; web sayfası kendi bileşen ailesiyle çizildiği için iki platformun görünüm farkı bilinçlidir. Ölçü (alan yüksekliği, punto, iç boşluk) temanın `--ng-form-*` değişkenlerinden gelir. Yazı AİLESİ köprüde yoktur: temanın ailesi alanlarda (metin, sayı, parola, açılır liste), başlıklarda (`app-bar`, `form`, `modal`/`drawer`, boş durum) ve sekme çubuğunda uygulanır; `text` ve düğme etiketi ata kabın `typographyStyle` ailesini, o da yoksa platform yazı tipini kullanır; alan, anahtar ve onay kutusu etiketleri HER ZAMAN platform yazı tipiyle çizilir.",
@@ -27,11 +60,30 @@ export const MOBILE_STATE_FLAG_NOTES = {
27
60
  * sabit katmanlar, alt ağaçları hizaya girmez. İçerik genişlikli: genişliği içeriğinden gelen
28
61
  * yapraklar, sağ kenar sapması sayılmaz.
29
62
  */
30
- export const MOBILE_DROPPABLE_TYPES = ['carousel', 'collapsible-container', 'container', 'drawer', 'floating-container', 'form', 'grouped-list', 'modal', 'repeater', 'safe-area', 'scroll-view', 'stack', 'tabs', 'tooltip', 'view', 'widget-area'];
63
+ export const MOBILE_DROPPABLE_TYPES = ['carousel', 'collapsible-container', 'container', 'drawer', 'flip-card', 'floating-container', 'form', 'grouped-list', 'modal', 'repeater', 'safe-area', 'scroll-view', 'stack', 'swipeable-stack', 'tabs', 'tooltip', 'view', 'widget-area'];
31
64
  export const MOBILE_OUT_OF_FLOW_TYPES = ['app-bar', 'drawer', 'fab', 'floating-container', 'modal', 'toast'];
32
65
  export const MOBILE_INTRINSIC_WIDTH_TYPES = ['avatar', 'badge', 'icon', 'rating', 'text', 'timer'];
33
66
  /** Yalnız davranış ekleyen kap: çizimi çocuğundan ibarettir, kendi boşluğu içeriği içeri iter (mizanpaj S6). */
34
67
  export const MOBILE_TRANSPARENT_WRAPPER_TYPES = ['tooltip'];
68
+ /**
69
+ * Satır listeleri: çocuklarını makinenin satır kapsamıyla (`Row` / `item`, `_repeaterValues`) satır başına çizen tipler.
70
+ * Kaynak: frontend `packages/ng-apps-flow/src/machine/handlers/row-list-type.ts` `ROW_LIST_TYPES` (parite:
71
+ * `test/normalize.test.ts`). Kapsam tek seviyedir, bu yüzden biri ötekinin alt ağacına yazılamaz (PAGE-013,
72
+ * `normalize.ts` `nestedRepeaterNodes`; plans/mobil-animasyon-sistemi faz 04 F-3).
73
+ */
74
+ export const MOBILE_ROW_LIST_TYPES = ['repeater', 'grouped-list', 'swipeable-stack'];
75
+ const ROW_LIST_NESTING_NOTE = `Satır listeleri (${MOBILE_ROW_LIST_TYPES.map(type => `\`${type}\``).join(', ')}) birbirinin İÇİNE konmaz: satır kapsamı tek seviyedir, MCP yazımı \`PAGE-013\` ile reddeder.`;
76
+ /**
77
+ * Sabit görünümlü bileşenin BİLEREK boş bırakılabilen görünümü (`view` çocukları içindeki sırası): veri yokken açılan
78
+ * görünüm. Boş bırakılınca liste boşken hiçbir şey çizilmez — tasarım tercihidir, boş kap değil (MOB-018;
79
+ * plans/mobil-animasyon-sistemi faz 04 F-4). `swipeable-stack`'te aynı görünüm `loop` kapalıyken son karttan sonra da
80
+ * açılır: boş bırakılınca deste bitince alan boş kalır; aynı tercih, not düşülmez. Bu görünüm satır kapsamında da
81
+ * değildir: içindeki liste iç içe sayılmaz (PAGE-013, `nestedRepeaterNodes` — yalnız mobil profilde; web listesinin
82
+ * görünümü yoktur). Kaynak: frontend şemalarının `views` prop'undaki `empty` seçeneği
83
+ * (`packages/ng-apps-ui-native/components.json`; parite: `test/mobile-layout-lint.test.ts`). `flip-card` yüzleri burada
84
+ * yoktur: boş yüz ekranda boş kutudur.
85
+ */
86
+ export const MOBILE_OPTIONAL_VIEW_INDEX = { repeater: 1, 'swipeable-stack': 1 };
35
87
  export const mobileComponentKnowledge = {
36
88
  container: {
37
89
  summary: 'Flex akışı; mobil sayfanın tek yerleşim kabı.',
@@ -42,7 +94,8 @@ export const mobileComponentKnowledge = {
42
94
  'Kutu modeli web ile AYNI kural ve varsayılanlarla çizilir (`appearance.marginType/paddingType`, `normal` = 4 / 10 dp, `custom` kenar kenar): tipi yazılmamış düğüm `normal` dış boşluk, bırakılabilir kap `normal` iç boşluk alır — kenara yaslanacak kapta `none` yazın. `wrap: true` iken satırlar çapraz hizayı izler (web `flex-layout` ile aynı: `stretch` kabı doldurur, `top`/`left` üstten dizer).',
43
95
  "`title`/`description` web gibi gövdenin üstünde başlık bandı çizer (sarmalayıcı kapta hiç); görünümü `headerStyle`tan (başlık rengi/boyu/kalınlığı, ayraç, dolgu, dış boşluk, asgari boy, hiza — token ve `calc` çözülür). `size: grow` her doğrudan çocuğa ana eksende pay verir (web `flex: 1 1 auto`; içerik boyundan büyür, eşit değil — eşit karo için çocuğa `widthType: fixed` + yüzde).",
44
96
  "`click` (mobile-only olay, K1): bir akışa bağlıysa kap runtime'da basılır — bileşik kart (`card` çocuk almaz, kap alır). Basılı his soluklaşma + küçülme; içteki düğmenin basışı kabın `click`ini tetiklemez. Bağsız kap dokunuşu yutmaz. `ariaLabel` basılabilir kabın ekran okuyucu adıdır; cihazda rol `button`, web'de rol yazılmaz (react-native-web `button` rolünü `<button>` öğesine çevirir, içteki düğme iç içe `<button>` olurdu)."
45
- ]
97
+ ],
98
+ propNotes: { heroTag: HERO_TAG_NOTE }
46
99
  },
47
100
  'widget-area': {
48
101
  summary: 'Konteyner widget`ının kökü; yerleşim sözleşmesi `container` ile aynıdır.',
@@ -87,6 +140,7 @@ export const mobileComponentKnowledge = {
87
140
  summary: 'Metin; punto temanın tip ölçeğinden gelir.',
88
141
  differences: [
89
142
  '`wordBreak` düşer: React Native metni `numberOfLines`/`ellipsizeMode` ile sınırlar, CSS kırma kuralının karşılığı yoktur.',
143
+ 'Düşenler: `record`, `recordField`, `revealEntity` (korumalı değerin kilitli maskesi ve "Göster"i mobilde yok; metin bağlandığı değeri olduğu gibi çizer).',
90
144
  "`lineClamp` (1–10) `numberOfLines` + sonda üç nokta olarak uygulanır (2026-09-27, f-05). Mobile-only `highlight`: aramaya bağlanan metin — eşleşen parçalar `font-semibold text-primary`; karşılaştırma uygulama diline göre katlanır (`tr`'de `I`↔`ı`, `İ`↔`i`). Satır içi bağlantı için `markdown` + `linkClick` kullanın.",
91
145
  "Seviyenin mobil karşılığı varsa (`--ng-font-size-<seviye>-mobile`) o kullanılır; metin stili ATA KAPTAN MİRAS ALINMAZ (RN'de miras yoktur), kabuk tipografiyi bağlama yazar."
92
146
  ]
@@ -128,7 +182,8 @@ export const mobileComponentKnowledge = {
128
182
  "`clearable`: değer doluyken ve alan düzenlenebilirken sağda × (`<elementId>__clear`); basış `change('')` + `changed('')`, odak alanda kalır.",
129
183
  "Mobile-only (D11): `keyboardType` (`default|email|phone|number|decimal|url`; boşsa maskeden), `autoComplete` (`off|email|name|tel|username|postal-code|one-time-code` → iOS `textContentType`), `variant: search` (hap köşe, başta arama ikonu, odaktayken sağda uygulama dilinde \"Vazgeç\" `<elementId>__cancel`: değeri temizler, klavyeyi kapatır, `cancel` yayar), `isValid` (yeşil kenar + ✓; geçersizlik kazanır).",
130
184
  '`prefixIcon`/`suffixIcon` alanın içinde glif olarak çizilir (ortak `FieldBox`); `autofocus` runtime\'da klavyeyi açar. 2026-09-27\'ye dek dördü de çizilmiyordu (f-05).',
131
- 'Sayaç kuralı web ile aynı: yalnız `showCounter` açık VE `maxChars` > 0 iken çizilir.'
185
+ 'Sayaç kuralı web ile aynı: yalnız `showCounter` açık VE `maxChars` > 0 iken çizilir.',
186
+ 'Düşenler: `isMasked` (korumalı değerin "yeni değer gir" kipi mobilde yok; alan bağlandığı değeri olduğu gibi gösterir).'
132
187
  ]
133
188
  },
134
189
  textarea: {
@@ -220,7 +275,8 @@ export const mobileComponentKnowledge = {
220
275
  'Görselin boyu (`imageFrameStyle`, `video`/`lottie` ile aynı sıra): (1) boy kabuktan geliyorsa görsel kabuğu DOLDURUR — `heightType: fixed` + `px` boyda tam, `fixed` + `%` boyda ve `fill`de 96 dp taban boyun altına inmeden (ata içerik boyundaysa yüzde ve `fill` boy vermez); (2) değilse `appearance.aspectRatio` görselin kendisine yazılır: boy `auto` ve genişlik `fill`/`fixed` iken boy genişlikten türer; (3) ikisi de yoksa 96 dp taban boy (MOB-012 bilgi satırı). 2026-09-27\'ye dek (1) yoktu: `fixed` verilen görsel 96 dp çizilip altında boşluk bırakıyordu.',
221
276
  'Düşenler: `isZoomable`, `isRotatable` (görsel kendisi büyütmez; tam ekran görüntüleyici ayrı iş, plans/mobil-plantcare-acigi f-14).',
222
277
  'Mobile özgü alanlar: `fit` (`cover` varsayılan — web kuralı: `widthType: contain` sığdırır; `contain` sığdırır, `fill` gerer), `fallbackSrc` (kaynak boşsa ya da yüklenemezse çizilir; o da yoksa yer tutucu) ve `surfaceStyle` (köşe, kenar, gölge, opaklık; köşe görselin kendisine de yazılır, grupta köşe yoksa tema köşesi). Yuvarlak küçük resim: `surfaceStyle: { radius: "14px" }` + `appearance.aspectRatio: 1` — sarmalayıcı kap gerekmez.'
223
- ]
278
+ ],
279
+ propNotes: { heroTag: HERO_TAG_NOTE }
224
280
  },
225
281
  icon: {
226
282
  summary: 'Tek glif; Material Symbols LİGATÜRÜ olarak çizilir.',
@@ -255,20 +311,21 @@ export const mobileComponentKnowledge = {
255
311
  ]
256
312
  },
257
313
  repeater: {
258
- summary: 'İki görünümlü liste (YALNIZ mobil; web listesi tek şablonlu): children = [view (satır şablonu, satır başına tekrarlanır), view (boş görünüm, satır yokken bir kez)]; satır kimlikleri `__row-<i>`.',
314
+ summary: 'İki görünümlü liste (YALNIZ mobil; web listesi tek şablonlu): children = [view (satır şablonu, satır başına tekrarlanır), view (boş görünüm, satır yokken bir kez)]; satır kimlikleri `__row-<i>` (`page_screenshot` satırı `<elementId>__row-<i>` ile tıklar; özet satırları sayar).',
259
315
  differences: [
260
316
  '`FlatList` v1`de KULLANILMAZ: sanallaştırma yalnız gerçek veriyle anlamlı, tasarım kipinde ölçüm haritasını bozar (kaplama görünmeyen satırın kutusunu bulamaz).',
261
317
  'Tasarım kipinde şablon BİR kez çizilir. Runtime`da her satır web ile AYNI makine parametresiyle (`index`, `row`, `repeaterId`) çizilir: bağlı prop`lar `Row` / `RowIndex` / `item` okur. Satır yoksa (`rows` boş dizi ya da `null`) İKİNCİ görünüm (boş görünüm) bir kez, tam genişlikte ve sayfa bağlamıyla çizilir (ADR docs/adr/0005, 2026-09-30; web listesi boşken hiçbir şey çizmez). Boş durumu oraya serbestçe tasarla (emptystate, düğme, görsel); `emptyTitle`/`emptyDescription`/`emptyIcon` prop\'ları ve kardeş `emptystate` yolu KALDIRILDI. Görünümün çocukları dikey akar (`view` çizimi container\'a sütun olarak devredilir).',
262
318
  'Satırdaki bir düğümün olayı ÖNCE tekrarlayıcıya `change { selectedIndex }` yazar (aynı satırsa yazmaz), sonra kendi akışını koşar (web sırası), çocuğun `click`inden sonra da `select` yayar. Akışta tıklanan satırın iki yolu var: `<tekrarlayıcı>.selectedItem` / `.selectedIndex` HER runtime sürümünde çalışır (önerilen); `Row` / `RowIndex` / `item` ARC-121 düzeltmesini (2026-09-27) taşıyan runtime`da da satırı verir ve `item`a yazmak YALNIZ o satırı günceller — daha eski runtime`da akış ifadesinde boştu (ölçüldü 2026-09-19).',
263
319
  'Satır bağlamındaki bir akışın setter hedefi satırın şablonu DIŞINDAysa (bir modal, sayfa düzeyi bir metin) yazım SAYFA kapsamında olur; İÇİNDEyse satır bazlı depoya (`_repeaterValues`) yazılır — kapsamı olayın kaynağı değil hedefin AĞAÇTAKİ yeri belirler, `flow-trigger` üzerinden koşan aynı akış için de değişmez (ZM-08, `isElementInRepeaterTemplate`, 2026-09-26).',
264
- 'İç içe repeater zaten MCP tarafından reddedilir (`PAGE-013`) — başlıklar altında gruplu liste (güne göre görevler) için `grouped-list` kullanın; yoksa bir DIŞ repeater\'ın şablonuna İÇ satırdan yazan bir akışın yazımı dış satırın değil İÇ satırın kimliğiyle anahtarlanırdı — bilinen açık, `PAGE-013` riski düşük tutar.',
320
+ `${ROW_LIST_NESTING_NOTE} Başlıklar altında gruplu liste (güne göre görevler) için \`grouped-list\` kullanın; yoksa bir DIŞ listenin şablonuna İÇ satırdan yazan bir akışın yazımı dış satırın değil İÇ satırın kimliğiyle anahtarlanırdı — bilinen açık, \`PAGE-013\` riski düşük tutar.`,
265
321
  '`columnCount` > 1 iken satırlar yüzde genişlikle sarılır (RN`de CSS grid yok).',
266
322
  '`direction: horizontal` (MOBİLE ÖZGÜ) satırları tek satırlı, göstergesiz yatay kaydırıcıda dizer — ana sayfanın bitki kartı şeridi. Öğe genişliği `itemWidth` (px; boşsa şablonun kendi `appearance` genişliği), aralık `horizontalSpacing`; `snap: item` kaydırmayı kart başına oturtur (web önizlemesinde CSS scroll-snap). `columnCount` yatayda yok sayılır. Şablon kabına `widthType: fill` verin ki kart `itemWidth`i doldursun.',
267
323
  'GRUPLAMA bu bileşende YOKTUR: başlıklar altında liste (türe/zamana göre görevler) `grouped-list`tir — sayaç başlık şablonunda `Section.count`, toplu eylem başlıktaki bir `button` (akışı `Section.items` okur).',
324
+ 'Düşenler: `cardFill` (web ızgarasında kartı en uzun kartın boyuna uzatır; mobil satır içeriğinin boyundadır).',
268
325
  'Çoklu seçim (MOBİLE ÖZGÜ): `selectionMode: multiple` satır başına onay kutusu çizer, dokunuş seçimi değiştirir ve `itemPress`/`change`/`select` YAYILMAZ; `selectionChange { selectedIndices, count }` yayılır. Seçim düğüme yazılır: `{{ <liste>.selectedItems.length }} seçildi`, `<liste>.selectedIndices`. `longPressSelects: true` uzun basışla kipi açar (basılan satır seçili). Kipi kapatmak: setter ile `selectionMode: \'single\'` + `selectedIndices: []` + `selectedItems: []`. Seçim başlığı ayrı bir `app-bar`dır, iki çubuk `isHidden` bağıyla yer değiştirir.',
269
326
  '`swipeActions` (eylemin `icon`u etiketin üstünde glif olarak çizilir) ve `itemPressable` MOBİLE ÖZGÜDÜR (webde yok). Jest RN `Animated` + `PanResponder` ile kurulur — paket v1`de `reanimated`/`gesture-handler` yok — ve yalnız YATAY harekette yakalanır, böylece dikey kaydırma kaba kalır. Tasarım kipinde satırlar şablon olarak çizildiği için jest hiç bağlanmaz.',
270
327
  '`swipe` ve `itemPress` yükleri satırı `row` alanında TAŞIR; akış ifadesindeki `Row`/`RowIndex` bağı ayrı bir konudur (ARC-121).',
271
- 'Düşenler: `autoRowSize` (mobil satır her zaman içeriğinin boyundadır), `size` (mobil kart hücresi çocuklarını büyütmez; 2026-09-30).'
328
+ 'Düşenler: `autoRowSize` (mobil satır her zaman içeriğinin boyundadır), `size` (mobil kart hücresi çocuklarını büyütmez; 2026-09-30), `cardFill` (kart hücre boyuna gerilmez, içeriğinin boyundadır; 2026-10-14).'
272
329
  ],
273
330
  propNotes: {
274
331
  itemPressable: 'Satıra dokunulabilir yapar; dokunuş önce `change { selectedIndex }`, sonra `itemPress` yayar.',
@@ -536,7 +593,7 @@ export const mobileComponentKnowledge = {
536
593
  '`defaultView: month | week | day`. Ay: günde en çok `maxVisibleEvents` (varsayılan 3) nokta, fazlası `+`; seçili gün dolgulu, altında `dayCountLabel` sayacı (`{n} görev`); geçmiş günler `pastDimmed` ile soluk. Hafta: 7 günlük şerit, yatay kaydırma haftayı değiştirir. Gün: `startHour`–`endHour` saat satırları, çakışan bloklar yan yana, süresiz satır 30 dk, `doneField` doluysa blok soluk, bugünde "şimdi" çizgisi.',
537
594
  'MOBİLE ÖZGÜ: `pastDimmed`, `showTodayButton`, `legend` (açık `{label, color}` listesi — satırlardan türetilmez), `dayCountLabel`, `doneField`.',
538
595
  'Olaylar web sözleşmesiyle, yük `event.data`da (düğüme yazılmaz): `dateClick { date, rows }` (mobilde günün satırları da gelir — seçili günün görevlerini alttaki `repeater`a setter ile `event.data.rows` yazın), `eventClick { id, title, row }` (gün çizelgesinde), `monthChange { month, year, view, start, end }` (oklar, kaydırma ve Bugün — her görünümde), `viewChange { view, start, end }`.',
539
- 'Düşenler: `isEditable`/`isCreatable` ve `eventMove`/`eventCreate` (sürükleme jesti), `agendaDays` (liste görünümü `grouped-list` ile), `isLoading`.'
596
+ 'Düşenler: `isEditable`/`isCreatable` ve `eventMove`/`eventCreate` (sürükleme jesti), `agendaDays` (liste görünümü `grouped-list` ile), `isLoading`, `source` (süreç görevleri kaynağı mobilde yok; takvim yalnız `rows` çizer).'
540
597
  ],
541
598
  propNotes: {
542
599
  rows: 'Etkinlik satırları; genelde sorguya kod bağıyla: `{ type: \'code\', ref: \'{{ q_gorevler.data }}\' }`.',
@@ -549,12 +606,13 @@ export const mobileComponentKnowledge = {
549
606
  summary: 'Gruplu liste (FlutterFlow GroupedListView): düz `rows`u `groupBy` alanına göre bölümlere ayırır, her bölümü bir başlık ve öğeleriyle çizer. İç içe repeater (PAGE-013) yerine gün/kategori başlıklı liste budur. Web`de karşılığı YOKTUR.',
550
607
  differences: [
551
608
  'Çocuklar KONUMSAL şablondur: çocuk 0 bölüm başlığı, çocuk 1 öğe şablonu; fazlası çizilmez. Tasarım kanvasında iki şablon bir kez, alt alta çizilir.',
552
- 'Öğe repeater satırıyla AYNI params`ı alır (`index` = `rows` içindeki DÜZ indeks, `row`, `repeaterId`): bağlı prop`larda `Row`, `RowIndex`, `{{ item.<alan> }}` iki yönlü bağ; akışta tıklanan satır `<liste>.selectedItem`; satır kapsamlı yazım (`_repeaterValues`) repeater`daki gibi.',
609
+ 'Öğe repeater satırıyla AYNI params`ı alır (`index` = `rows` içindeki DÜZ indeks, `row`, `repeaterId`): bağlı prop`larda `Row`, `RowIndex`, `{{ item.<alan> }}` iki yönlü bağ; akışta tıklanan satır `<liste>.selectedItem`; satır kapsamlı yazım (`_repeaterValues`) repeater`daki gibi. Öğe kimlikleri repeater`daki gibi `__row-<i>` (düz indeks): `page_screenshot` öğeyi `<elementId>__row-<i>` ile tıklar, özet öğeleri sayar.',
553
610
  'Başlık ifadelerde `Section` okur: `{ key, title, count, items }` — ör. `{{ Section.title }} · {{ Section.count }}`. Başlık satır kapsamı TAŞIMAZ (salt okunur, `Row`/`item` boş). `Section` ayrılmış addır: aynı adlı komponent ifade bağlamından süzülür.',
554
611
  'Gruplama: tarih/saat değeri cihazın yerel gününe (`YYYY-AA-GG`) indirgenir, diğer değer metne; başlık = anahtar (biçimli başlık için `Section.key` ile ifade yazın). Alanı boş/eksik satırlar en sondaki `otherSectionTitle` bölümüne düşer.',
555
612
  'Yapışkan başlık (`stickySectionHeaders`) yalnız boyu kabuktan gelen listede çalışır ve cihazda/önizlemede SINIRLI boy ister: `appearance.heightType: fixed` + `heightString` verin. `auto`da liste düz akıştır; sayfanın kaydırıcısı içindeki `fill` içerik kadar uzar, iç kaydırıcı oluşmaz, başlık yapışmaz.',
556
613
  'Öğe şablonu basılabilir bir bileşense (`button`, `click`i bağlı `list-item`) dokunuş onun `click`ine gider ve `itemPress` yayılmaz (repeater ile aynı); `click`i bağlı olmayan `list-item` basılamaz, dokunuş `itemPress`e gider (2026-10-08). Akışı `list-item.click`e bağlarsanız satır seçimi yine olur.',
557
614
  'Çoklu seçim `repeater` ile AYNI sözleşmedir: `selectionMode: multiple` (onay kutusu, dokunuş seçer, `itemPress` yayılmaz), `longPressSelects`, `selectionChange { selectedIndices, count }`, düğümde `selectedIndices`/`selectedItems` — gruplu görev listesinde toplu Ertele/Tamamla.',
615
+ ROW_LIST_NESTING_NOTE,
558
616
  'v1`de yok: `columnCount`, sanallaştırma, başlıktan yazan akış. Studio`nun satır farkında yerleri (Bind modalı `Row` önerisi, setter satır hedefi) yalnız `repeater`ı tanır — `grouped-list` içinde `Row`/`item` ifadesini elle yazın.'
559
617
  ],
560
618
  propNotes: {
@@ -570,6 +628,46 @@ export const mobileComponentKnowledge = {
570
628
  longPressSelects: 'Uzun basış çoklu seçim kipini açar.'
571
629
  }
572
630
  },
631
+ 'flip-card': {
632
+ summary: 'İki yüzlü kart (FlutterFlow FlippableCard): dokununca 3B dönerek öteki yüzünü gösterir. Web`de karşılığı YOKTUR.',
633
+ differences: [
634
+ "children TAM iki `view` düğümüdür, bu sırayla: ön yüz, arka yüz (`repeater` gibi sabit görünüm; başka şekil 422 `fixed-views`). İçeriği görünümlerin İÇİNE koyun: `flip-card › view › …`. Görünümün çocukları dikey akar.",
635
+ 'Görünen yüz `isFlipped`tir (false ön, true arka). Karta dokunuş onu çevirir ve `flip { isFlipped }` yayar; akış da setter ile `isFlipped` yazabilir (o zaman `flip` yayılmaz). Koşul: `{{ <kart>.isFlipped }}`.',
636
+ 'Yüzün içindeki denetim (button, list-item, switch, metin alanı) dokunuşu kendisi alır, kart dönmez. Yalnız düğmeyle dönen kart: `flipOnPress: false` + düğmenin akışında setter `isFlipped: !<kart>.isFlipped`.',
637
+ 'Kartın boyu ÖN yüzün boyudur; arka yüz aynı kutuyu doldurur (daha uzun arka yüz taşar). İki yüz farklı boydaysa karta `appearance.heightType: fixed` verin.',
638
+ 'Dönüş `duration` ms sürer (100–2000, varsayılan 400), `direction: horizontal` düşey eksende, `vertical` yatay eksende. "Hareketi azalt"ta ve `page_screenshot`ın donuk karesinde yüz anında değişir.',
639
+ 'Tasarım kanvasında kart dönmez ve önizlemedeki TEK yüz çizilir (`isFlipped`); öteki yüz, içinden bir düğüm seçilince açılır. Görünmeyen yüz çalışırken dokunuş almaz.'
640
+ ],
641
+ propNotes: {
642
+ isFlipped: 'Açılışta görünen yüz: `false` ön (varsayılan), `true` arka. Basış ve akış bu alanı yazar.',
643
+ flipOnPress: 'Karta dokunuş çevirir mi (varsayılan açık). Kapalıyken yalnız `isFlipped` yazımı çevirir.',
644
+ direction: '`horizontal` (varsayılan; kart sağa-sola döner) · `vertical` (yukarı-aşağı).',
645
+ duration: 'Dönüşün süresi (ms), 100–2000; varsayılan 400.',
646
+ views: "Sabit iki görünüm: 'front' ve 'back'. Değer YAZILMAZ; görünümler children'daki iki view düğümüdür."
647
+ }
648
+ },
649
+ 'swipeable-stack': {
650
+ summary: 'Kaydırılarak atılan kart destesi (FlutterFlow SwipeableStack): `rows`un her satırı bir karttır, üstteki kart sola ya da sağa atılır. Web`de karşılığı YOKTUR.',
651
+ differences: [
652
+ "İki görünümlü satır listesidir (`repeater` ile aynı şekil): children = [view (kart şablonu, satır başına), view (boş görünüm, kart kalmayınca bir kez)]; başka şekil 422 `fixed-views`. Kart şablonu `repeater` satırının params`ını alır: bağlı prop`larda `{{ item.<alan> }}`, `Row`, `RowIndex`.",
653
+ 'Üstteki kart `currentIndex` satırıdır; arkasında en çok `visibleCount − 1` kart görünür (her biri `stackOffset` dp aşağıda ve %5 küçük). Kaydırma `currentIndex`i bir artırır — denetimlidir: akış setter ile `currentIndex` yazarak desteyi ilerletebilir ya da başa alabilir (`0`); o zaman kart animasyonsuz değişir ve olay yayılmaz.',
654
+ 'Kart genişliğin %30`undan uzağa çekilince ya da hızla savrulunca atılır, yoksa yerine döner. Sırayla: `change { selectedIndex }` → `swipeLeft` / `swipeRight` (yük `event.data`: `{ index, row, direction }`) → son satırsa `finish`. Akışta kaydırılan kart `event.data.row` ya da `<deste>.selectedItem`.',
655
+ '`swipeDirections: left | right` öteki yönü kapatır (kart o yöne dirençle çekilir ama atılmaz). Beğen/geç ayrımı iki olaya iki akış bağlanarak kurulur; yön tek akışta `event.data.direction`.',
656
+ '`loop` kapalıyken son karttan sonra boş görünüm çizilir (`currentIndex` = satır sayısı); açıkken deste başa döner. `rows` boşsa da boş görünüm çizilir.',
657
+ 'Jest yalnız YATAY harekette yakalanır: dikey kaydırma sayfada kalır, kart sürüklenirken sayfa kaymaz. Karttaki düğmeye dokunuş düğmenin akışını koşturur (önce `change { selectedIndex }`); düğmeden başlayan kaydırma düğmeyi tetiklemez.',
658
+ 'Destenin boyu ÜSTTEKİ kartın boyudur; kartlara aynı boyu verin (şablon kabına `heightType: fixed`), yoksa kart değiştikçe deste boy değiştirir. Tasarım kanvasında şablon bir kez çizilir, jest yoktur.',
659
+ `${ROW_LIST_NESTING_NOTE} v1\`de yok: dikey kaydırma yönleri, akıştan animasyonlu atma, sanallaştırma. Studio\`nun satır farkında yerleri (Bind modalı \`Row\` önerisi) yalnız \`repeater\`ı tanır — \`item\`/\`Row\` ifadesini elle yazın.`
660
+ ],
661
+ propNotes: {
662
+ rows: 'Düz satır dizisi (repeater `rows` ile aynı biçim); genellikle sorguya kod bağıyla: `{ type: \'code\', ref: \'{{ q_kartlar.data }}\' }`.',
663
+ currentIndex: 'Üstteki kartın `rows` içindeki indeksi (varsayılan 0). Kaydırma yazar; akış da yazabilir.',
664
+ visibleCount: 'Aynı anda çizilen kart sayısı, 1–5 (varsayılan 3).',
665
+ stackOffset: 'Arkadaki kartın derinlik başına aşağı kayması (dp), varsayılan 8; 0 kartları tam üst üste koyar.',
666
+ swipeDirections: '`both` (varsayılan) · `left` · `right`.',
667
+ loop: 'Son karttan sonra ilk kart gelir; `finish` yine son satırda yayılır.',
668
+ views: "Sabit iki görünüm: 'item' (kart şablonu) ve 'empty' (boş görünüm). Değer YAZILMAZ; görünümler children'daki iki view düğümüdür."
669
+ }
670
+ },
573
671
  'floating-container': {
574
672
  summary: 'Ekrana sabit kap: çocuklarını ekranın dokuz noktasından birine sabitler, sayfa kayarken yerinde kalır (FAB`dan fazlası: alt eylem çubuğu, üst şerit, köşe rozeti). Web`de karşılığı YOKTUR.',
575
673
  differences: [
@@ -630,13 +728,14 @@ export const mobileComponentKnowledge = {
630
728
  ]
631
729
  },
632
730
  video: {
633
- summary: 'Video oynatıcı; prop ve olaylar web ikizininki (`src`, `poster`, `autoplay`, `loop`, `muted`, `controls`; `play`/`pause`/`ended`/`click`).',
731
+ summary: 'Video oynatıcı; prop ve olaylar web ikizininki (`src`, `poster`, `autoplay`, `loop`, `muted`, `controls`, `ariaLabel`; `play`/`pause`/`ended`/`click`/`load`/`error`).',
634
732
  differences: [
635
733
  'Cihazda `expo-video` (denetimler platformun kendi oynatıcısı), Studio kanvası ve önizlemede HTML5 `<video>`. Kanvasta oynatılmaz ve dokunuş almaz — dokunuş seçim jestidir, denetimler yalnız görünür.',
636
734
  'Kutu: boyu kabuktan gelen video (`heightType: fixed` + ölçülebilir `heightString`, ya da `fill`) kabuğu doldurur; genişliği belli video (`widthType: fill`, ya da `fixed` + `widthString`) boyunu orandan alır — panelin `appearance.aspectRatio`su, yoksa 16:9; genişliği belli değilse boy 200 dp. Studio`dan bırakılan video `heightType: fixed` alır ama ölçüsü yoktur: 16:9 için `widthType: fill` verin. Video kutuyu `cover` ile doldurur (web ikizi gibi).',
637
735
  '`autoplay` yalnız oynatıcı kurulurken (sayfa açılışı, kaynak değişimi) uygulanır; tarayıcı sesli otomatik oynatmayı engelleyebilir (`muted` açın). `ended` `loop` açıkken yayılmaz. `click` yalnız `controls: false` iken yayılır — denetimler açıkken dokunuş oynatıcınındır.',
638
- 'Cihazda afiş yerel bir özellik değildir: `poster` ilk oynatmaya dek videonun üstünde çizilir ve denetimler açıkken ortasındaki oynat düğmesi oynatmayı başlatır. Düşen: `style`.',
639
- 'Cihazda sayfa odağı kaybedince (yığında arkaya düşünce, başka sekmeye geçince) video durur ve `pause` yayar; dönüşte kendiliğinden sürmez. Döngü sınırındaki ve ara belleğe almadaki kısa duruşlar `pause`/`play` yaymaz (web `<video loop>` gibi).'
736
+ 'Cihazda afiş yerel bir özellik değildir: `poster` ilk oynatmaya dek videonun üstünde çizilir ve denetimler açıkken ortasındaki oynat düğmesi oynatmayı başlatır. Erişilebilir ad (`ariaLabel`) cihazda yalnız `controls: false` iken okunur (oynatıcının ve `click` düğmesinin adı); denetimler açıkken yerel denetimler ekran okuyucudan gizlenmesin diye oynatıcı tek öğe yapılmaz. Düşen: `style`.',
737
+ 'Cihazda sayfa odağı kaybedince (yığında arkaya düşünce, başka sekmeye geçince) video durur ve `pause` yayar; dönüşte kendiliğinden sürmez. Döngü sınırındaki ve ara belleğe almadaki kısa duruşlar `pause`/`play` yaymaz (web `<video loop>` gibi).',
738
+ '`load` kaynak yüklenince `{ duration, width, height }` (cihazda boyut ilk video izinden; bilinmeyen değer 0), `error` `{ code, message }` yayar — cihazda `expo-video` hata kodu taşımaz, `code` 0. Oynatma niyeti (`isPlaying: true`) varken hata kolu `false`a geri yazar (web ikizi gibi). (2026-10-14).'
640
739
  ]
641
740
  },
642
741
  audio: {
@@ -828,8 +927,8 @@ export const mobileComponentKnowledge = {
828
927
  differences: [
829
928
  '`variant: table` (varsayılan) yatay kaydırılan tablodur: başlık satırı, sıralanabilir kolonda başlığa dokunuş (`<elementId>__sort-<kolon>`, yön oku başlıkta), eylem sütunu satırın sonunda; dikey kaydırma sayfanındır, tablonun üstünden başlayan dikey kaydırma sayfayı kaydırır; kolonların toplamı çerçeveden darsa kolonlar web gibi çerçeveye yayılır (`table-layout: fixed`). `variant: cards` satır başına kart çizer: ilk görünür görsel kolon kartın başına, ilk görsel olmayan kolon başlığa, kalanlar etiket-değer satırlarına; kart kipinde sıralama başlığı yoktur (sıralama akışla yazılır).',
830
929
  'Eylemler web paletiyle (`solid` birincil, `information` kenarlı, `transparent` hayalet, `error`/`danger` yıkıcı, `warning`, `dark`): `actionType: button` satırda düğmeler (`<elementId>__action-<satır>-<sıra>`), `dropdown` ⋯ düğmesi (`<elementId>__more-<satır>`) alttan açılan eylem sayfası (`<elementId>__sheet-action-<sıra>`; cihazda `modal` ile aynı katman). Satırın `_actions` dizisi web gibi eylemi ezer, `isHidden` gizler, `isDisabled` kilitler.',
831
- 'Sayfalama (`isPaginated`): ‹ `1–10 / 57` › çubuğu (`<elementId>__page-prev`, `__page-next`); metin dil bağımsızdır. Web`in sayfa numarası kutusu ve sayfa başına satır seçimi yok (`limit` panelden ya da akıştan; düğümde hiç yazılmamışsa web gibi 10). Başlık doluyken yanındaki yenile düğmesi (`<elementId>__refresh`) `mount`u koşturur.',
832
- 'Düşenler: `style`, `actionsPinned` (sabit eylem sütunu). Okunmayanlar: kolonların `isFilterable`ı ve gizli `filters` — filtre düzenleyicisi mobilde yok, akışın yazdığı `filters` sorguya olduğu gibi gider; özet satırı yok. Satır sanallaştırma yok: uzun veri için yol `limit`tir (100 satır × 6 kolon cihazda geliştirme kipinde ~0,7 sn`de çizildi).',
930
+ 'Sayfalama (`isPaginated`): ‹ `1–10 / 57` › çubuğu (`<elementId>__page-prev`, `__page-next`); metin dil bağımsızdır. Web`in sayfa numarası kutusu ve sayfa başına satır seçimi yok (`limit` panelden ya da akıştan; düğümde hiç yazılmamışsa web gibi 10). Başlık doluyken yanındaki yenile düğmesi (`<elementId>__refresh`) `mount`u koşturur; `showRefresh: false` düğmeyi kaldırır.',
931
+ 'Düşenler: `style`, `actionsPinned` (sabit eylem sütunu); satır içi düzenleme `editMode`, `canEditRow`, `canAddRow`, `addRowButton`, `addRowShortcut`, `canDeleteRow`, `bulkActions`, `canReorderRows`, `orderField` ve olayları `save`, `discard`, `cellSelectionChange`, `rowSelectionChange` (mobil tablo salt okunur; satır işlemi `actions` ile yapılır; ailenin kolon alanları columns[].editable · columns[].relationTypeField · columns[].relationTargets da düşer); filtre arayüzü `filterMode`, `advancedFilter`, `filterPosition`, `showFilterSummary`; araçlar ve yerleşim `showExport`, `showColumnManager`, `toolsPosition`, `footerPosition`, `showRecordSummary`, `stickyHeader`, `stickyFooter`; sayfalama biçimi `paginationMode`, `paginationPosition`; satır dokunuşu `rowClick`, `rowDoubleClick` (satıra dokunuş olay yaymaz); `revealEntity`. Okunmayanlar: kolonların `isFilterable`ı ve gizli `filters` — filtre düzenleyicisi mobilde yok, akışın yazdığı `filters` sorguya olduğu gibi gider; özet satırı yok. Satır sanallaştırma yok: uzun veri için yol `limit`tir (100 satır × 6 kolon cihazda geliştirme kipinde ~0,7 sn`de çizildi).',
833
932
  'Hücre: metin tablo kipinde tek satırda kırpılır (`multiLine` ise `lineClamp` kadar satır), kart kipinde sarar; etiket rengi temadan çözülür, üstündeki metin koyu/açık seçilir; `image` kolonu `imageHeight` karesi; `file` ve `url` dokunuşla sistemde açılır (kanvasta kilitli); `progress` her zaman çubuktur (daire kipi çubuk çizilir). Yükleniyor (`isLoading`) satırların yerine ortada dönen gösterge; satır yokken gövde web`in boş satırı kadar (120) boş kalır, ileti yazılmaz. Bırakılamaz — web ikizi bırakılabilir bildirir ama çocuk çizmez.'
834
933
  ],
835
934
  propNotes: {
@@ -862,7 +961,7 @@ export const mobileComponentKnowledge = {
862
961
  differences: [
863
962
  'Dikey tek düzen. Satırlar `groupByDate` (MOBİLE ÖZGÜ, varsayılan açık) ile gün başlıklarına ayrılır ("Bugün", "Dün", yoksa `dateFormat`); kapalıyken tek liste. `items[].images` (MOBİLE ÖZGÜ): adres dizisi (`["a.jpg", "b.jpg"]`, statik satırda da kabul edilir — ARC-152) ya da virgülle ayrılmış metin (`"a.jpg, b.jpg"`); bağla gelen satırda `[{ src }]` de okunur. Satırın altında küçük resimler.',
864
963
  'Satıra dokunuş `clickItem { currentItem, index }` yayar; öğenin kendi `flow`u da koşar (web: bağlı akış önce başlatılır). `maxCount` aşılınca "Tümünü göster" (`<elementId>__show-more`) kalanı açar ve `showMore` yayar. `showMoreText`/`emptyText` yazılmamışsa uygulama dilinden gelir ("Tümünü göster" / "Henüz kayıt yok"). Satır iç rolü `<elementId>__item-<index>`.',
865
- 'Düşenler: `orientation` (mobil yalnız dikey), `style`.'
964
+ 'Düşenler: `orientation` (mobil yalnız dikey), `style`, `isClickable` (satır, düğümün `clickItem`ı bağlıysa kendiliğinden basılır).'
866
965
  ]
867
966
  },
868
967
  'photo-viewer': {
@@ -234,6 +234,47 @@ export const mobileComponentEventContracts = {
234
234
  'floating-container': {
235
235
  mount: NO_DATA
236
236
  },
237
+ 'flip-card': {
238
+ mount: NO_DATA,
239
+ flip: {
240
+ payload: {
241
+ isFlipped: 'boolean — YENİ durum: true arka yüz, false ön yüz'
242
+ },
243
+ reads: "karta dokununca (flipOnPress açıkken); makine isFlipped'i düğüme yazar, akış <kart>.isFlipped okur. Akışın isFlipped'e yazması kartı çevirir ama flip yaymaz. Yüzün içindeki basılabilir bileşene (button, list-item) dokunuş kartı çevirmez"
244
+ }
245
+ },
246
+ 'swipeable-stack': {
247
+ mount: NO_DATA,
248
+ change: {
249
+ payload: {
250
+ selectedIndex: "number — kaydırılan (ya da içindeki bir bileşenine dokunulan) kartın rows içindeki indeksi; aynı kartta yeniden yayılmaz. Makine selectedIndex'i yazar, akışta kart <deste>.selectedItem"
251
+ }
252
+ },
253
+ swipeLeft: {
254
+ payload: {
255
+ index: 'number (kaydırılan kartın rows içindeki indeksi, 0 tabanlı)',
256
+ row: 'object (kaydırılan kartın satır verisi)',
257
+ direction: "'left'"
258
+ },
259
+ reads: "self'e yazılmaz — event.data ile oku. Olaydan önce change { selectedIndex } yayılır ve düğümün currentIndex'i bir sonraki karta yazılır (<deste>.currentIndex yeni üst karttır)"
260
+ },
261
+ swipeRight: {
262
+ payload: {
263
+ index: 'number (kaydırılan kartın rows içindeki indeksi, 0 tabanlı)',
264
+ row: 'object (kaydırılan kartın satır verisi)',
265
+ direction: "'right'"
266
+ },
267
+ reads: "self'e yazılmaz — event.data ile oku. Olaydan önce change { selectedIndex } yayılır ve düğümün currentIndex'i bir sonraki karta yazılır (<deste>.currentIndex yeni üst karttır)"
268
+ },
269
+ finish: {
270
+ payload: {
271
+ index: 'number (son kartın rows içindeki indeksi)',
272
+ row: 'object (son kartın satır verisi)',
273
+ direction: "'left' | 'right'"
274
+ },
275
+ reads: "self'e yazılmaz — event.data ile oku. rows'un SON satırı kaydırılınca, yön olayından sonra; loop açıkken de yayılır (deste başa döner)"
276
+ }
277
+ },
237
278
  repeater: {
238
279
  swipe: {
239
280
  payload: {
@@ -0,0 +1,252 @@
1
+ /**
2
+ * Mobil `motionStyle` grubunun gateway aynası: alan tablosu ve çözücü. Kaynak frontend'dedir
3
+ * (`zeyrek-frontend/packages/ng-apps-ui-native/src/motion/` — `motion-fields.ts`, `motion-presets.ts`,
4
+ * `resolve-motion.ts`); bu dosya onun kopyasıdır ve iki kilitle bağlıdır (`test/motion-resolve.test.ts`):
5
+ * frontend'in altın vakaları (`motion-golden.json`) aynı çıktıyı vermeli, mobil katalogdaki grubun alanları ve
6
+ * seçenekleri bu tabloyla aynı olmalı. Test kırılınca test düzeltilmez, bu dosya senkronlanır.
7
+ *
8
+ * Çözüm reanimated'in girdisinin serileştirilebilir tanımıdır: giriş/çıkış/yerleşim oluşturucu adıyla,
9
+ * döngü ve değer geçişi CSS API alanlarıyla (frontend ADR 0006).
10
+ */
11
+ export const MOTION_ENTER = ['none', 'fade', 'fadeUp', 'fadeDown', 'slideLeft', 'slideRight', 'zoomIn'];
12
+ export const MOTION_EXIT = ['none', 'fade', 'fadeUp', 'fadeDown', 'slideLeft', 'slideRight', 'zoomOut'];
13
+ export const MOTION_EASING = ['ease', 'easeIn', 'easeOut', 'easeInOut', 'linear'];
14
+ export const MOTION_LOOP = ['none', 'pulse', 'spin', 'bounce'];
15
+ export const MOTION_TRANSITION = ['none', 'colors', 'opacity', 'transform', 'all'];
16
+ export const MOTION_PRESS = ['none', 'scale', 'opacity'];
17
+ /**
18
+ * Grubu TAŞIMAYAN mobil tipler: görünen içeriğini düğüm kabuğunun dışında çizerler (katman, bildirim, ekran katmanına
19
+ * taşınan), kabukları çalışma anında boş kutudur — frontend `catalog-schema.ts` `DRAWN_OUTSIDE_SHELL`. Katalogla
20
+ * kilitli (`test/motion-resolve.test.ts`).
21
+ */
22
+ export const MOTION_OUTSIDE_SHELL = ['drawer', 'fab', 'floating-container', 'modal', 'photo-viewer', 'toast'];
23
+ /** ms cinsinden kabul aralıkları. */
24
+ export const MOTION_RANGES = {
25
+ duration: [100, 2000],
26
+ delay: [0, 5000]
27
+ };
28
+ /** Alan düğümde yokken çizerin okuduğu değerler; şema bunları düğüme YAZMAZ (grubun kayıtlı varsayılanı yoktur). */
29
+ export const MOTION_DEFAULTS = {
30
+ enter: 'none',
31
+ exit: 'none',
32
+ duration: 300,
33
+ delay: 0,
34
+ easing: 'easeOut',
35
+ layout: false,
36
+ loop: 'none',
37
+ transition: 'none',
38
+ press: 'none'
39
+ };
40
+ export const MOTION_FIELDS = Object.keys(MOTION_DEFAULTS);
41
+ /** Seçenekli alanların değer kümeleri; katalog paritesi ve sözlük notu buradan okur. */
42
+ export const MOTION_CHOICES = {
43
+ enter: MOTION_ENTER,
44
+ exit: MOTION_EXIT,
45
+ easing: MOTION_EASING,
46
+ loop: MOTION_LOOP,
47
+ transition: MOTION_TRANSITION,
48
+ press: MOTION_PRESS
49
+ };
50
+ const DECIMAL_TEXT = /^\d+(\.\d+)?$/;
51
+ const isEmpty = (raw) => raw === undefined || raw === null || (typeof raw === 'string' && raw.trim() === '');
52
+ /**
53
+ * Alanın değeri. Boş değer ne değer ne uyarıdır (alan yazılmamış). Okunamayan değer `value` taşımaz; uyarı
54
+ * frontend'inkiyle harfi harfine aynıdır (altın vaka onu karşılaştırır).
55
+ */
56
+ export const motionValue = (field, raw) => {
57
+ if (isEmpty(raw))
58
+ return {};
59
+ const reject = (expected) => ({
60
+ warning: `motionStyle.${field}: ${typeof raw === 'string' ? `'${raw}'` : JSON.stringify(raw)} geçersiz — ${expected}, atlandı`
61
+ });
62
+ const name = field;
63
+ if (name === 'duration' || name === 'delay') {
64
+ const [min, max] = MOTION_RANGES[name];
65
+ // Metin yalnız ondalık yazımla sayıdır: `Number()` üstel ('1e3') ve onaltılık ('0x1F4') metni de sayıya çevirir.
66
+ const number = typeof raw === 'number' ? raw : typeof raw === 'string' && DECIMAL_TEXT.test(raw.trim()) ? Number(raw) : NaN;
67
+ return Number.isFinite(number) && number >= min && number <= max
68
+ ? { value: number }
69
+ : reject(`${min}…${max} arası sayı (ms)`);
70
+ }
71
+ if (name === 'layout') {
72
+ if (raw === true || raw === 'true')
73
+ return { value: true };
74
+ if (raw === false || raw === 'false')
75
+ return { value: false };
76
+ return reject('true ya da false');
77
+ }
78
+ const choices = MOTION_CHOICES[name] ?? [];
79
+ return typeof raw === 'string' && choices.includes(raw)
80
+ ? { value: raw }
81
+ : reject(choices.join(', '));
82
+ };
83
+ const ENTER_PRESETS = {
84
+ fade: 'FadeIn',
85
+ fadeUp: 'FadeInUp',
86
+ fadeDown: 'FadeInDown',
87
+ slideLeft: 'SlideInLeft',
88
+ slideRight: 'SlideInRight',
89
+ zoomIn: 'ZoomIn'
90
+ };
91
+ const EXIT_PRESETS = {
92
+ fade: 'FadeOut',
93
+ fadeUp: 'FadeOutUp',
94
+ fadeDown: 'FadeOutDown',
95
+ slideLeft: 'SlideOutLeft',
96
+ slideRight: 'SlideOutRight',
97
+ zoomOut: 'ZoomOut'
98
+ };
99
+ const LAYOUT_PRESET = 'LinearTransition';
100
+ const EASING_CSS = {
101
+ ease: 'ease',
102
+ easeIn: 'ease-in',
103
+ easeOut: 'ease-out',
104
+ easeInOut: 'ease-in-out',
105
+ linear: 'linear'
106
+ };
107
+ /** Döngülerin kendi süresi ve eğrisi vardır; düğümde `duration`/`easing` YAZILMAMIŞSA bunlar geçer, 300 ms değil. */
108
+ const LOOP_PRESETS = {
109
+ spin: {
110
+ keyframes: { from: { transform: [{ rotate: '0deg' }] }, to: { transform: [{ rotate: '360deg' }] } },
111
+ duration: 1000,
112
+ easing: 'linear'
113
+ },
114
+ pulse: {
115
+ keyframes: { '0%': { opacity: 1 }, '50%': { opacity: 0.5 }, '100%': { opacity: 1 } },
116
+ duration: 2000,
117
+ easing: 'cubic-bezier(0.4, 0, 0.6, 1)'
118
+ },
119
+ bounce: {
120
+ keyframes: {
121
+ '0%': { transform: [{ translateY: '-25%' }], animationTimingFunction: 'cubic-bezier(0.8, 0, 1, 1)' },
122
+ '50%': { transform: [{ translateY: '0%' }], animationTimingFunction: 'cubic-bezier(0, 0, 0.2, 1)' },
123
+ '100%': { transform: [{ translateY: '-25%' }], animationTimingFunction: 'cubic-bezier(0.8, 0, 1, 1)' }
124
+ },
125
+ duration: 1000,
126
+ easing: 'linear'
127
+ }
128
+ };
129
+ const TRANSITION_PROPERTIES = {
130
+ colors: ['backgroundColor', 'borderColor', 'color'],
131
+ opacity: ['opacity'],
132
+ transform: ['transform'],
133
+ all: 'all'
134
+ };
135
+ const PRESS_PRESETS = {
136
+ scale: { property: 'scale', to: 0.95 },
137
+ opacity: { property: 'opacity', to: 0.7 }
138
+ };
139
+ /** Dokunma tepkisinin süresi kendinindir; düğümün süre/gecikme/eğrisi ona geçmez. */
140
+ const PRESS_TIMING = { duration: 150, easing: 'easeOut' };
141
+ const isActive = (settings) => settings.enter !== 'none' ||
142
+ settings.exit !== 'none' ||
143
+ settings.layout ||
144
+ settings.loop !== 'none' ||
145
+ settings.transition !== 'none' ||
146
+ settings.press !== 'none';
147
+ /**
148
+ * Frontend `resolveMotion` ile aynı kurallar: dondurma önce gelir (tasarım kipi ya da "hareketi azalt" → `null`),
149
+ * hiçbir hareket alanı dolu değilse `null` (yalnız süre yazılmış ayar hareket değildir), okunamayan alan ve
150
+ * tanınmayan anahtar atlanıp `warnings`'e yazılır.
151
+ */
152
+ export const resolveMotion = (input, context) => {
153
+ if (context.design || context.reduceMotion)
154
+ return null;
155
+ if (!input || typeof input !== 'object' || Array.isArray(input))
156
+ return null;
157
+ const warnings = [];
158
+ const settings = { ...MOTION_DEFAULTS };
159
+ const given = new Set();
160
+ for (const [key, raw] of Object.entries(input)) {
161
+ if (!MOTION_FIELDS.includes(key)) {
162
+ warnings.push(`motionStyle.${key}: tanınmayan alan, atlandı`);
163
+ continue;
164
+ }
165
+ const field = key;
166
+ const read = motionValue(field, raw);
167
+ if (read.warning)
168
+ warnings.push(read.warning);
169
+ if (read.value === undefined)
170
+ continue;
171
+ Object.assign(settings, { [field]: read.value });
172
+ given.add(field);
173
+ }
174
+ // Hareket çıkmadıysa sarmalayıcı takılmaz; atlanan alanın uyarısı yine de döner (yalnız `warnings` taşıyan sonuç).
175
+ if (!isActive(settings))
176
+ return warnings.length > 0 ? { warnings } : null;
177
+ const result = { warnings };
178
+ const timing = { duration: settings.duration, delay: settings.delay, easing: settings.easing };
179
+ if (settings.enter !== 'none')
180
+ result.entering = { preset: ENTER_PRESETS[settings.enter], ...timing };
181
+ if (settings.exit !== 'none')
182
+ result.exiting = { preset: EXIT_PRESETS[settings.exit], ...timing };
183
+ if (settings.layout)
184
+ result.layout = { preset: LAYOUT_PRESET, duration: settings.duration, easing: settings.easing };
185
+ if (settings.press !== 'none')
186
+ result.press = { ...PRESS_PRESETS[settings.press], ...PRESS_TIMING };
187
+ const style = {};
188
+ if (settings.loop !== 'none') {
189
+ const loop = LOOP_PRESETS[settings.loop];
190
+ Object.assign(style, {
191
+ animationName: loop.keyframes,
192
+ animationDuration: `${given.has('duration') ? settings.duration : loop.duration}ms`,
193
+ animationDelay: `${settings.delay}ms`,
194
+ animationTimingFunction: given.has('easing') ? EASING_CSS[settings.easing] : loop.easing,
195
+ animationIterationCount: 'infinite'
196
+ });
197
+ }
198
+ if (settings.transition !== 'none') {
199
+ Object.assign(style, {
200
+ transitionProperty: TRANSITION_PROPERTIES[settings.transition],
201
+ transitionDuration: `${settings.duration}ms`,
202
+ transitionDelay: `${settings.delay}ms`,
203
+ transitionTimingFunction: EASING_CSS[settings.easing]
204
+ });
205
+ }
206
+ if (Object.keys(style).length > 0)
207
+ result.style = style;
208
+ return result;
209
+ };
210
+ /*
211
+ * Buradan aşağısı AYNA DEĞİLDİR: gateway'in saklanan biçimi (değer sarmalları) okumasıdır.
212
+ */
213
+ /**
214
+ * Statik olmayan değer sarmalları — `normalize.ts` `PROP_VALUE_TYPES`'ın `static` dışındakileri. O modül bu dosyayı
215
+ * (katalog notları üzerinden) içe aktarır; döngü olmasın diye liste burada yinelenir, `motion-resolve.test.ts` kilitler.
216
+ */
217
+ export const MOTION_BINDING_TYPES = ['file', 'flow', 'widget', 'element', 'page', 'code', 'translation', 'connectData'];
218
+ const TEMPLATE_TEXT = /\{\{[\s\S]*\}\}/;
219
+ /**
220
+ * Grubun İÇİNDEKİ tek alanın ifadeye bağı; bağ yoksa `null`. `code` sarmalı çalışma anında DEĞERLENDİRİLİR (frontend
221
+ * ADR 0020; ölçüldü 2026-10-09, web `/dev` ve mobil oyun alanı — ondan önce hiçbir şey uygulamıyordu): uyarı değildir,
222
+ * değeri burada bilinemez. Alan düzeyinde çözülmeyen iki yazım kalır: sarmalsız düz metindeki `{{ … }}` (üst düzey
223
+ * prop'ta da ifade değildir) ve `code` dışındaki bağ türleri. Çözücü TANIMADIĞI alanı bağlı olsa da atlar: orada bağın
224
+ * türü değil alanın adı söylenir (yazım hatası `looop` ifadeye bağlıyken sessiz geçiyordu — inceleme 2026-10-09). `ref`'i
225
+ * boş `code` sarmalı ifade değildir: çizer sarmalın verisini ham değer okur, bağ toplamaz. Rapor
226
+ * (`nativeStyle.motion`) ve yazım denetimi (MOB-028) bu ayrımı buradan okur.
227
+ */
228
+ export const motionFieldBinding = (field, raw) => {
229
+ const wrapper = typeof raw === 'object' && raw !== null && !Array.isArray(raw) ? raw : undefined;
230
+ const bound = wrapper !== undefined && typeof wrapper.type === 'string' && MOTION_BINDING_TYPES.includes(wrapper.type);
231
+ // Statik sarmal: tasarım şemasında `type: 'static'`, yayınlanan sayfanın yükünde `type` yok ya da `null`.
232
+ const fixed = wrapper !== undefined && (wrapper.type === 'static' || ((wrapper.type ?? null) === null && 'data' in wrapper));
233
+ const inner = fixed ? wrapper.data : raw;
234
+ if (!bound && !(typeof inner === 'string' && TEMPLATE_TEXT.test(inner)))
235
+ return null;
236
+ if (!MOTION_FIELDS.includes(field))
237
+ return { kind: 'unresolved', warning: `motionStyle.${field}: tanınmayan alan, atlandı` };
238
+ if (!bound) {
239
+ return { kind: 'unresolved', warning: `motionStyle.${field}: '{{ … }}' düz metindir, ifade olarak çalışmaz — alan yok sayılır; ifade için alanı { type: 'code', ref } sarmalıyla yazın` };
240
+ }
241
+ const candidate = wrapper?.data;
242
+ const fallback = isEmpty(candidate) || (typeof candidate === 'string' && TEMPLATE_TEXT.test(candidate)) ? undefined : candidate;
243
+ const carried = fallback === undefined ? {} : { fallback };
244
+ const outcome = fallback === undefined ? 'alan yok sayılır' : `sarmalın verisi (${typeof fallback === 'string' ? `'${fallback}'` : JSON.stringify(fallback)}) ham değer olarak okunur`;
245
+ if (wrapper?.type === 'code') {
246
+ if (typeof wrapper.ref === 'string' && wrapper.ref !== '')
247
+ return { kind: 'expression', ...carried };
248
+ // Çizer ifadesi boş sarmalı bağ saymaz (`inner-bindings.ts` `isCodeBinding`; panelde fx açık, metin silinmiş).
249
+ return { kind: 'unresolved', ...carried, warning: `motionStyle.${field}: 'code' sarmalının ifadesi (ref) boş, bağ değildir — ${outcome}` };
250
+ }
251
+ return { kind: 'unresolved', ...carried, warning: `motionStyle.${field}: alan düzeyinde yalnız ifade (code) çalışır — '${String(wrapper?.type)}' bağı çözülmez, ${outcome}` };
252
+ };