@appaflytech/zeyrek-ai-mcp 0.0.20 → 0.0.22
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.
- package/README.md +14 -13
- package/dist/knowledge/componentCatalog.js +937 -212
- package/dist/knowledge/componentEvents.js +366 -66
- package/dist/knowledge/mobileComponentCatalog.js +213 -1
- package/dist/knowledge/mobileComponentEvents.js +48 -0
- package/dist/knowledge/normalize.js +55 -11
- package/dist/knowledge/platform/mobileRules.js +7 -5
- package/dist/knowledge/rules.js +21 -0
- package/dist/platform/diagnostics.js +85 -0
- package/dist/platform/ifExists.js +129 -0
- package/dist/platform/responses.js +2 -1
- package/dist/platform/strictArguments.js +22 -2
- package/dist/platform/writeDiff.js +58 -31
- package/dist/platform/writeView.js +17 -0
- package/dist/resources/guides/component.md +9 -1
- package/dist/resources/guides/functions.md +31 -2
- package/dist/resources/guides/getting-started.md +35 -0
- package/dist/resources/guides/mobile.md +51 -2
- package/dist/resources/guides/page.md +34 -16
- package/dist/resources/guides/translation.md +5 -6
- package/dist/resources/schema-fingerprint.json +2 -2
- package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +18 -0
- package/dist/tools/designSchemaPatch.js +9 -5
- package/dist/tools/flows.js +36 -42
- package/dist/tools/functions.js +20 -10
- package/dist/tools/microservices.js +39 -11
- package/dist/tools/mobile.js +1 -1
- package/dist/tools/pages.js +65 -17
- package/dist/tools/schemaOps.js +2 -2
- package/dist/tools/workflows.js +39 -12
- package/package.json +7 -2
|
@@ -146,6 +146,18 @@ export const mobileComponentKnowledge = {
|
|
|
146
146
|
'Seçenekler normalize edilir: düz metin dizisi, nesne dizisi ve `optionType: "grouped"` çocukları tek biçime iner; grup başlığı seçilemez, etiketi olmayan seçenek değerini gösterir.'
|
|
147
147
|
]
|
|
148
148
|
},
|
|
149
|
+
'language-selector': {
|
|
150
|
+
summary: 'Dil değiştirici; `dropdown`un tam ekran `Modal` listesiyle çizilir, seçim uygulama çapında kalıcı olur.',
|
|
151
|
+
differences: [
|
|
152
|
+
"Çizim `dropdown`un kendisidir (plans/mobil-coklu-dil K4): tetikleyici `role=\"combobox\"` (iç rol `<elementId>__trigger`), liste tam ekran `Modal`da `role=\"option\"` + `data-value` satırları; temizleme düğmesi YOK (dil boş olamaz). `page_screenshot` `select: '<elementId>', value: 'en'` ile seçilir, seçenek metni dilin yerel adıdır (`LANGUAGE_NAMES`: `tr` → Türkçe, `en` → English).",
|
|
153
|
+
"Diller ve aktif dil makineden enjekte edilir (web `RenderComponent` ile aynı): `languages` = projenin dil listesi (`project_update(languages)`), `value` = `machine.getLanguage()`. Seçim `machine.setLanguage(value)` ile bütün düğümlere `reload` yayar (çeviriye bağlı prop'lar yerinde çözülür), sonra `change { value, selectedLabel }` sayfa akışına gider.",
|
|
154
|
+
"Seçim makineye VERİLEN depoya `zeyrek:<app>:lang` anahtarıyla yazılır: `/preview` ve `/dev|/view`'da tarayıcı `localStorage`, cihazda uygulamanın kv-store'u — uygulama yeniden açılınca o dille başlar. Cihazda barındırıcı seçimi uygulama çapında yayar (`onLanguageChange`): öteki sekmelerin ekranları ve sekme etiketleri de değişir; `?lang` yalnız web barındırıcısında vardır.",
|
|
155
|
+
"Projede dil tanımlı değilse (`languages` boş) liste boş çizilir — önce `project_update(languages, defaultLanguage)`; `project_get` yanıtındaki `localization` bloğu runtime'ın çevirileri çözüp çözemeyeceğini söyler."
|
|
156
|
+
],
|
|
157
|
+
propNotes: {
|
|
158
|
+
label: 'Alan etiketi (`FieldShell`); boşsa yalnız tetikleyici çizilir.'
|
|
159
|
+
}
|
|
160
|
+
},
|
|
149
161
|
divider: {
|
|
150
162
|
summary: 'Çizgi; etiket verilirse çizgi etiketin iki yanına bölünür.',
|
|
151
163
|
differences: [
|
|
@@ -305,7 +317,7 @@ export const mobileComponentKnowledge = {
|
|
|
305
317
|
differences: [
|
|
306
318
|
'`marks`, `showTicks` ve `autofocus` DÜŞÜRÜLDÜ.',
|
|
307
319
|
'Jest `PanResponder` ile çözülür ve YALNIZ YATAY sürüklemeyi yakalar: dokunuşu başlangıçta yakalasaydı sayfa kaydırması değeri değiştirirdi (ölçüldü — kaydırma 40`ı 100 yapıyordu).',
|
|
308
|
-
'Sürüklerken `change`, parmak kalkınca `changed` yayılır; akış genelde `changed`e bağlanır.'
|
|
320
|
+
'Sürüklerken `change`, parmak kalkınca `changed` yayılır; akış genelde `changed`e bağlanır. İze dokunuş değeri o noktaya getirir (`change` + `changed`). Jest `modal`/çekmece içinde de çalışır.'
|
|
309
321
|
]
|
|
310
322
|
},
|
|
311
323
|
tabs: {
|
|
@@ -327,5 +339,205 @@ export const mobileComponentKnowledge = {
|
|
|
327
339
|
propNotes: {
|
|
328
340
|
side: 'Balonun tetikleyiciye göre yanı. Seçilen yan ekrana sığmazsa balon ÇEVRİLMEZ, yalnız ekran içine kaydırılır.'
|
|
329
341
|
}
|
|
342
|
+
},
|
|
343
|
+
markdown: {
|
|
344
|
+
summary: 'Markdown metnini çizer: başlık, paragraf, liste, bağlantı, kod, alıntı, ayraç, tablo, görsel. Web`de karşılığı YOKTUR — web `html` ayrı bileşendir ve HTML çalıştırır.',
|
|
345
|
+
differences: [
|
|
346
|
+
'Metin `marked` ile ayrıştırılır ve bileşenin kendi ilkelleriyle çizilir: HTML üretilmez, hiçbir şey çalıştırılmaz. Satır içi HTML etiketi düşer (içindeki metin kalır), blok HTML etiketsiz metne iner; `&` gibi varlıklar çözülür — kod aralığında çözülmez, orada ham metindir.',
|
|
347
|
+
'HTML gibi: yumuşak satır sonu boşluktur ve ardışık boşluklar teke iner, yalnız sert kırılma (satır sonunda iki boşluk) satır kırar. GFM görev maddesi (`- [ ]`, `- [x]`) nokta yerine ☐/☑ çizilir (sıralı listede numaranın yanında); sıralı liste yazıldığı sayıdan başlar (`0.` dahil).',
|
|
348
|
+
'Başlık puntoları `text` bileşeninin tip ölçeğinden (`h1`–`h4`; 5 ve 6 `h4` puntosunda), renkler token sınıflarından: kod `--muted` zemin, bağlantı `--primary`, alıntı ve tablo `--border` çizgi. Düğümün `typographyStyle`ı her metne iner; puntosu ve satır yüksekliği GÖVDE metnindir, başlık kendi puntosunu korur.',
|
|
349
|
+
'Görsel yalnız tek başına bir paragrafta ve http(s) adresiyle çizilir (tam genişlik, 180 dp yükseklik, `contain`); satır içindeki görsel alt metnine iner.',
|
|
350
|
+
'Bağlantıya dokunuş `linkClick { href }` yayar (ham adres); `linkOpensExternal` açıkken (varsayılan) yalnız http(s), mailto ve tel adresi sistem tarayıcısında açılır — `javascript:` ya da göreli adres açılmaz ama olay yine gelir, gezinmeye akış karar verir. Tasarım kipinde bağlantı basılamaz; boş içerik tasarımda yer tutucu, runtime`da hiçbir şey çizer.'
|
|
351
|
+
],
|
|
352
|
+
propNotes: {
|
|
353
|
+
content: "Markdown metni (CommonMark + GFM tablo ve üstü çizili); bağlanabilir: `{ type: 'code', ref: '{{ ... }}' }`.",
|
|
354
|
+
linkOpensExternal: 'false iken bağlantı yalnız `linkClick` yayar, hiçbir şey açmaz — gezinmeyi akış yapar (ör. `redirect`).'
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
calendar: {
|
|
358
|
+
summary: 'Satır içi ay takvimi: tek gün ya da aralık seçer. Web`de katalog bileşeni DEĞİLDİR (web `calendar` yalnız `datepicker`ın açılır ızgarası); ızgara kuralı onunla aynı modülden.',
|
|
359
|
+
differences: [
|
|
360
|
+
'Prop adları web `datepicker`ınkiler (`value`, `minDate`, `maxDate`, `locale`) ve `change` yükü onun biçimidir (`yyyy-MM-ddT00:00:00`, yerel duvar saati, dilim eki yok) — iki bileşen akışlarda birbirinin yerine geçer. Gelen değer web `datepicker`ın `toDate` kuralıyla okunur: yalın `yyyy-MM-dd` yerel gündür; dilim taşıyan metin (`…Z`, `+00:00` — platform statik tarih değerini UTC`ye çevirip kaydeder) yerel güne çevrilir; sayı zaman damgası da okunur.',
|
|
361
|
+
'Izgara altı haftadır (42 gün); hafta Türkçede Pazartesi, İngilizcede Pazar başlar. `locale` boşsa uygulamanın dili; Türkçe olmayan dil İngilizceye, bilinmeyen dil Türkçeye düşer.',
|
|
362
|
+
'İç roller: `<elementId>__prev` ve `<elementId>__next` (ay), `<elementId>__day-yyyy-MM-dd` (gün) — `page_screenshot` `click` onlarla basar. Ay dışındaki güne basış o aya da geçer.',
|
|
363
|
+
'Salt okunurken aylar gezilir ama gün seçilmez; pasifken ikisi de kapalı. Değer akıştan başka bir aya yazılınca takvim o aya döner. Tasarım kipinde statik ay çizilir, basılamaz.'
|
|
364
|
+
],
|
|
365
|
+
propNotes: {
|
|
366
|
+
mode: "`single` (varsayılan) ya da `range`. Aralıkta ilk basış başlangıç (`value`), ikinci basış bitiş (`endValue`); başlangıçtan önceki gün ya da tamamlanmış aralığa basış yeni aralık başlatır. `change` iki alanı birlikte yazar.",
|
|
367
|
+
endValue: "Aralığın bitişi; yalnız `mode: 'range'` iken okunur ve panelde görünür.",
|
|
368
|
+
minDate: 'Sınırdan önceki günler soluk ve basılamaz, sınırın kendisi seçilebilir; `maxDate` simetrik.',
|
|
369
|
+
markedDates: "İşaretli günler: `[{ date: 'yyyy-MM-dd' }]` (düz tarih metni dizisi de okunur) — günün altında nokta; seçili günde nokta zıt renktedir."
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
otpinput: {
|
|
373
|
+
summary: 'Tek kullanımlık kod kutuları; kurallar web ile AYNI modülden (`otp-logic`): temizleme, tamamlanma, ayraç, yeniden gönder sayacı.',
|
|
374
|
+
differences: [
|
|
375
|
+
'Web her kutuyu ayrı alan yapar; mobilde yazıyı kutuların üstündeki TEK görünmez alan alır (`<elementId>__input`): yapıştırma, SMS`ten otomatik doldurma (`oneTimeCode`) ve silme klavyenin kendi davranışıdır, kutular yalnız gösterir. Alan uzunluk sınırı taşımaz — ayraçlı yapıştırma (`123-456`) temizlenip kutu sayısına kesilir, rakam kaybolmaz — ve imleç hep sondadır. `page_screenshot` `set` o alana yazar.',
|
|
376
|
+
'`change` ve `complete` web gibi yalnız GERÇEK değişimde yayılır (`complete` kodu dolduran değişimle birlikte); kabul edilmeyen karakter ve fazlası düşer.',
|
|
377
|
+
'Kilitler web ile aynı: pasif, salt okunur, görünüm ve yükleniyor kipleri yazmayı kapatır; görünüm kipinde kutular düz metne iner ve yeniden gönder çizilmez. Sayaç bağlanınca başlar, `<elementId>__resend` basınca `resend` yayar ve yeniden başlar; tasarım kipinde sayaç koşmaz.',
|
|
378
|
+
'`style` düşer (ham CSS); kutu ölçüsü form yüksekliği, punto form puntosunun 1,2 katı, boşluk puntoya oranlı (web `--otp-gap`).'
|
|
379
|
+
]
|
|
380
|
+
},
|
|
381
|
+
qrcode: {
|
|
382
|
+
summary: 'QR kodu: `data` kodlanır, `quietZone` sessiz bölgedir (modül birimi, varsayılan 1).',
|
|
383
|
+
differences: [
|
|
384
|
+
'Matris `toqr` ile üretilir ve düz görünümlerle çizilir (`react-native-svg` yok); hata düzeltme `M` sabittir ve web\'in `errorCorrectionLevel` prop\'u mobil şemadan düşer; renk web gibi siyah üstüne beyaz — koyu temada da (okuyucular ters kodu her zaman okumaz).',
|
|
385
|
+
'Kare kutunun kenarı: genişliği belli düğümde (`widthType: fill` ya da `fixed`) düğümün genişliği, içerik genişliğinde (`auto` ya da `widthType` hiç yazılmamış) 200 dp; genişlik ölçülemezse de (kanvasta satır kabındaki `fill`) 200 dp — cihaz o düğümü tam genişlik çizer. Sabit px boy kenarı sınırlar. Modül ölçüsü tam sayıya iner, artan birkaç piksel ortalanır. Belirli boy için `appearance.widthType: fixed` + `widthString`.',
|
|
386
|
+
'Veri boşsa ya da QR`a sığmıyorsa (yaklaşık 2,3 KB üstü) yer tutucu çizilir. `quietZone` web ile aynı `common` grubundadır (eski adı `padding` ortak çizim katmanının CSS boşluğuyla çakışıyordu, CQ-272).'
|
|
387
|
+
],
|
|
388
|
+
propNotes: {
|
|
389
|
+
quietZone: 'Sessiz bölge, MODÜL birimiyle (piksel değil); varsayılan 1, panel aralığı web şemasıyla aynı 1–10 (çizer 16\'ya kadar kabul eder). Okuyucular için 2–4 önerilir.'
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
stack: {
|
|
393
|
+
summary: 'Çocuklarını üst üste bindiren kap (FlutterFlow Stack). Web`de karşılığı YOKTUR.',
|
|
394
|
+
differences: [
|
|
395
|
+
'İLK çocuk taban katmandır: akışta durur ve yığının boyunu verir. Sonraki her çocuk onun ÜSTÜNE biner; dizi sırası çizim sırasıdır (sonraki üstte).',
|
|
396
|
+
'Üst katmanlar yığının `alignment`ıyla (9 nokta) hizalanır; kaydırmayı çocuğun kendi kenar boşluğu verir (ör. `bottom-right` + sağ/alt margin 8). Hizalama ÇOCUKTA değil yığındadır — `appearance.stackAlign` diye bir alan YOKTUR.',
|
|
397
|
+
'Farklı köşelerde katman için çocuk kap iki eksende `fill` olur (yığını kaplar) ve içeriğini kendi `horizontalAlign`/`verticalAlign`ı ile yerleştirir.',
|
|
398
|
+
'Katmanın boş alanı dokunuşu alttaki katmana geçirir. Çocuklar `blocks` taşımaz (MOB-001 aynen geçerli).'
|
|
399
|
+
],
|
|
400
|
+
propNotes: {
|
|
401
|
+
alignment: "Üst katmanların yeri: `top-left` (varsayılan) · `top-center` · `top-right` · `center-left` · `center` · `center-right` · `bottom-left` · `bottom-center` · `bottom-right`. Yığının boyu kutudan geliyorsa (`fixed`, `fill`, oran) taban da bu noktaya hizalanır."
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
'list-item': {
|
|
405
|
+
summary: 'Liste satırı (FlutterFlow ListTile): baş ikon ya da görsel · başlık · alt başlık · son metin ve ikon; basılabilir. Web`de karşılığı YOKTUR.',
|
|
406
|
+
differences: [
|
|
407
|
+
'Satıra dokunuş `click` yayar (yük yok). Pasifken soluk ve basılamaz; tasarım kipinde basış kanvasın seçim jestidir.',
|
|
408
|
+
'Baş tarafta `leadingImage` `leadingIcon`dan önce gelir (40 dp yuvarlak görsel; yüklenmezse başlığın baş harfleri). Boş ikon çizilmez.',
|
|
409
|
+
'Ölçüler Material liste satırı: tek satır 56, alt başlıklı 72; `dense` 48 / 60. Renkler token sınıflarından (başlık `--foreground`, alt başlık ve son taraf `--muted-foreground`, basılıyken `--accent`).',
|
|
410
|
+
'Son tarafta anahtar YOKTUR: anahtarlı satır `container` (satır) + `text` + `switch` ile kurulur. Tekrarlayıcı içinde satır olarak kullanılır — `repeater` satırının akışı tıklanan satırı `<tekrarlayıcı>.selectedItem` ile okur.'
|
|
411
|
+
]
|
|
412
|
+
},
|
|
413
|
+
carousel: {
|
|
414
|
+
summary: 'Parmakla kaydırılan sayfalar (FlutterFlow Carousel ve PageView): çocuk sayfalar ya da web gibi `items` görselleri.',
|
|
415
|
+
differences: [
|
|
416
|
+
'Prop adları ve olaylar web kaydırıcısınınki: `activeIndex` etkin sayfadır, kaydırma/oklar/noktalar/otomatik oynatma yalnız `slideChange { activeIndex }` yayar ve şerit prop değişince kayar (web gibi denetimli).',
|
|
417
|
+
'Mobilde bırakılabilir: çocuğu varsa her ÇOCUK bir sayfadır ve `items` okunmaz (`tabs` gövde modeli); çocuksuz kaydırıcı `items` görsellerini çizer. Kanvasta yalnız `activeIndex` sayfası çizilir — sayfa seçici paneldeki bu alandır.',
|
|
418
|
+
'Görsel sayfaya dokunuş web gibi yalnız bağlantısı ya da akışı olan sayfada `slideClick { currentItem, index }` yayar; dış adres (http(s), mailto, tel, alan adı) sistem tarayıcısında açılır, uygulama içi yol (`/sayfa`) açılmaz — gezinmeyi `slideClick` akışı yapar.',
|
|
419
|
+
'Düşenler: `style`, `pauseOnHover` (sürükleme oynatmayı zaten durdurur), `transition` (sayfalar kayar). Ek: `peek`. Oklar `<elementId>__prev`/`__next`, noktalar `<elementId>__dot-<n>` iç rolleri — `page_screenshot` `click` onlarla basar.',
|
|
420
|
+
'Boy kutudan gelmiyorsa görsel sayfa 16:9, çocuk sayfalar en uzununun boyunda. Genişlik belli değilse (Studio`dan bırakılan düğümün `widthType`ı yok) cihaz genişliği kullanılır; kaydırıcıya `widthType: fill` verin.'
|
|
421
|
+
],
|
|
422
|
+
propNotes: {
|
|
423
|
+
peek: 'Komşu sayfaların kenardan görünen payı (dp); en çok genişliğin dörtte biri, sayfalar araya küçük boşluk alır. 0 (varsayılan) tam genişlik sayfa.'
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
fab: {
|
|
427
|
+
summary: 'Yüzen eylem düğmesi (FlutterFlow FloatingActionButton): ekranın alt köşesinde durur, sayfa kaydırılırken yerinde kalır. Web`de karşılığı YOKTUR.',
|
|
428
|
+
differences: [
|
|
429
|
+
'Sayfanın NERESİNE konursa konsun runtime`da barındırıcının ekran katmanına taşınır: kenardan 16 dp, sekme çubuğu varsa onun üstünde, yoksa ev göstergesinin üstünde. Düğümün kendi yeri akışta boş kalır — sayfa kök kabının sonuna koyun.',
|
|
430
|
+
'Dokunuş `click` yayar. `page_screenshot` düğmeyi `<elementId>__press` ile bulur (düğüm kabuğu runtime`da boştur).',
|
|
431
|
+
'Renk `kind`den, `button` ile aynı eşleme (dolu varyant); 56 dp yuvarlak. `extended` açık ve `label` doluysa etiket ikonun yanında yazılır, yoksa etiket yalnız erişilebilirlik adıdır.',
|
|
432
|
+
'Kanvasta yerinde, kesikli bir halka içinde çizilir ve basılamaz.'
|
|
433
|
+
],
|
|
434
|
+
propNotes: {
|
|
435
|
+
position: '`bottom-right` (varsayılan) · `bottom-center` · `bottom-left`.',
|
|
436
|
+
icon: 'Material Symbols adı; boşsa `add`.'
|
|
437
|
+
}
|
|
438
|
+
},
|
|
439
|
+
timer: {
|
|
440
|
+
summary: 'Geri sayım ya da kronometre (FlutterFlow Timer). Web`de karşılığı YOKTUR.',
|
|
441
|
+
differences: [
|
|
442
|
+
'Akış bileşenin yöntemini çağıramaz, yalnız prop yazar: başlat/duraklat `isRunning` (setter), sıfırlama `value` yazmaktır (geri sayımda ör. `duration`, kronometrede 0).',
|
|
443
|
+
'Çalışırken her saniye `tick { value }` yayar ve makine `value`yu yazar; geri sayım 0`a inince `finish { isRunning: false }` yayar. Sayaç duvar saatinden hesaplanır, aralık gecikse de kaymaz.',
|
|
444
|
+
'Gösterim `format`la (`mm:ss` saati dakikaya katar, `HH:mm:ss`, `ss`); punto `text` bileşeninin `h2` ölçeğinden, `typographyStyle` üstüne biner. Tasarım kipinde durur.'
|
|
445
|
+
],
|
|
446
|
+
propNotes: {
|
|
447
|
+
value: 'Gösterilen saniye; boşsa geri sayımda `duration`, kronometrede 0. Sayaç her tikte yazar — akışın yazdığı başka bir değer sayacı o değerden yeniden kurar.',
|
|
448
|
+
duration: 'Geri sayımın başlangıcı (saniye, varsayılan 60); yalnız `mode: countdown`da panelde görünür.'
|
|
449
|
+
}
|
|
450
|
+
},
|
|
451
|
+
rating: {
|
|
452
|
+
summary: 'Yıldızla puanlama; kurallar web ile AYNI modülden (`rating-logic`): yarım yıldız, aynı puana dokununca temizleme, puan metni.',
|
|
453
|
+
differences: [
|
|
454
|
+
'Yıldıza dokunuş `change { value }` yayar; `allowHalf` açıkken yıldızın SOL yarısı ayrı dokunma alanıdır ve yarım puan verir. İç roller: `<elementId>__star-<n>` (1`den; tam puan — yarım açıkken yıldızın sağ yarısı) ve `<elementId>__star-<n>-half` (sol yarı) — `page_screenshot` `click` onlarla basar.',
|
|
455
|
+
'Ekran okuyucuda yıldız şeridi TEK ayarlanabilir öğedir: değeri okunur, yukarı/aşağı kaydırma web`in ok tuşu adımıyla arttırır/azaltır (yarım açıkken 0,5). Pasif (`isDisabled`) soluk çizilir.',
|
|
456
|
+
'Cihazın ikon yazı tipi dolgusuzdur: varsayılan yıldız Unicode ★ ile iki hâlde de dolu çizilir, hâl RENKLE ayrışır (`inactiveColor` `default` kenar rengi, öteki tonlar soluk). Seçilen `icon` glif olarak kalır ve dolu/boş yalnız renkle ayrışır.',
|
|
457
|
+
'`focus`/`blur` şemada kalır ama mobilde yayılmaz (yıldız şeridinin klavye odağı yok). Düşenler: `style`, `autofocus`.'
|
|
458
|
+
]
|
|
459
|
+
},
|
|
460
|
+
'multi-select': {
|
|
461
|
+
summary: 'Çoklu seçim: alanda kaldırılabilir çipler, dokununca alttan açılan onay kutulu liste; değer ve olaylar web ile aynı.',
|
|
462
|
+
differences: [
|
|
463
|
+
'Değer `{ label, value }` DİZİSİDİR (düz değer ve `{ id, text }` de okunur); her dokunuş `change { value }` yayar, yük `id`/`text` ikizlerini taşır — web `multi-select.functions.ts`in kuralları mobilde ikizlendi.',
|
|
464
|
+
'Liste alttan açılır (`dropdown` emsali): gruplu listede (`optionType: grouped`) çocuklu satır `isSelectable` değilse başlıktır, gruplar hep açıktır (`isExpanded` okunmaz). Arama kutusu `search` olayına akış bağlıysa `search { searchText }` yayar ve listeyi akış yazar, değilse yerelde süzer. Liste açılınca `focus`, kapanınca (`Done` ya da perde) `blur`; arama doluyken kapanırsa ve `search` akışa bağlıysa web gibi `search { searchText: "" }` de yayılır (yeniden açılan liste eski sonuçla kalmasın).',
|
|
465
|
+
'Üst sınır web gibi formun `maxItems` doğrulamasıdır, seçim kesilmez. Alanda en çok üç çip, gerisi "+n". İç roller `__trigger`, `__clear`, `__done`; satırlar `role="option"` + `data-value`.'
|
|
466
|
+
]
|
|
467
|
+
},
|
|
468
|
+
toast: {
|
|
469
|
+
summary: 'Geçici bildirim tetikleyicisi; tetikleme sözleşmesi web ile aynı (akış `isOpen`ı açar, bileşen bildirimi gösterip `isOpen`ı kapatır).',
|
|
470
|
+
differences: [
|
|
471
|
+
'Bildirim uygulamanın toast deposuna eklenir ve SAYFADAN BAĞIMSIZ yaşar: "Kaydedildi" gösterip `redirect` yapan akışta hedef sayfada görünmeye devam eder. Düğümün kendisi runtime`da hiçbir şey çizmez; kanvasta örnek balon yerinde çizilir.',
|
|
472
|
+
'Web`in dokuz `position`ı telefonda üçe iner: `top-*`/`top-full` üst, `center` orta, öteki hepsi (varsayılan `bottom-right`) alt; bildirim ekran genişliğindedir. Alttaki bildirim sekme çubuğunun üstünde durur.',
|
|
473
|
+
'Açık `modal`, çekmece ya da `dropdown`/`multi-select` listesi varken bildirim onun ÜSTÜNDE çizilir (modaldaki formun tipli uyarısı, çoklu seçimin her dokunuşunda koşan akışın uyarısı görünür); saydam `tooltip` balonunun altında sayfadaki yerinde kalır. Klavye açıkken alttaki bildirim klavyenin üstüne çıkar.',
|
|
474
|
+
'`dedupeKey` YAZILMIŞSA aynı anahtarlı bildirim görünürken ya da `delay`i dolmamışken yenisi yığılmaz; yazılmamışsa web gibi yığılır. Bir konumda en çok üç bildirim, fazlası en eskisini kapatır. Süre web`in tür varsayılanı (bilgi/başarı 4 sn, uyarı 6, hata 8, yükleniyor kalıcı).',
|
|
475
|
+
'Olaylar web`inki: `open`, `close`, `click` (gövde), `actionClick { text, index }` (eylemin kendi akışıyla). Düşenler: `style`, `pauseOnHover`, `showCopyButton` (pano native modül ister), `allowHtml` (metin olduğu gibi), `priority`.',
|
|
476
|
+
'Akışın `alert` adımındaki tipli uyarılar (success/info/warning/error) cihazda da aynı depo üzerinden toast olur ve beklemez; düz uyarı onay bekleyen diyalogdur.'
|
|
477
|
+
]
|
|
478
|
+
},
|
|
479
|
+
video: {
|
|
480
|
+
summary: 'Video oynatıcı; prop ve olaylar web ikizininki (`src`, `poster`, `autoplay`, `loop`, `muted`, `controls`; `play`/`pause`/`ended`/`click`).',
|
|
481
|
+
differences: [
|
|
482
|
+
'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.',
|
|
483
|
+
'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).',
|
|
484
|
+
'`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.',
|
|
485
|
+
'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`.',
|
|
486
|
+
'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).'
|
|
487
|
+
]
|
|
488
|
+
},
|
|
489
|
+
audio: {
|
|
490
|
+
summary: 'Ses oynatıcı (FlutterFlow AudioPlayer): oynat/duraklat düğmesi, başlık, ilerleme izi ve süre. Web`de karşılığı YOKTUR.',
|
|
491
|
+
differences: [
|
|
492
|
+
'Cihazda `expo-audio`, kanvas ve önizlemede HTML5 `<audio>`; denetim iki yerde aynı çizimdir. Cihazda iOS`un sessiz anahtarına uymaz (müzik uygulamalarının davranışı).',
|
|
493
|
+
'Akış yöntem çağıramaz, prop yazar: çalma `isPlaying`dir — açılışta `true` otomatik oynatmadır, akış `true`/`false` yazarak başlatır ve durdurur, bitmiş ses baştan çalar. Kullanıcı denetimle başlatıp durdurunca `play`/`pause` `{ isPlaying }` yayar ve makine prop`u yazar; ses bitince önce `pause`, ardından `ended { isPlaying: false }` (`loop` açıkken `ended` yok). Tarayıcı sesli otomatik oynatmayı reddederse `pause { isPlaying: false }` yayılır.',
|
|
494
|
+
'İzde dokunulan yere atlanır. İç roller `<elementId>__toggle` (oynat/duraklat) ve `<elementId>__track` (iz) — `page_screenshot` `click` onlarla basar.',
|
|
495
|
+
'`showControls: false` bileşeni görünmez yapar ama ses çalar (arka plan sesi, akışla çalınan efekt); kanvasta kesikli yer tutucu çizilir. Tasarım kipinde çalmaz.',
|
|
496
|
+
'Ara belleğe alma (yavaş ağ, izde tamponlanmamış noktaya atlama) duruş sayılmaz, `pause` yaymaz. `ended` akışı aynı turda yeniden `isPlaying: true` yazarsa ses baştan çalar (akışla tekrar). Cihazda sayfa odağı kaybedince (yığında arkaya düşünce) ses durur ve `pause { isPlaying: false }` yayar; dönüşte kendiliğinden sürmez. Kaynak değişince yeni kaynak baştan yüklenir, `isPlaying` açıksa çalar.'
|
|
497
|
+
],
|
|
498
|
+
propNotes: {
|
|
499
|
+
src: 'Ses dosyasının adresi ya da platform dosyası (mp3, m4a, aac, wav). Canlı yayın adresinin süresi bilinmez: iz ilerlemez, süre 0:00 yazar.',
|
|
500
|
+
isPlaying: 'Çalıyor mu: akışın yazdığı `true` çalar, `false` durdurur. Kullanıcının denetimi ve bitiş bu prop`u geri yazar.',
|
|
501
|
+
title: 'Denetimin üstündeki başlık (tek satır); boşsa çizilmez.'
|
|
502
|
+
}
|
|
503
|
+
},
|
|
504
|
+
lottie: {
|
|
505
|
+
summary: 'Lottie animasyonu (FlutterFlow Lottie Animation): Lottie JSON ya da dotLottie (`.lottie`). Web`de karşılığı YOKTUR.',
|
|
506
|
+
differences: [
|
|
507
|
+
'Cihazda `lottie-react-native`, kanvas ve önizlemede `@lottiefiles/dotlottie-react`: çizici ve WASM`ı ayrı parçadır, yalnız `lottie` düğümü olan sayfada yüklenir ve WASM paketin kendisinden sunulur (CDN`e gidilmez). Kanvasta da oynar.',
|
|
508
|
+
'Akış prop yazar: `isPlaying: false` yerinde durdurur, `true` kaldığı yerden sürdürür. Döngüsüz (`loop: false`) animasyon bitince `finish { isPlaying: false }` yayar; akış yeniden `true` yazınca baştan oynar — `finish` akışının kendisi aynı turda `true` yazsa da (akışla tekrar). Tasarım kipinde açık kalan `isPlaying` bitişi tekrar ettirmez.',
|
|
509
|
+
'Kutu `video` ile aynı kuraldan, varsayılan oran olmadan: kabuktan gelen boy ya da (genişlik belliyken) panelin `aspectRatio`su, yoksa 150 dp; animasyon kutuya `contain` ile sığar. Dokunuş almaz, `click` olayı yoktur.'
|
|
510
|
+
],
|
|
511
|
+
propNotes: {
|
|
512
|
+
src: "Animasyonun adresi (`.json` Lottie, `.lottie` dotLottie — cihazda adreste `.lottie` GEÇMELİ, uzantısız dosya JSON sanılır), platform dosyası ya da JSON'un kendisi METİN olarak (yapıştırılan ya da ifadenin ürettiği metin, ör. `{{ JSON.stringify(...) }}`). NESNE olarak bağlanan animasyon kaynak DEĞİLDİR (runtime prop eşleyicisi iç içe düz nesneyi bozuyor) — yer tutucu çizilir. Katman dizisi (`layers`) taşımayan JSON da kaynak sayılmaz.",
|
|
513
|
+
speed: 'Oynatma hızı çarpanı (1 = normal); 0,1–10 aralığına kırpılır, geçersiz değer 1.'
|
|
514
|
+
}
|
|
515
|
+
},
|
|
516
|
+
signature: {
|
|
517
|
+
summary: 'İmza pedi; prop ve olaylar web ikizininki (`value` PNG data URL ya da ham SVG, `penColor`, `penWidth`, `outputFormat`, `clearText`; `change { value }`, `clear`).',
|
|
518
|
+
differences: [
|
|
519
|
+
'Cihazda mürekkep `react-native-svg`, jest `PanResponder`; kanvas ve önizlemede DOM `<svg>`. Her tamamlanan vuruşta değer web gibi `change { value }` ile yazılır: `self.value` de `event.data.value` da dolu gelir. PNG cihazda görünmez bir `Svg`nin anlık görüntüsüdür (ekran ölçeğinde), web`de `<canvas>`ın; ikisi aynı SVG belgesinin çizimidir ve kalem rengi boşsa ekranda tema metin rengi, değerde somut `#111111` olur.',
|
|
520
|
+
'Vuruş sürerken sayfanın ve `scroll-view`un kaydırması kilitlidir: dikey vuruş sayfayı sürüklemez (web`de pedin `touch-action: none`ı); parmak kalkınca sayfa normal kayar.',
|
|
521
|
+
'Temizle düğmesi (`<elementId>__clear`, `clearText`, boşsa "Clear") çizim ya da değer varken ve alan kilitsizken görünür; basınca önce `change { value: \'\' }`, sonra `clear`. Ped `<elementId>__pad`. Tasarım kipinde çizilmez, Temizle basılmaz.',
|
|
522
|
+
'Boyu kabuktan gelen düğümde (`heightType: fixed` ya da `fill`) ped kabuğu doldurur; yoksa taban yükseklik boya göre 72/96/128 dp. Kayıtlı `value` PNG ise görsel, SVG (ham işaretleme ya da `data:image/svg+xml`) ise vektör olarak çizilir; tanınmayan metin çizilmez. Düşen: `style`.',
|
|
523
|
+
'iOS`ta sistem jesti imzadan önce gelir: iOS 26`da yığına itilmiş sayfada pede SAĞA atılan vuruş sayfayı kapatır (ARC-116, ölçüldü), `modal` sunumlu sayfada aşağı vuruş sayfayı kapatabilir (ölçülmedi). İmzayı sekme kökündeki ya da itilmemiş sayfaya koyun.'
|
|
524
|
+
]
|
|
525
|
+
},
|
|
526
|
+
webview: {
|
|
527
|
+
summary: 'Uygulama içinde web sayfası (`src`) ya da satır içi HTML (`html`) — FlutterFlow WebView. Web`in `iframe`inin mobil karşılığıdır; web`de bu slug YOKTUR.',
|
|
528
|
+
differences: [
|
|
529
|
+
'Cihazda `react-native-webview`; kanvas ve önizlemede web `iframe`inin korumalı alanıyla `<iframe>` (betik, form, açılır pencere; `allow-same-origin` yok — çerez ya da depolama isteyen site önizlemede eksik çalışabilir, cihazda tam). Kanvasta dokunuş almaz. Android`de görünüm kendi içeriğini sayfanın kaydırma kabı içinde de kaydırır.',
|
|
530
|
+
'Gezinme varsayılan KAPALI (güvenlik kararı): ilk yükleme ve yönlendirmeleri (ilk `load`a kadar), alt çerçeveler ve aynı belgedeki çapalar serbesttir; yüklemeden sonra başka belgeye giden gezinme `navigate { url }` yayar ve ENGELLENİR. Adres sistemde (http(s) tarayıcıda, `mailto:`/`tel:`/`sms:` ilgili uygulamada) YALNIZ kullanıcının dokunduğu bağlantıda açılır — betiğin gezinmesi (`location`, zamanlayıcı, programla tıklama) ve form gönderimi yalnız olay yayar, uygulama şemaları (ör. `whatsapp://`) hiç açılmaz. `allowNavigation: true` gezinmeyi görünümün içinde yapar, `navigate` yine yayılır. Yeni pencere (`target="_blank"`, `window.open`) görünümde açılmaz: `navigate` yayılır, kullanıcının dokunduğu bağlantıysa (izin açıkken de) sistemde açılır.',
|
|
531
|
+
'Kuralın bilinen boşlukları: ilk `load`dan ÖNCE her gezinme serbesttir (betik ya da hızlı dokunuş dahil); aynı adrese yapılan form gönderimi aynı belge sayılır; Android POST isteğini kurala hiç getirmez ve karar 250 ms içinde verilemezse yüklemeye izin verir (olay yine yayılır). Web`de köprü yalnız `html` içeriğinde ve yalnız gerçek bağlantı dokunuşunu görür: betiğin gezinmesi, form gönderimi ve açılır pencere çerçevenin içinde ya da yeni sekmede kalır; başka kökenli `src` sayfasında köprü hiç yoktur.',
|
|
532
|
+
'Sayfadan akışa: `window.ReactNativeWebView.postMessage(metin)` → `message { data }` (metin olduğu gibi; JSON`u akış ayrıştırır). Web`de köprü `html` içeriğinde çalışır, başka kökenli `src` sayfasında YOKTUR.',
|
|
533
|
+
'Kutu `lottie` kuralından: kabuktan gelen boy ya da (genişlik belliyken) panelin `aspectRatio`su, yoksa 300 dp. `load` her tamamlanan yüklemede (izin verilen gezinmeden sonra da) yayılır. Cihazda yüklenemeyen sayfa (ağ hatası) `error` yayar ve yer tutucu çizer; web`de tarayıcı engellenen ya da bulunamayan çerçevede de `load` atar, `error` pratikte gelmez (web `iframe` gibi).'
|
|
534
|
+
],
|
|
535
|
+
propNotes: {
|
|
536
|
+
src: 'Mutlak adres (`https://…`); `javascript:`, `data:`, `vbscript:`, `file:` şemaları boşa düşer (web `iframe` süzgeci). Göreli adres cihazda yüklenmez. `html` doluysa yok sayılır.',
|
|
537
|
+
html: 'Satır içi HTML belgesi; doluysa `src`nin önüne geçer. Cihazda `about:blank` kökeninde, web`de kökensiz korumalı `srcdoc` olarak çizilir; sayfa içi çapa (`#bolum`) yerinde kaydırır.',
|
|
538
|
+
allowNavigation: 'Açıkken bağlantılar görünümün İÇİNDE açılır; kapalıyken (varsayılan) gezinme engellenir ve kullanıcının dokunduğu bağlantı sistemde açılır. İki durumda da `navigate { url }` yayılır.',
|
|
539
|
+
injectedJs: 'Belge yüklenince koşan JavaScript (ör. `window.ReactNativeWebView.postMessage(document.title)`); cihazda iki içerikte, web`de yalnız `html`de. Gömülen sayfada tam yetkiyle koşar — yalnız güvenilen içerikte kullanın.',
|
|
540
|
+
title: 'Erişilebilirlik etiketi (ekran okuyucu; web`de `<iframe title>`).'
|
|
541
|
+
}
|
|
330
542
|
}
|
|
331
543
|
};
|
|
@@ -19,6 +19,54 @@ export const mobileComponentEventContracts = {
|
|
|
19
19
|
tooltip: {
|
|
20
20
|
open: NO_DATA
|
|
21
21
|
},
|
|
22
|
+
calendar: {
|
|
23
|
+
mount: NO_DATA,
|
|
24
|
+
change: {
|
|
25
|
+
payload: {
|
|
26
|
+
value: "string 'yyyy-MM-ddT00:00:00' | null — seçilen gün (aralıkta başlangıç); web datepicker'ın biçimi: yerel duvar saati, gece yarısı, dilim eki yok",
|
|
27
|
+
endValue: "string 'yyyy-MM-ddT00:00:00' | null — YALNIZ mode: 'range'; ilk basışta null, ikinci basışta bitiş günü"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
stack: {
|
|
32
|
+
mount: NO_DATA
|
|
33
|
+
},
|
|
34
|
+
'list-item': {
|
|
35
|
+
click: NO_DATA
|
|
36
|
+
},
|
|
37
|
+
fab: {
|
|
38
|
+
click: NO_DATA
|
|
39
|
+
},
|
|
40
|
+
timer: {
|
|
41
|
+
mount: NO_DATA,
|
|
42
|
+
tick: { payload: { value: 'number — gösterilen saniye (geri sayımda kalan, kronometrede geçen); makine value prop\'una yazar' } },
|
|
43
|
+
finish: { payload: { isRunning: 'false — geri sayım 0\'a indi; makine isRunning\'i kapatır' } }
|
|
44
|
+
},
|
|
45
|
+
audio: {
|
|
46
|
+
mount: NO_DATA,
|
|
47
|
+
play: { payload: { isPlaying: "true — çalma başladı (denetim ya da akış); makine isPlaying'i yazar" } },
|
|
48
|
+
pause: { payload: { isPlaying: "false — çalma durdu (denetim, akış, bitiş ya da tarayıcının otomatik oynatma reddi); makine isPlaying'i yazar" } },
|
|
49
|
+
ended: { payload: { isPlaying: "false — ses sona vardı (loop kapalıyken, pause'tan sonra); makine isPlaying'i kapatır" } }
|
|
50
|
+
},
|
|
51
|
+
lottie: {
|
|
52
|
+
mount: NO_DATA,
|
|
53
|
+
finish: { payload: { isPlaying: "false — döngüsüz animasyon bitti; makine isPlaying'i kapatır, yeniden true yazmak baştan oynatır" } }
|
|
54
|
+
},
|
|
55
|
+
webview: {
|
|
56
|
+
mount: NO_DATA,
|
|
57
|
+
load: { payload: {}, reads: "her tamamlanan yüklemede yayılır (izin verilen gezinmeden sonra da); cihazda yüklenemeyen sayfada yayılmaz (yerine error), web'de tarayıcı engellenen çerçevede de yayar" },
|
|
58
|
+
navigate: { payload: { url: "string — sayfanın gitmek istediği adres; makine url prop'una yazar (self.url). allowNavigation kapalıyken (varsayılan) gezinme engellenir; adres yalnız kullanıcının dokunduğu bağlantıda sistemde açılır" } },
|
|
59
|
+
message: { payload: { data: "string — sayfanın window.ReactNativeWebView.postMessage(metin) ile yolladığı metin, olduğu gibi; makine data prop'una yazar (self.data)" } },
|
|
60
|
+
error: { payload: {}, reads: "cihazda sayfa yüklenemeyince (ağ hatası) yayılır, görünüm yer tutucu çizer; web'de pratikte gelmez (iframe gibi)" }
|
|
61
|
+
},
|
|
62
|
+
markdown: {
|
|
63
|
+
mount: NO_DATA,
|
|
64
|
+
linkClick: {
|
|
65
|
+
payload: {
|
|
66
|
+
href: "string — dokunulan bağlantının ham adresi; şema süzülmeden gelir (dışarıda yalnız http(s), mailto, tel açılır)"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
},
|
|
22
70
|
'app-bar': {
|
|
23
71
|
clickBack: NO_DATA,
|
|
24
72
|
clickMenu: NO_DATA,
|
|
@@ -2082,32 +2082,76 @@ export function orphanedFlowPlan(schema, pageEvents, ops) {
|
|
|
2082
2082
|
if (op.op !== 'set_event' && op.op !== 'remove_event')
|
|
2083
2083
|
continue;
|
|
2084
2084
|
const eventName = String(op.event ?? '');
|
|
2085
|
+
const eventValue = typeof op.eventValue === 'string' ? op.eventValue : undefined;
|
|
2086
|
+
const boundTo = (events) => asObjects(events)
|
|
2087
|
+
.filter(event => String(event.name ?? '') === eventName && typeof event.value === 'string' && event.value !== '');
|
|
2088
|
+
// Bir olaya birden fazla akış bağlı olabilir. Değersiz `remove_event` o durumda platformda
|
|
2089
|
+
// `ambiguous-event` ile düşer ve hiçbir bağ kalkmaz; değerli olan yalnız o akışı çözer.
|
|
2090
|
+
const removedBy = (named) => eventValue === undefined
|
|
2091
|
+
? (named.length === 1 ? named : [])
|
|
2092
|
+
: named.filter(event => event.value === eventValue);
|
|
2085
2093
|
// `remove_event target:'page'` düğümü değil barındırıcının düzey olaylarını hedefler
|
|
2086
2094
|
// (`PageSchemaMicroOpApplier.PageTarget`); düğüm aramasına düşseydi plan boş kalır ve yalnız
|
|
2087
2095
|
// oraya bağlı akış kapıdan geçip kalıcı silinirdi. Düzey olayı olmayan widget'ta applier da
|
|
2088
2096
|
// düğüm aramasına düşer.
|
|
2089
2097
|
if (op.op === 'remove_event' && target === 'page' && Array.isArray(pageEvents)) {
|
|
2090
2098
|
const hostEvents = asObjects(pageEvents);
|
|
2091
|
-
const removed =
|
|
2092
|
-
|
|
2093
|
-
continue;
|
|
2094
|
-
const surviving = new Set(hostEvents.filter(event => event !== removed && typeof event.value === 'string').map(event => String(event.value)));
|
|
2099
|
+
const removed = removedBy(boundTo(hostEvents));
|
|
2100
|
+
const surviving = new Set(hostEvents.filter(event => !removed.includes(event) && typeof event.value === 'string').map(event => String(event.value)));
|
|
2095
2101
|
collectFlowRefsExcept(nodes, '', surviving);
|
|
2096
|
-
|
|
2097
|
-
|
|
2102
|
+
const flowIds = [...new Set(removed.map(event => String(event.value)))].filter(flowId => !surviving.has(flowId));
|
|
2103
|
+
if (flowIds.length > 0)
|
|
2104
|
+
plan.push({ target, op: 'remove_event', flowIds });
|
|
2098
2105
|
continue;
|
|
2099
2106
|
}
|
|
2100
2107
|
const node = findNodeByElementId(nodes, target);
|
|
2101
|
-
const
|
|
2102
|
-
|
|
2103
|
-
|
|
2108
|
+
const named = boundTo(node?.events);
|
|
2109
|
+
// `set_event` adın BÜTÜN bağlarını tek kayda indirir; yeni değerden farklı her akış bağını kaybeder.
|
|
2110
|
+
const losing = op.op === 'set_event' ? named.filter(event => event.value !== eventValue) : removedBy(named);
|
|
2111
|
+
if (losing.length === 0)
|
|
2104
2112
|
continue;
|
|
2105
2113
|
const surviving = survivingRefs(target, eventName);
|
|
2106
|
-
|
|
2107
|
-
|
|
2114
|
+
for (const event of named) {
|
|
2115
|
+
if (!losing.includes(event))
|
|
2116
|
+
surviving.add(String(event.value));
|
|
2117
|
+
}
|
|
2118
|
+
const flowIds = [...new Set(losing.map(event => String(event.value)))].filter(flowId => !surviving.has(flowId));
|
|
2119
|
+
if (flowIds.length > 0)
|
|
2120
|
+
plan.push({ target, op: String(op.op), flowIds });
|
|
2108
2121
|
}
|
|
2109
2122
|
return plan;
|
|
2110
2123
|
}
|
|
2124
|
+
/**
|
|
2125
|
+
* PAGE-034 — dolu bir olaya farklı akışla `set_event` gelmesi. `set_event` adın bütün bağlarını
|
|
2126
|
+
* TEK kayda indirir, yani önceki akış bağını sessizce kaybeder; akış başka yerden de
|
|
2127
|
+
* referanslıysa `orphanedFlowPlan` da susar ve kayıp hiç görünmez (maddelerim 4: makale önerisi
|
|
2128
|
+
* akışı, akıllı öneri bağlanınca çalışmaz oldu). Olaya ikinci akış eklemek `add_event`'tir; bilerek
|
|
2129
|
+
* değiştirmek için op'a `replaceBoundFlows: true` verilir. Okunan ağaçta olmayan hedef denetlenmez.
|
|
2130
|
+
*/
|
|
2131
|
+
export function boundEventOps(schema, ops) {
|
|
2132
|
+
const nodes = asObjects(schema);
|
|
2133
|
+
return asObjects(ops)
|
|
2134
|
+
.filter(op => op.op === 'set_event' && op.replaceBoundFlows !== true)
|
|
2135
|
+
.flatMap(op => {
|
|
2136
|
+
const target = String(op.target ?? '');
|
|
2137
|
+
const event = String(op.event ?? '');
|
|
2138
|
+
const eventValue = typeof op.eventValue === 'string' ? op.eventValue : '';
|
|
2139
|
+
const bound = asObjects(findNodeByElementId(nodes, target)?.events)
|
|
2140
|
+
.filter(entry => String(entry.name ?? '') === event && typeof entry.value === 'string' && entry.value !== '')
|
|
2141
|
+
.map(entry => String(entry.value));
|
|
2142
|
+
const replaced = [...new Set(bound)].filter(flowId => flowId !== eventValue);
|
|
2143
|
+
if (replaced.length === 0)
|
|
2144
|
+
return [];
|
|
2145
|
+
return [`PAGE-034: '${target}' komponentinin '${event}' olayına zaten bağlı akış var (${replaced.join(', ')}). set_event olayın BÜTÜN bağlarını tek akışa indirir ve bu akışlar olay tetiklenince artık çalışmaz. `
|
|
2146
|
+
+ `Olaya bir akış daha eklemek için { op: 'add_event', target: '${target}', event: '${event}', eventValue: '${eventValue}' } kullanın — bağlı akışların hepsi paralel koşar. `
|
|
2147
|
+
+ `Mevcut bağı bilerek değiştirmek istiyorsanız aynı op'a replaceBoundFlows: true ekleyin.`];
|
|
2148
|
+
});
|
|
2149
|
+
}
|
|
2150
|
+
/** Gateway'e ait op alanları platforma gitmez. */
|
|
2151
|
+
export function stripGatewayEventFields(ops) {
|
|
2152
|
+
for (const op of ops)
|
|
2153
|
+
delete op.replaceBoundFlows;
|
|
2154
|
+
}
|
|
2111
2155
|
export function orphanedFlowMessage(plan) {
|
|
2112
2156
|
const lines = plan.map(entry => `'${entry.target}' (${entry.op}) → ${entry.flowIds.join(', ')}`);
|
|
2113
2157
|
return `İstek gönderilmedi: bu yama ${plan.reduce((total, entry) => total + entry.flowIds.length, 0)} akışı sayfada referanssız bırakıyor ve platform referanssız akışı KALICI siler — ${lines.join(' · ')}. `
|
|
@@ -58,16 +58,18 @@ export const REDIRECT_PRESENTATIONS = new Set(['push', 'modal', 'replace']);
|
|
|
58
58
|
* Mobil pakete giren bileşen Dalga 2/3 listesinden DÜŞER: yayında olduğunu mobil sözlükteki kaydı
|
|
59
59
|
* söyler (`mobileComponentKnowledge`, parite testiyle `components.json`'a kilitli). Katalog A
|
|
60
60
|
* (2026-09-24) sekiz web ikizini buradan indirdi: `radio`, `checkboxgroup`, `slider`, `tabs`, `badge`,
|
|
61
|
-
* `avatar`, `progress`, `collapsible-container
|
|
62
|
-
*
|
|
61
|
+
* `avatar`, `progress`, `collapsible-container`; Katalog C (09) Dalga 3'ten `video` ve `signature`ı; dil dalgası
|
|
62
|
+
* (plans/mobil-coklu-dil F1, 2026-09-25) Dalga 2'den `language-selector`ı. Dalga 1
|
|
63
|
+
* listesi artık yalnız `mobileNotes`'un bilgi satırıdır — MOB-003'te yayındaki bileşeni sözlük tanır
|
|
64
|
+
* (`box-section` Dalga 1'de ama pakette yok).
|
|
63
65
|
*/
|
|
64
66
|
export const MOBILE_COMPONENT_WAVES = Object.fromEntries([
|
|
65
67
|
...['container', 'box-section', 'form', 'slot', 'text', 'icon', 'image', 'divider', 'spacer', 'button', 'textinput', 'textarea', 'numberinput', 'password', 'checkbox', 'switch', 'dropdown', 'repeater', 'card', 'emptystate', 'form-validation-errors', 'modal', 'drawer', 'app-bar'].map(slug => [slug, 1]),
|
|
66
|
-
...['box', 'datepicker', 'timepicker', '
|
|
67
|
-
...['datatable', 'chart', 'kanban', 'timeline', 'fileUpload', 'filebutton', 'fileInput', '
|
|
68
|
+
...['box', 'datepicker', 'timepicker', 'phoneinput', 'statcard', 'detailview', 'page-header', 'autocomplete'].map(slug => [slug, 2]),
|
|
69
|
+
...['datatable', 'chart', 'kanban', 'timeline', 'fileUpload', 'filebutton', 'fileInput', 'texteditor', 'html', 'stepper', 'tree-view'].map(slug => [slug, 3])
|
|
68
70
|
]);
|
|
69
71
|
export const MOBILE_WEB_ONLY = {
|
|
70
|
-
iframe: "
|
|
72
|
+
iframe: "'webview' (src ya da html; bağlantılar varsayılan olarak sistem tarayıcısında açılır)",
|
|
71
73
|
sidebar: "'drawer' widget'ı + app-bar.menuTarget",
|
|
72
74
|
navigations: 'Ayarlar › Sekmeler (application_update properties.mobile.tabs)',
|
|
73
75
|
header: "'app-bar'",
|
package/dist/knowledge/rules.js
CHANGED
|
@@ -420,6 +420,8 @@ export function rawQueryParameterNames(rawQuery) {
|
|
|
420
420
|
}
|
|
421
421
|
const isRecord = (value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
422
422
|
const functionsGuide = 'ngapps://guides/functions';
|
|
423
|
+
/** FN-006: bu kadar dolu satırdan uzun gövde yorumsuz kalmaz; tek satırlık `return` yorum istemez. */
|
|
424
|
+
const FUNCTION_COMMENT_MIN_LINES = 5;
|
|
423
425
|
export const policyRules = [
|
|
424
426
|
{
|
|
425
427
|
id: 'MS-001',
|
|
@@ -2030,6 +2032,25 @@ export const policyRules = [
|
|
|
2030
2032
|
return [];
|
|
2031
2033
|
}
|
|
2032
2034
|
},
|
|
2035
|
+
{
|
|
2036
|
+
id: 'FN-006',
|
|
2037
|
+
module: 'function',
|
|
2038
|
+
appliesTo: ['create', 'update'],
|
|
2039
|
+
sourceRef: "Kullanıcı isteği 2026-09-18: ajan fonksiyon yazarken gövdeye ne yaptığını söyleyen yorum koyar; Studio editörü yorumu yeşil italik çizer. Gövde kendi satırına sarılır (function-source.ts, GlobalFunction.cs AppendLine), '//' kapanış parantezini yutmaz",
|
|
2040
|
+
check: definition => {
|
|
2041
|
+
const name = String(definition.name ?? '?');
|
|
2042
|
+
const body = typeof definition.body === 'string' ? definition.body : '';
|
|
2043
|
+
const lineCount = body.split('\n').filter(line => line.trim() !== '').length;
|
|
2044
|
+
if (lineCount < FUNCTION_COMMENT_MIN_LINES || /(^|\s)\/\/|\/\*/m.test(body))
|
|
2045
|
+
return [];
|
|
2046
|
+
return [{
|
|
2047
|
+
ruleId: 'FN-006',
|
|
2048
|
+
severity: 'warn',
|
|
2049
|
+
message: `'${name}' gövdesi ${lineCount} satır ama hiç yorum yok. Başa ne yaptığını, parametreleri ve dönüşü söyleyen kısa bir '//' yorumu, açık olmayan adımların üstüne de birer satır yorum ekleyin; kodu tekrar eden yorum yazmayın.`,
|
|
2050
|
+
guide: functionsGuide
|
|
2051
|
+
}];
|
|
2052
|
+
}
|
|
2053
|
+
},
|
|
2033
2054
|
{
|
|
2034
2055
|
id: 'FLOW-024',
|
|
2035
2056
|
module: 'flow',
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
const activeMode = new AsyncLocalStorage();
|
|
4
|
+
const MAX_NOTES = 8;
|
|
5
|
+
const MAX_NOTE_CHARS = 240;
|
|
6
|
+
const MAX_DROPS = 5;
|
|
7
|
+
export const diagnosticsParam = z.enum(['compact', 'full']).optional().describe("Yanıttaki gatewayNormalized / silentlyDropped listelerinin boyu. compact (varsayılan): aynı not tekilleşir, uzun not kısalır (en fazla 8); aynı yolun düşmeleri tek satırda sayıyla toplanır (en fazla 5 grup); droppedCount her zaman tam sayıdır. full: listelerin tamamı.");
|
|
8
|
+
export function runWithDiagnostics(mode, run) {
|
|
9
|
+
return activeMode.run(mode, run);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Tanılama listeleri her yazma çağrısında yanıta girer ve aynı gerekçe cümlesi çağrı başına tekrar
|
|
13
|
+
* eder. Kırpma yalnız GÖRÜNÜMÜ küçültür: `silentlyDropped` "kaydedildi mi" sorusunun tek kanıtı olduğu
|
|
14
|
+
* için liste hiçbir zaman sessizce boşalmaz — toplam sayı ve kalan satır sayısı yanıtta kalır, tam
|
|
15
|
+
* liste gateway günlüğünde (`sessiz düşme` / `düzeltme` adımları) durur.
|
|
16
|
+
*
|
|
17
|
+
* Kip bağlamı olmayan çağrı (okuma ve silme tool'ları, doğrudan `toolResult` kullanan testler) tam
|
|
18
|
+
* listeyi görür; kısaltma yalnız `diagnostics` parametresini taşıyan yazma tool'larında devreye girer.
|
|
19
|
+
*/
|
|
20
|
+
export function compactDiagnostics(payload) {
|
|
21
|
+
if (activeMode.getStore() !== 'compact')
|
|
22
|
+
return payload;
|
|
23
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload))
|
|
24
|
+
return payload;
|
|
25
|
+
const record = payload;
|
|
26
|
+
const notes = Array.isArray(record.gatewayNormalized) ? record.gatewayNormalized.map(String) : undefined;
|
|
27
|
+
const drops = Array.isArray(record.silentlyDropped) ? record.silentlyDropped.map(String) : undefined;
|
|
28
|
+
if (notes === undefined && drops === undefined)
|
|
29
|
+
return payload;
|
|
30
|
+
const hidden = [];
|
|
31
|
+
const result = { ...record };
|
|
32
|
+
if (notes !== undefined) {
|
|
33
|
+
const counts = new Map();
|
|
34
|
+
for (const note of notes)
|
|
35
|
+
counts.set(note, (counts.get(note) ?? 0) + 1);
|
|
36
|
+
const unique = [...counts.entries()].map(([note, count]) => {
|
|
37
|
+
const shortened = note.length > MAX_NOTE_CHARS ? `${note.slice(0, MAX_NOTE_CHARS - 1)}…` : note;
|
|
38
|
+
return count > 1 ? `${shortened} (×${count})` : shortened;
|
|
39
|
+
});
|
|
40
|
+
const shortenedCount = notes.filter(note => note.length > MAX_NOTE_CHARS).length;
|
|
41
|
+
const kept = unique.slice(0, MAX_NOTES);
|
|
42
|
+
result.gatewayNormalized = unique.length > MAX_NOTES ? [...kept, `… ${unique.length - MAX_NOTES} not daha`] : kept;
|
|
43
|
+
if (unique.length > MAX_NOTES)
|
|
44
|
+
hidden.push(`${unique.length - MAX_NOTES} not`);
|
|
45
|
+
if (shortenedCount > 0)
|
|
46
|
+
hidden.push(`${shortenedCount} notun gerekçesi kısaltıldı`);
|
|
47
|
+
}
|
|
48
|
+
if (drops !== undefined) {
|
|
49
|
+
const droppedCount = typeof record.droppedCount === 'number' ? record.droppedCount : drops.length;
|
|
50
|
+
const grouped = groupDrops(drops, record.droppedCountCapped === true || droppedCount > drops.length);
|
|
51
|
+
const kept = grouped.slice(0, MAX_DROPS);
|
|
52
|
+
if (grouped.length !== drops.length)
|
|
53
|
+
hidden.push('benzer düşme satırları toplandı');
|
|
54
|
+
if (grouped.length > MAX_DROPS)
|
|
55
|
+
hidden.push(`${grouped.length - MAX_DROPS} düşme grubu`);
|
|
56
|
+
result.silentlyDropped = grouped.length > MAX_DROPS ? [...kept, `… ${grouped.length - MAX_DROPS} grup daha`] : kept;
|
|
57
|
+
result.droppedCount = droppedCount;
|
|
58
|
+
}
|
|
59
|
+
if (hidden.length === 0)
|
|
60
|
+
return result;
|
|
61
|
+
return { ...result, diagnosticsNote: "Kısaltıldı; yazmayı tekrarlamayın. Tam liste: sonraki çağrıda diagnostics:'full'." };
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Aynı yolun farklı öğelerindeki düşmeler (`selectFields[fieldId=a].path`, `selectFields[fieldId=b].path`, …)
|
|
65
|
+
* aynı sorunu tekrar eder. Seçici ve indeks `[*]`'a indirgenir, grup tek satırda sayıyla ve bir örnekle
|
|
66
|
+
* döner. Listedeki HER satır kayıptır (sıra uyarısı ve kesme bilgisi `silentlyDroppedNotes`'tadır ve
|
|
67
|
+
* buraya hiç girmez); micro-op satırları (`add_component 'x': …`) da kendi yolu altında gruplanır.
|
|
68
|
+
* Liste kesildiyse ya da sayım tavana vurduysa grup sayısı "en az" diye yazılır.
|
|
69
|
+
*/
|
|
70
|
+
function groupDrops(drops, partial) {
|
|
71
|
+
const groups = new Map();
|
|
72
|
+
for (const line of drops) {
|
|
73
|
+
const separator = line.indexOf(': ');
|
|
74
|
+
const path = separator === -1 ? line : line.slice(0, separator);
|
|
75
|
+
const outcome = line.includes(' ↔ ') ? (line.endsWith('kaydedilen yok') ? ' · kaydedilmedi' : ' · farklı kaydedildi') : '';
|
|
76
|
+
const pattern = `${path.replace(/\[[^\]]*\]/g, '[*]')}${outcome}`;
|
|
77
|
+
groups.set(pattern, [...(groups.get(pattern) ?? []), line]);
|
|
78
|
+
}
|
|
79
|
+
return [...groups.entries()].map(([pattern, lines]) => {
|
|
80
|
+
if (lines.length === 1)
|
|
81
|
+
return lines[0];
|
|
82
|
+
const example = lines[0].length > 160 ? `${lines[0].slice(0, 159)}…` : lines[0];
|
|
83
|
+
return `${pattern}: ${partial ? 'en az ' : ''}${lines.length} öğe — ör. ${example}`;
|
|
84
|
+
});
|
|
85
|
+
}
|