@appaflytech/zeyrek-ai-mcp 0.0.37 → 0.0.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/knowledge/componentCatalog.js +457 -156
- package/dist/knowledge/componentEvents.js +49 -8
- package/dist/knowledge/connectDataBind.js +10 -2
- package/dist/knowledge/expressionPrelude.js +1 -0
- package/dist/knowledge/mobileComponentCatalog.js +21 -9
- package/dist/knowledge/mobileComponentEvents.js +21 -1
- package/dist/knowledge/mobileStore.js +75 -0
- package/dist/knowledge/normalize.js +52 -17
- package/dist/knowledge/platform/mobile.js +1 -1
- package/dist/knowledge/platform/mobileRules.js +1 -1
- package/dist/knowledge/platform/web.js +1 -1
- package/dist/knowledge/rules.js +244 -25
- package/dist/knowledge/styleResolve.js +27 -8
- package/dist/knowledge/styleResolveNative.js +4 -2
- package/dist/knowledge/themeResolve.js +1 -1
- package/dist/knowledge/themeSchema.js +2 -1
- package/dist/observability/trace.js +7 -2
- package/dist/platform/capture/actions.js +24 -1
- package/dist/platform/capture/summary.js +4 -1
- package/dist/platform/secrets.js +50 -2
- package/dist/resources/examples/mobile-app-shell-drawer.json +1 -0
- package/dist/resources/examples/mobile-list-detail-form.json +3 -0
- package/dist/resources/guides/component.md +1 -1
- package/dist/resources/guides/entity.md +16 -0
- package/dist/resources/guides/expressions.md +7 -6
- package/dist/resources/guides/merge.md +5 -4
- package/dist/resources/guides/microservice.md +2 -2
- package/dist/resources/guides/middleware.md +3 -1
- package/dist/resources/guides/mobile-publish.md +24 -0
- package/dist/resources/guides/mobile.md +50 -25
- package/dist/resources/guides/page.md +61 -23
- package/dist/resources/guides/theme.md +1 -0
- package/dist/resources/guides/translation.md +41 -1
- package/dist/resources/guides/workflow.md +31 -1
- package/dist/resources/schema-fingerprint.json +3 -3
- package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +2 -1
- package/dist/resources/schemas/OrganizationProjectDto.json +8 -0
- package/dist/resources/schemas/UpsertMobileStoreSettingsCommand.json +59 -0
- package/dist/tools/applications.js +6 -3
- package/dist/tools/common.js +9 -5
- package/dist/tools/entities.js +41 -4
- package/dist/tools/environments.js +1 -1
- package/dist/tools/flows.js +5 -1
- package/dist/tools/layouts.js +22 -7
- package/dist/tools/mobile.js +15 -1
- package/dist/tools/mobileStore.js +21 -5
- package/dist/tools/pages.js +31 -45
- package/dist/tools/queries.js +1 -1
- package/dist/tools/rows.js +14 -3
- package/dist/tools/screenWrite.js +2 -2
- package/dist/tools/screens.js +39 -0
- package/dist/tools/translations.js +148 -6
- package/package.json +1 -1
|
@@ -108,18 +108,18 @@ export const componentEventContracts = {
|
|
|
108
108
|
mount: NO_DATA,
|
|
109
109
|
change: {
|
|
110
110
|
payload: { value: 'FilterGroup (iç içe and/or kural ağacı)', jexl: 'string (aynı ağacın hazır jexl ifadesi)' },
|
|
111
|
-
reads: "setter yalnız `value`yi self’e yazar; `jexl` YALNIZ event.data’dadır (params) — sorguya bağlayacağın alan odur"
|
|
111
|
+
reads: "setter yalnız `value`yi self’e yazar; `jexl` YALNIZ event.data’dadır (params) — sorguya bağlayacağın alan odur. `value` (ağaç) de event.data’da gelir — State’e geri yazmak için event.data.value’yu kullan. Ağaç değişmediyse (etkin birleştiriciye yeniden basmak) olay yayılmaz"
|
|
112
112
|
}
|
|
113
113
|
},
|
|
114
114
|
comments: {
|
|
115
115
|
mount: NO_DATA,
|
|
116
116
|
submit: {
|
|
117
117
|
payload: { text: 'string', parentId: "string (yanıtsa üst yorumun id’si, kök yorumda '')", mentions: 'string[] (metinden çözülmüş @kişi listesi)', author: 'string (currentUser)', process: "{ scope: 'task' | 'instance', targetId, visibility, body (@Ad → @<id>), mentionUserIds } | null (serbest yorumda null)" },
|
|
118
|
-
reads: 'Komponent yorumu ANINDA iyimser olarak listeye ekler (bekleyen katman); kalıcılık akışın işidir — rows
|
|
118
|
+
reads: 'Yük YALNIZ event.data’dadır (self.text/self.mentions boş gelir): {{ event.data.text }}. Komponent yorumu ANINDA iyimser olarak listeye ekler (bekleyen katman); kalıcılık akışın işidir — bekleyen satır, rows’a aynı üst yorum + metin (+ yazar) ile YENİ bir satır gelince yerini ona bırakır; akış kaydetmezse ya da metni değiştirirse "gönderiliyor…" kalır'
|
|
119
119
|
},
|
|
120
|
-
like: { payload: { id: 'string', liked: 'boolean (yeni durum)', author: 'string (currentUser — işlemi yapan)' }, reads: '
|
|
121
|
-
edit: { payload: { id: 'string', text: 'string (yeni metin)', author: 'string (currentUser — işlemi yapan)' }, reads: '
|
|
122
|
-
delete: { payload: { id: 'string', author: 'string (currentUser — işlemi yapan)' }, reads: '
|
|
120
|
+
like: { payload: { id: 'string', liked: 'boolean (yeni durum)', author: 'string (currentUser — işlemi yapan)' }, reads: 'Yük yalnız event.data’dadır. Beğeni de iyimserdir; sayaç rows tazelenene kadar yerel. isDisabled/isReadonly/isViewmode açıkken yayılmaz' },
|
|
121
|
+
edit: { payload: { id: 'string', text: 'string (yeni metin)', author: 'string (currentUser — işlemi yapan)' }, reads: 'Yük yalnız event.data’dadır. Düzenleme de iyimserdir; yeni metin "düzenlendi" işaretiyle rows tazelenene kadar yerel. Metin değişmediyse yayılmaz' },
|
|
122
|
+
delete: { payload: { id: 'string', author: 'string (currentUser — işlemi yapan)' }, reads: 'Yük yalnız event.data’dadır. Satır (kök yorumsa yanıtlarıyla birlikte) listeden hemen gizlenir; uca yazmak akışın işidir' }
|
|
123
123
|
},
|
|
124
124
|
'auth-layout': { mount: NO_DATA, primaryClick: NO_DATA, secondaryClick: NO_DATA },
|
|
125
125
|
layout: { mount: NO_DATA, logout: NO_DATA },
|
|
@@ -354,7 +354,7 @@ export const componentEventContracts = {
|
|
|
354
354
|
'audit-basic': { mount: NO_DATA },
|
|
355
355
|
'audit-history': {
|
|
356
356
|
mount: NO_DATA,
|
|
357
|
-
showMore: { payload: {}, reads: 'yalnız histories visibleLogCount’u aşınca çıkan "Daha fazla göster" düğmesine basılınca; görünür sınır visibleLogCount kadar büyür, kalan kalmayınca düğme kaybolur. Bileşen veri çekmez; backend kayıt başına en fazla 5 işlem verdiği için meta.history bağlıyken düğme ancak visibleLogCount < 5 iken çıkar' }
|
|
357
|
+
showMore: { payload: {}, reads: 'yalnız histories visibleLogCount’u aşınca çıkan "Daha fazla göster" düğmesine basılınca; görünür sınır visibleLogCount kadar büyür, kalan kalmayınca düğme kaybolur; en yeni (ilk) girdi değişince sınır başa döner. Bileşen veri çekmez; backend kayıt başına en fazla 5 işlem verdiği için meta.history bağlıyken düğme ancak visibleLogCount < 5 iken çıkar' }
|
|
358
358
|
},
|
|
359
359
|
'form-validation-errors': { mount: NO_DATA },
|
|
360
360
|
'page-header': {
|
|
@@ -381,7 +381,7 @@ export const componentEventContracts = {
|
|
|
381
381
|
},
|
|
382
382
|
click: {
|
|
383
383
|
payload: {},
|
|
384
|
-
reads:
|
|
384
|
+
reads: "Yalnız akışa bağlıysa çalışır (sarmalayıcı <div>; controls:false iken role=button + Enter/Boşluk da tetikler); controls açıkken videonun YÜZEYİNE tıklamak hem oynatır hem bu olayı tetikler, kontrol çubuğundaki yerel oynat/duraklat düğmesi ise videoyu oynatır AMA bu olayı TETİKLEMEZ (ölçüldü 2026-10-05). isLoading:true iken HİÇ çalışmaz — o dalda oynatıcı yerine iskelet çizilir ve sarmalayıcı tıklanabilir değildir"
|
|
385
385
|
},
|
|
386
386
|
play: {
|
|
387
387
|
payload: { isPlaying: 'boolean (true)' },
|
|
@@ -394,6 +394,14 @@ export const componentEventContracts = {
|
|
|
394
394
|
ended: {
|
|
395
395
|
payload: { isPlaying: 'boolean (false)' },
|
|
396
396
|
reads: 'loop açıkken HİÇ yayılmaz (döngü videosu bitmez). event.data: { currentTime, duration } — bitişte currentTime pratikte duration’a eşittir. Bitişte pause’dan SONRA gelir (spesifikasyon sırası), yani ended akışı pause akışının ardından koşar'
|
|
397
|
+
},
|
|
398
|
+
load: {
|
|
399
|
+
payload: {},
|
|
400
|
+
reads: "Kaynağın İLK KARESİ hazır olunca (loadeddata) yayılır; kaynak her değiştiğinde yeniden. Değer yazmaz (setter yok). event.data: { duration: number (saniye; sonsuz ya da bilinmiyorsa 0), width: number, height: number (kaynağın kendi piksel boyu) }. Yalnız oynatıcı çizilirken yayılır: isLoading:true iken <video> yoktur, load gelmez (iskeleti kaynağı çözen sorgudan kapat). Boş src'de ya da error'da yayılmaz"
|
|
401
|
+
},
|
|
402
|
+
error: {
|
|
403
|
+
payload: {},
|
|
404
|
+
reads: "Kaynak yüklenemediğinde yayılır (404, video olmayan dosya, desteklenmeyen biçim, ağ kopması). Değer yazmaz, ama isPlaying true ise false'a düşer. event.data: { code: number (MediaError.code: 1 iptal · 2 ağ · 3 çözümleme · 4 kaynak yok/desteklenmiyor), message: string (tarayıcıya bağlı, çoğu kez boş) }. Bileşen kendi hata görünümü çizmez: siyah kutu kalır, kullanıcıya bildirmek akışın işidir. Boş src'de yayılmaz"
|
|
397
405
|
}
|
|
398
406
|
},
|
|
399
407
|
// — 2026-08-25 eşleme turu: ng-apps-ui'da olay YAYAN ama burada kaydı olmayan komponentler.
|
|
@@ -672,7 +680,7 @@ export const componentEventContracts = {
|
|
|
672
680
|
fileName: 'string (okunan dosyanın adı; henüz dosya yoksa "")',
|
|
673
681
|
rowCount: 'number (dosyadaki HAM veri satırı sayısı — doğrulamadan önce)'
|
|
674
682
|
},
|
|
675
|
-
reads: "adım her değiştiğinde yayılır; currentStep ve fileName düğüme de yazılır (self.currentStep), rowCount yalnız event.data'dadır"
|
|
683
|
+
reads: "adım her değiştiğinde yayılır; currentStep ve fileName düğüme de yazılır (self.currentStep), rowCount yalnız event.data'dadır. resetAfterComplete açıkken complete'ten hemen sonra { currentStep: 0, fileName: tamamlanan dosya, rowCount: 0 } ile bir kez daha yayılır"
|
|
676
684
|
},
|
|
677
685
|
complete: {
|
|
678
686
|
payload: {
|
|
@@ -790,6 +798,39 @@ export const componentEventContracts = {
|
|
|
790
798
|
reads: "self'e yazılmaz — event.data ile oku; okumayanlara bildirim gönderen akışı çağır"
|
|
791
799
|
}
|
|
792
800
|
},
|
|
801
|
+
'version-compare': {
|
|
802
|
+
mount: NO_DATA,
|
|
803
|
+
pairChange: {
|
|
804
|
+
payload: { oldId: 'string | number', newId: 'string | number' },
|
|
805
|
+
reads: "setter: oldId/newId self'e yazılır; event.data = { oldId, newId } — akış yeni çiftin files/fields/contextNote'unu yeniler"
|
|
806
|
+
},
|
|
807
|
+
fileRequest: {
|
|
808
|
+
payload: { id: 'string | number (dosyası gereken sürüm)' },
|
|
809
|
+
reads: "self'e yazılmaz — event.data.id ile oku; akış dosyayı alıp files[id] = { name, type, data, size } yazar, alamazsa files[id].error = { message, at } yazar (at: deneme zamanı). Tekrar dene fileRequest'i yeniden yayar ve files[id]'nin İÇERİĞİ öncekinden farklı olana dek bekler — hata kaydı her denemede ayırt edilebilir olmalı (aynı { error: true } yeniden yazılırsa kart beklemede kalır). Dosya gelip sonra files'tan düşerse yeniden istenir"
|
|
810
|
+
},
|
|
811
|
+
openVersion: { payload: { id: 'string | number' }, reads: "self'e yazılmaz — event.data.id ile oku" },
|
|
812
|
+
summary: {
|
|
813
|
+
payload: { all: 'number', add: 'number', del: 'number', mod: 'number' },
|
|
814
|
+
reads: "self'e yazılmaz — event.data ile oku; karşılaştırma bitince bir kez — karşılaştırılamaz sonuçta (görsel, taranmış pdf) yayılmaz"
|
|
815
|
+
},
|
|
816
|
+
selectionChange: {
|
|
817
|
+
payload: { selected: 'number (değişikliğin n’si)', section: 'string (değişikliğin bölümü / grubu)', kind: "'add' | 'del' | 'mod'" },
|
|
818
|
+
reads: "kendi gezinmede setter: yalnız selected self'e yazılır; event.data = { selected, section, kind } — okuma modunda görev çubuğunun 'Seçili: <bölüm> · <tür>' satırı. Açılışta / çift değişince (ilk seçim) ve selected dışarıdan yazılıp seçim değişince de yayılır, setter'sız (self'e yazılmaz); aynı seçim yeniden yayılmaz — akış selected'ı geri yazınca döngü olmaz"
|
|
819
|
+
},
|
|
820
|
+
inlineViewChange: {
|
|
821
|
+
payload: { inlineView: "'marked' | 'standard'" },
|
|
822
|
+
reads: "setter: inlineView self'e yazılır; event.data = { inlineView } — inline kipte 'İşaretli / Standart görünüm' anahtarı; sayfa standard'da kendi görüntüleyicisini gösterir, durumu (state) akış tutar"
|
|
823
|
+
},
|
|
824
|
+
inlineToolClick: {
|
|
825
|
+
payload: { key: 'string (basılan inlineTools düğmesinin ya da seçeneği seçilen menu öğesinin key’i)', item: 'string? (yalnız menu öğesinde: seçilen options[].key)' },
|
|
826
|
+
reads: "self'e yazılmaz — event.data.key (menu öğesinde ayrıca event.data.item) ile oku; inline satırındaki uygulama düğmesi (ör. yakınlaştır, indir, yazdır, tam ekran) ya da menu öğesinin seçeneği (ör. versiyon). Sağ grup sığmayınca '⋯' listesinden seçilen düğme satırdakiyle AYNI yükü yayar (ADR 0020). isDisabled düğme / seçenek yaymaz; text / separator öğesi basılmaz"
|
|
827
|
+
},
|
|
828
|
+
close: { payload: {}, reads: 'yük yok — kapat düğmesi, Esc ya da yüklemede Vazgeç' },
|
|
829
|
+
error: {
|
|
830
|
+
payload: { id: 'string | number? (karşılaştırma worker’ı çökerse ya da karşılaştırmanın kendisi hata verirse yok)', kind: "'fetch' | 'read' | 'unsupported' | 'tooMany'", message: 'string? (fetch: files[id].error.message ya da HTTP durumu; read: çözme/okuma hatası; unsupported: dosya türü; tooMany: değişiklik sayısı 5000 sınırını aştı — kimliksiz, kart gösterilir)' },
|
|
831
|
+
reads: "self'e yazılmaz — event.data ile oku"
|
|
832
|
+
}
|
|
833
|
+
},
|
|
793
834
|
'case-view': {
|
|
794
835
|
mount: NO_DATA,
|
|
795
836
|
action: {
|
|
@@ -375,7 +375,11 @@ export function buildDefinition(input, describe, connectable, connectablePaths =
|
|
|
375
375
|
for (const slug of (keptSlugs(input.edit?.editable) ?? []).filter(item => describe.fields.find(field => field.slug === item)?.type === 'file')) {
|
|
376
376
|
block('CD-018', `'${slug}' dosya alanı: edit.editable'a konamaz — bağlı bileşen dosyayı satır içinde yazmaz, platform kaydı 422 connect-data-editable-file ile reddeder. '${slug}'i editable'dan çıkarın; dosyayı file_upload + row_update (ya da bir microservice) yazar.`);
|
|
377
377
|
}
|
|
378
|
-
|
|
378
|
+
// qm S-12: view kaynaklı microservice'te (describe source.textSearch, MicroserviceConnectDataSourceExecutor) alanlar süzülemez
|
|
379
|
+
// ama arama vardır — ConnectDataService request.Search'ü uca geçirir; searchable alanı ise kayıtta 422 alır. Genel
|
|
380
|
+
// "süzülemez/aranamaz" mesajı ajanı çıkmaza sokuyordu. View kaynağında aynı yolu CD-013 söyler (connectDataShapeIssues).
|
|
381
|
+
const textSearchSource = input.source.kind === 'microservice' && describe.source?.textSearch === true;
|
|
382
|
+
for (const slug of [...(keptSlugs(input.filterable) ?? []), ...(textSearchSource ? [] : keptSlugs(input.searchable) ?? [])]) {
|
|
379
383
|
const field = describe.fields.find(item => item.slug === slug);
|
|
380
384
|
if (field && (field.canFilter === false || (field.operators ?? []).length === 0)) {
|
|
381
385
|
block('CD-002', `'${slug}' süzülemez (canFilter yok ya da işleç yok); filterable/searchable'a konamaz.`);
|
|
@@ -389,7 +393,11 @@ export function buildDefinition(input, describe, connectable, connectablePaths =
|
|
|
389
393
|
}
|
|
390
394
|
for (const slug of keptSlugs(input.searchable) ?? []) {
|
|
391
395
|
const field = describe.fields.find(item => item.slug === slug);
|
|
392
|
-
|
|
396
|
+
const searchableField = field?.type === 'string' && !field.isProtected && (field.operators ?? []).includes('icontains');
|
|
397
|
+
if (field && textSearchSource && !searchableField) {
|
|
398
|
+
block('CD-002', `'${slug}' aranamaz: '${describe.source?.slug ?? describe.source?.id}' metin aramalı bir kaynak (describe source.textSearch) — arama searchable ile değil, istekteki search ile sunucuda çalışır; platform searchable alanı kayıtta 422 connect-data-source-feature-unsupported ile reddeder. searchable'ı boş bırakın; arama metnini platform istekteki search ile uca geçirir ve uç metni kendisi arar.`);
|
|
399
|
+
}
|
|
400
|
+
else if (field && field.canFilter !== false && !searchableField) {
|
|
393
401
|
block('CD-002', `'${slug}' aranamaz: arama yalnız korumasız, içerir (icontains) işleci olan metin alanında çalışır — platform bu alanı aramada YOK SAYAR (geçerli hiç alan kalmazsa 422 ConnectDataFilterNotAllowed). Aranabilir bir metin alanı seçin.`);
|
|
394
402
|
}
|
|
395
403
|
}
|
|
@@ -160,7 +160,7 @@ export const mobileComponentKnowledge = {
|
|
|
160
160
|
differences: [
|
|
161
161
|
'`isIndeterminate` `value`yu bastırır ve tire (`remove` glifi) çizer; belirsiz kutuya dokunmak onu seçili yapar.',
|
|
162
162
|
'Etikete dokunmak da kutuyu çevirir (basış satırın `Pressable`ında toplanır).',
|
|
163
|
-
'`page_screenshot` kutuyu `<elementId>__press`
|
|
163
|
+
'`page_screenshot` kutuyu `<elementId>__press` yüzeyinden çevirir (düğmedeki gibi); kök kimliğe `click` de o yüzeye basar (SCR-015).',
|
|
164
164
|
'`labelPosition` yalnız `left`/`right`; `left` seçilince etiket ile kutu satırın iki ucuna yaslanır.',
|
|
165
165
|
'Düşenler: `autofocus` ve `focus`/`blur` olayları (onay kutusunun klavye odağı yok, tek sinyal değer değişimidir).'
|
|
166
166
|
]
|
|
@@ -168,7 +168,7 @@ export const mobileComponentKnowledge = {
|
|
|
168
168
|
switch: {
|
|
169
169
|
summary: 'Açma/kapama anahtarı; shadcn `Switch` ilkeli (`@rn-primitives/switch`) — üç barındırıcıda AYNI görünüm.',
|
|
170
170
|
differences: [
|
|
171
|
-
'`page_screenshot` anahtarı `<elementId>__press`
|
|
171
|
+
'`page_screenshot` anahtarı `<elementId>__press` (`role="switch"`) yüzeyinden çevirir; kök kimliğe `click` de o yüzeye basar (SCR-015).',
|
|
172
172
|
'Platformun kendi anahtarı (iOS/Android `Switch`) KULLANILMAZ: açıkken zemin `--primary`, kapalıyken `--input`, baş `--background`; kanvas, önizleme ve cihaz aynı bileşeni çizer.',
|
|
173
173
|
'`isReadonly` ve `isDisabled` anahtarı kilitler ve soldurur; `isDisabled` etiketi de soldurur. Tasarım kanvasında anahtar basılamaz olduğu için HER anahtar soluk çizilir — önizlemede ve cihazda değil.',
|
|
174
174
|
'Düşenler: `autofocus` ve `focus`/`blur` olayları (anahtarın klavye odağı yok, tek sinyal değer değişimidir).'
|
|
@@ -179,7 +179,7 @@ export const mobileComponentKnowledge = {
|
|
|
179
179
|
differences: [
|
|
180
180
|
'`presentation` (mobile özgü): `auto` (varsayılan) sekiz ve daha az seçenekli, aramasız listeyi alanın hemen ALTINDA açar (altta yer yoksa ya da klavye açıksa üstte), öbürünü ekranın altından açılan sayfada; `sheet` hep sayfa, `inline` hep alan altı (arama varsa sayfa).',
|
|
181
181
|
'Sayfada dokuz ve daha çok seçenek ya da `search` akışa bağlıyken tepede arama kutusu durur (iç rol `<elementId>__search`): bağlıysa `search { searchText }` yayar ve listeyi akış yazar, değilse yerelde süzer. Açılış `focus`, kapanış `blur` yayar.',
|
|
182
|
-
'Seçenek görseli web `SelectItem` önceliğiyle: `prefixImage` > `prefixText` (zemini `color`) > `icon` (rengi `color`); seçiliyken kutuda da çizilir. Gizli seçenek (`isHidden`) çizilmez. `isViewmode` kutuyu kilitler, ok ve temizleme düğmesini kaldırır. Düşenler: `options[].tooltip`, `isExpanded`, `isSelectable
|
|
182
|
+
'Seçenek görseli web `SelectItem` önceliğiyle: `prefixImage` > `prefixText` (zemini `color`) > `icon` (rengi `color`); seçiliyken kutuda da çizilir. Gizli seçenek (`isHidden`) çizilmez. `isViewmode` kutuyu kilitler, ok ve temizleme düğmesini kaldırır. Düşenler: `options[].tooltip`, `isExpanded`, `isSelectable` (ağaç ve üzerine gelme yok). Mobil şema `lookupMode`’u web sınıfından devralır ama mobil çizici onu OKUMAZ: ↗ lookup penceresi açılmaz, yazılan değer etkisizdir.',
|
|
183
183
|
'Tetikleyici shadcn `Input` görünümündedir (`--input` kenarı; yükseklik, köşe ve punto öteki alanlarla aynı `--ng-form-*` ölçüsü); liste `--popover` yüzeyinde, seçili satırda `check` glifi. Tetikleyici `role="combobox"` taşır (iç rol `<elementId>__trigger`, temizleme düğmesi `<elementId>__clear`), seçilebilir satırlar `role="option"` + `data-value`; liste katmanda açıldığı için `page_screenshot` `select` seçeneği belgede arar ve değerle ya da görünen metinle seçer.',
|
|
184
184
|
'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.'
|
|
185
185
|
]
|
|
@@ -471,7 +471,7 @@ export const mobileComponentKnowledge = {
|
|
|
471
471
|
differences: [
|
|
472
472
|
'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).',
|
|
473
473
|
'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`.',
|
|
474
|
-
'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-
|
|
474
|
+
'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-416).',
|
|
475
475
|
"`click` bir akışa bağlıysa kod runtime'da basılır (web gibi); bağlı değilse dokunuşu yutmaz. QR'a sığmayan veri `error { errorText }`ü makine başına bir kez yayar (satırdaki seçim değişimi yeniden yaydırmaz)."
|
|
476
476
|
],
|
|
477
477
|
propNotes: {
|
|
@@ -494,7 +494,7 @@ export const mobileComponentKnowledge = {
|
|
|
494
494
|
'list-item': {
|
|
495
495
|
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.',
|
|
496
496
|
differences: [
|
|
497
|
-
'
|
|
497
|
+
'Satır yalnız `click` bir akışa BAĞLIYKEN basılır ve `click` yayar (yük yok); bağsız satır düz kutudur ve dokunuşu yutmaz — `repeater`/`grouped-list` şablonunda basış listenin `itemPress`ine gider (2026-10-08). Pasifken soluk ve basılamaz; tasarım kipinde basılmaz (basış kanvasın seçim jestidir).',
|
|
498
498
|
'Baş tarafta `leadingImage` `leadingIcon`dan önce gelir (40 dp yuvarlak görsel; yüklenmezse başlığın baş harfleri). Boş ikon çizilmez.',
|
|
499
499
|
"`leadingImage` `image` ile aynı çözümden geçer: kayıttaki FILE alanını nesne olarak verin (`{{ item.foto }}`) ya da `.absolute_path` yazın; `.value` dosya kimliğidir (GUID), görsel çizilmez (MOB-011).",
|
|
500
500
|
'Ö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`).',
|
|
@@ -553,7 +553,7 @@ export const mobileComponentKnowledge = {
|
|
|
553
553
|
'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
554
|
'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
555
|
'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
|
-
'Öğe şablonu basılabilir bir bileşense (`list-item
|
|
556
|
+
'Öğ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
557
|
'Ç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.',
|
|
558
558
|
'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
559
|
],
|
|
@@ -615,7 +615,7 @@ export const mobileComponentKnowledge = {
|
|
|
615
615
|
'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).',
|
|
616
616
|
'Ü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`.',
|
|
617
617
|
'Yerel arama (akış bağlı değilken) `dropdown` gibi veri dilinin yereliyle ve işaretsiz katlanır: İngilizce uygulamada "in" "India"yı, "suk" "Sükkülent"i bulur (2026-09-29; öncesinde hep `tr-TR`).',
|
|
618
|
-
'Satırın görseli web `SelectItem` önceliğiyle: `prefixImage` > `prefixText` (zemini `color`) > `icon` (rengi `color`). Düşenler: `options[].tooltip`, `isExpanded
|
|
618
|
+
'Satırın görseli web `SelectItem` önceliğiyle: `prefixImage` > `prefixText` (zemini `color`) > `icon` (rengi `color`). Düşenler: `options[].tooltip`, `isExpanded` (gruplar hep açık; üzerine gelme yok). Mobil şema `lookupMode`’u web sınıfından devralır ama mobil çizici onu OKUMAZ: ↗ lookup penceresi açılmaz, yazılan değer etkisizdir.'
|
|
619
619
|
]
|
|
620
620
|
},
|
|
621
621
|
toast: {
|
|
@@ -768,7 +768,7 @@ export const mobileComponentKnowledge = {
|
|
|
768
768
|
'Değer web gibi `{ value: base64 veri URL`si, fileName, type: "file" }` (çoklu kipte dizi, yeni seçim üstüne eklenir; boşaltma `{ type: "ref", value: null }`, çoklu kipte `[{ type: "ref", value: null }]`) — backend `TableFileParameter { value, type: ref|file|document, fileName }` biçiminin aynısı: `file` depoya yazılır, `ref` var olan dosyayı bağlar, `null` alanı boşaltır. Base64 sayfa durumunda ve istek gövdesinde taşındığı için `maxFileSizeMb` (mobil varsayılan 10) sıfırlanmaz. Kamera/galeri fotoğrafı uzun kenarı `maxImageDimension`a (varsayılan 1600 px; 0 = kapalı) indirilip JPEG olarak yeniden kodlanır — boyut kapısı küçültülmüş dosyaya bakar; belge seçiciden gelen dosyaya dokunulmaz. Fotoğraf arşivinden gelen HEIC iOS`ta JPEG`e çevrilir (`.jpg` — web`de Safari`nin yaptığı gibi), fotoğraflar `quality` ile sıkıştırılır. Görsel seçimler alanda küçük resimle görünür.',
|
|
769
769
|
'Tür/boyut/adet kapısı web kuralıdır (tek uyarı, tür ihlali önce; uyarı metni uygulama dilinde). İç roller: kutu `<elementId>__field`, Gözat `__browse`, temizle `__clear`, çoklu kipte satır silme `__file-<n>__delete`. `isReadonly`/`isViewmode` Gözat`ı ve X`i gizler.',
|
|
770
770
|
'Düşen: `style`.',
|
|
771
|
-
'f-13 (mobile özgü görünüm ve düzenleme): görsel dosya satırda 48 px küçük resimdir; tekli değerde satır "Değiştir" (`__file-0__change`, seçici yeniden açılır, değer yerinde değişir) ve kaldır taşır. `variant: tile` 1:1 kesikli "+ Ekle" karosu (`__add`) ve üç sütunlu küçük resim ızgarasıdır: dolu karoya basış değiştirir (`__file-<n>__change`), köşedeki × siler (`__file-<n>__delete`). `enableCrop` + `cropAspectRatio` cihazda sistem kırpıcısını açar (yalnız tek seçimde; iOS serbest oranı kesin uygulamaz; önizlemede kırpma yok), `rotateOnPick` (0/90/180/270) seçilen fotoğrafı döndürür. Kamera izni verilmezse değer değişmez, alanın altında Ayarlar bağlantılı not çıkar (`__settings`); alan geçersiz sayılmaz. Fotoğraf arşivi izin istemez (iOS PHPicker, Android sistem seçicisi).'
|
|
771
|
+
'f-13 (mobile özgü görünüm ve düzenleme): görsel dosya satırda 48 px küçük resimdir; tekli değerde satır "Değiştir" (`__file-0__change`, seçici yeniden açılır, değer yerinde değişir) ve kaldır taşır. `variant: tile` 1:1 kesikli "+ Ekle" karosu (`__add`) ve üç sütunlu küçük resim ızgarasıdır: dolu karoya basış değiştirir (`__file-<n>__change`), köşedeki × siler (`__file-<n>__delete`). `enableCrop` + `cropAspectRatio` cihazda sistem kırpıcısını açar (yalnız tek seçimde; iOS serbest oranı kesin uygulamaz; önizlemede kırpma yok), `rotateOnPick` (0/90/180/270) seçilen fotoğrafı döndürür. Kamera izni verilmezse değer değişmez, alanın altında Ayarlar bağlantılı not çıkar (`__settings`); alan geçersiz sayılmaz. Kamera iznini taşımayan derlemede (`source: camera`) izin istenmez, not Ayarlar bağlantısız "bu uygulamada kamera kullanılamıyor" der. Fotoğraf arşivi izin istemez (iOS PHPicker, Android sistem seçicisi).'
|
|
772
772
|
],
|
|
773
773
|
propNotes: {
|
|
774
774
|
source: 'Dosyanın geldiği yer: `files` (belgeler, varsayılan), `library` (fotoğraf arşivi), `camera`, `ask` (Kamera / Fotoğraflar / Dosyalar sorulur). `library` ve `camera` yalnız fotoğraf verir.',
|
|
@@ -797,7 +797,7 @@ export const mobileComponentKnowledge = {
|
|
|
797
797
|
'Cihazda `react-native-maps`: iOS Apple Haritalar, Android Google Haritalar (Expo Go`da anahtarsız; geliştirme/EAS derlemesinde Android Google anahtarı ve konum izin metinleri gerekir — ARC-128). Kanvas ve önizlemede Leaflet + OpenStreetMap karoları (anahtarsız, köşede OSM atfı; OSM`nin ortak karo sunucusu yoğun trafiğe açık değildir). Kanvasta dokunuş almaz ve sürüklenmez; karolar ve işaretçiler çizilir.',
|
|
798
798
|
'Görünüm üç sayıdır: `latitude`, `longitude`, `zoom` (web harita ölçeği 1–19, kesirli olabilir; cihazda Apple/Google bölgesine çevrilir). Nesne `center` prop`u YOKTUR: bağlanan düz nesne runtime`da bozulur (ARC-117).',
|
|
799
799
|
'Akış `latitude`/`longitude`/`zoom` yazınca harita oraya gider (web`de anında, cihazda kısa canlandırmayla) ve `regionChange` YAYMAZ. Kullanıcı sürükleyip yakınlaştırınca bıraktığı görünüm `regionChange { latitude, longitude, zoom }` ile düğüme geri yazılır (`carousel.activeIndex` gibi: prop`a bağlı ifade o an ezilir). Kutu ya da pencere boyunun değişmesi olay sayılmaz.',
|
|
800
|
-
'İşaretçiye dokunmak `markerClick { markerId }` yayar ve başlığı ya da açıklaması varsa balon açar. Harita işaretçiye ORTALANMAZ; kenardaki işaretçinin balonu sığmazsa harita sığdıracak kadar kayar ve bu kayma `regionChange` yayar (iOS ve web`de ölçüldü). Üst üste binen işaretçilerde iOS`ta `markerId` açılan balonunkinden farklı olabilir (ARC-
|
|
800
|
+
'İşaretçiye dokunmak `markerClick { markerId }` yayar ve başlığı ya da açıklaması varsa balon açar. Harita işaretçiye ORTALANMAZ; kenardaki işaretçinin balonu sığmazsa harita sığdıracak kadar kayar ve bu kayma `regionChange` yayar (iOS ve web`de ölçüldü). Üst üste binen işaretçilerde iOS`ta `markerId` açılan balonunkinden farklı olabilir (ARC-265). İşaretçi rengi temanın ana rengidir. Döndürme ve eğme kapalıdır (ölçek kuzey yukarı görünümün ölçeğidir).',
|
|
801
801
|
'`interactive: false` sürüklemeyi ve yakınlaştırmayı kapatır, işaretçiler dokunulabilir kalır ve sayfa haritanın üstünden kayar. Açıkken haritanın üstündeki dikey sürükleme haritayı kaydırır, sayfayı değil.',
|
|
802
802
|
'"Haritada aç" (adres önizlemesi): işaretçi dışına dokunuş `press { latitude, longitude, mapsUrl }` yayar ve düğüme yalnız `mapsUrl` yazılır. Yer tek işaretçiyse o işaretçi (adıyla), değilse görünümün merkezidir. Akış `redirect` `isRaw: true`, `path: \'<harita>.mapsUrl\'` ile açar: iOS Apple Haritalar, Android `geo:` (kullanıcının harita uygulaması), web Google Haritalar. Ayrı bir adım yoktur.',
|
|
803
803
|
'Kutu `webview` kuralından: kabuktan gelen boy ya da panelin `aspectRatio`su, yoksa 300 dp.'
|
|
@@ -811,6 +811,18 @@ export const mobileComponentKnowledge = {
|
|
|
811
811
|
showUser: 'Kullanıcının konumunu çizer. Cihazda iOS izni MapKit sorar, Android`de bileşen çalışma anı izni ister; web`de tarayıcı konum izni. Yalnız runtime`da. "Konumum" düğmesi yoktur (Android`inki kamerayı `regionChange` yaymadan taşırdı); konuma gitmek akışın `latitude`/`longitude` yazmasıdır.'
|
|
812
812
|
}
|
|
813
813
|
},
|
|
814
|
+
adBanner: {
|
|
815
|
+
summary: 'Google Mobile Ads banner`ı (kök ADR 0012). Web`de karşılığı YOKTUR. Reklam yalnız MAĞAZA sürümünde çizilir; kanvas, önizleme, `/view` ve Zeyrek Mobile oynatıcısı boyutlu kesikli yer tutucu ("Ad · <yerleşim> · 320×50") çizer.',
|
|
816
|
+
differences: [
|
|
817
|
+
'`unit` mağaza ayarındaki reklam yerleşiminin ADIDIR (`mobile_store_update` `settingsPatch.ads.units[].name`), AdMob birim kimliği DEĞİL: kimlik derlemenin platformuna göre manifestten çözülür. Yerleşim `banner` biçiminde olmalı; tam ekran yerleşimi (`interstitial`, `rewarded`…) banner çizmez — onlar `ad-show` adımının işidir.',
|
|
818
|
+
'Mağaza sürümünde sıra: rıza formu (UMP) → açılış izinleri (iOS ATT) → SDK başlatma; o bitene kadar ve reklam kapalı/rıza yokken bileşen YER KAPLAMAZ ve olay yaymaz. iOS`ta izleme izni verilmemişse reklam kişiselleştirilmeden istenir. Mağaza ayarında `testMode` açıkken Google test birimi çizilir ("Test Ad").',
|
|
819
|
+
'Yerleşim bu platformda yoksa ya da banner değilse yer kaplamaz ve `fail { code: \'unavailable\' }` yayar; oynatıcıda ve web`de de çalışma kipinde aynı olay yayılır (reklamsız akışı orada da sınayın). Yüklenemeyen reklam (`no-fill`) kutusunu kaldırır: boş kutu kullanıcıya hata gibi görünür.'
|
|
820
|
+
],
|
|
821
|
+
propNotes: {
|
|
822
|
+
unit: 'Reklam yerleşiminin adı (`^[a-z][A-Za-z0-9]{0,39}$`, mağaza ayarında tanımlı). Panel serbest metindir — yanlış ad cihazda boş kutu + `fail` verir.',
|
|
823
|
+
size: '`banner` 320×50 (varsayılan) · `largeBanner` 320×100 · `mediumRectangle` 300×250 · `fullBanner` 468×60 · `leaderboard` 728×90 (tablet) · `adaptive` kabın genişliğine uyarlı çapa banner · `inlineAdaptive` içerik arası uyarlı. Yer tutucunun ölçüsü bu tablodandır; uyarlanır boyların gerçek yüksekliğini SDK belirler.'
|
|
824
|
+
}
|
|
825
|
+
},
|
|
814
826
|
datatable: {
|
|
815
827
|
summary: 'Veri tablosu (FlutterFlow DataTable); prop`lar ve akış sözleşmesi web ikizininki: sıralama ya da sayfa değişince sorgu prop`ları (`sortField`/`sortType`/`offset`) düğüme yazılır ve `mount` yeniden koşar, satır eylemi önce `selectedRow`u yazar sonra eylemin kendi akışını koşturur (yazım web gibi sessizdir, `silentUpdate: [elementId]`: tablo yeniden çizilmez, kaydırma konumu korunur). Hücre biçimi, görünür kolonlar, sıralama döngüsü ve sayfa hesabı web`in modülünden gelir (`renderCell`, `buildViewColumns`, `nextSort`, `computePageState`): iki platform aynı veriden aynı metni üretir.',
|
|
816
828
|
differences: [
|
|
@@ -111,6 +111,26 @@ export const mobileComponentEventContracts = {
|
|
|
111
111
|
reads: "işaretçi dışına dokunuşta yayılır (interactive: false önizlemede bütün yüzey). Makine YALNIZ mapsUrl'i düğüme yazar (self.mapsUrl) — koordinat yazılsaydı harita kayardı; koordinat event.data'dadır. Tarif: redirect isRaw: true, path: '<harita>.mapsUrl'"
|
|
112
112
|
}
|
|
113
113
|
},
|
|
114
|
+
adBanner: {
|
|
115
|
+
mount: NO_DATA,
|
|
116
|
+
load: {
|
|
117
|
+
payload: {
|
|
118
|
+
width: 'number — yüklenen reklamın genişliği (dp)',
|
|
119
|
+
height: 'number — yüklenen reklamın yüksekliği (dp)'
|
|
120
|
+
},
|
|
121
|
+
reads: 'yalnız mağaza sürümünde, reklam yüklenip çizilince yayılır'
|
|
122
|
+
},
|
|
123
|
+
fail: {
|
|
124
|
+
payload: {
|
|
125
|
+
code: "string — SDK hata kodu (`googleMobileAds/no-fill`, `googleMobileAds/network-error`…) ya da `unavailable`: derleme reklam taşımıyor (oynatıcı, web), ya da yerleşim bu platformda yok / banner değil",
|
|
126
|
+
message: 'string — hatanın açıklaması'
|
|
127
|
+
},
|
|
128
|
+
reads: "reklam çizilemeyince yayılır; kutu yer kaplamaz. Reklam alanını akışla gizlemek ya da yerine içerik koymak için kullanın. Mağaza sürümünde rıza yokken ya da SDK henüz başlamamışken YAYILMAZ"
|
|
129
|
+
},
|
|
130
|
+
click: { payload: {}, reads: 'kullanıcı reklama dokununca (yalnız mağaza sürümü)' },
|
|
131
|
+
open: { payload: {}, reads: 'reklam ekranı kaplayan bir katman açınca' },
|
|
132
|
+
close: { payload: {}, reads: 'kullanıcı reklam katmanından uygulamaya dönünce' }
|
|
133
|
+
},
|
|
114
134
|
markdown: {
|
|
115
135
|
mount: NO_DATA,
|
|
116
136
|
linkClick: {
|
|
@@ -160,7 +180,7 @@ export const mobileComponentEventContracts = {
|
|
|
160
180
|
index: 'number (öğenin rows içindeki düz indeksi, 0 tabanlı)',
|
|
161
181
|
row: 'object (öğenin satır verisi)'
|
|
162
182
|
},
|
|
163
|
-
reads: 'yalnız itemPressable açıkken; önce change { selectedIndex } yayılır. Öğe şablonu basılabilir bir bileşense (list-item
|
|
183
|
+
reads: 'yalnız itemPressable açıkken; önce change { selectedIndex } yayılır. Öğe şablonu basılabilir bir bileşense (button, click\'i bağlı list-item) dokunuş onun click\'ine gider, itemPress yayılmaz; click\'i bağlı olmayan list-item basılamaz, dokunuş itemPress\'e gider'
|
|
164
184
|
},
|
|
165
185
|
swipe: {
|
|
166
186
|
payload: {
|
|
@@ -16,6 +16,14 @@ export const SPLASH_RESIZE_MODES = ['contain', 'cover'];
|
|
|
16
16
|
export const RELEASE_STATUSES = ['draft', 'completed'];
|
|
17
17
|
/** İznin mağaza derlemesindeki kipi (kök ADR 0010): kullanıma göre · her zaman · hiç. Anahtarları backend doğrular. */
|
|
18
18
|
export const PERMISSION_MODES = ['auto', 'on', 'off'];
|
|
19
|
+
/** Reklam ayarı (kök ADR 0012) — backend `MobileStoreSettingsValidator` / `MobileAdFormat` aynası. */
|
|
20
|
+
export const AD_APP_ID_PATTERN = /^ca-app-pub-\d{16}~\d{10}$/;
|
|
21
|
+
export const AD_UNIT_ID_PATTERN = /^ca-app-pub-\d{16}\/\d{10}$/;
|
|
22
|
+
export const AD_UNIT_NAME_PATTERN = /^[a-z][A-Za-z0-9]{0,39}$/;
|
|
23
|
+
export const AD_UNITS_MAX_COUNT = 40;
|
|
24
|
+
export const AD_FORMATS = ['banner', 'interstitial', 'rewarded', 'rewardedInterstitial', 'appOpen'];
|
|
25
|
+
/** Reklam açıkken taşınması gereken izin: iOS ATT (backend `MobileStoreSettings.AdsPermission`). */
|
|
26
|
+
export const ADS_PERMISSION = 'tracking';
|
|
19
27
|
export const LISTING_LIMITS = {
|
|
20
28
|
title: 30,
|
|
21
29
|
subtitle: 30,
|
|
@@ -200,6 +208,36 @@ export function storeSettingsFormatErrors(settings) {
|
|
|
200
208
|
add('review.email', 'geçerli e-posta olmalı');
|
|
201
209
|
if (text(apple.copyright).length > COPYRIGHT_MAX_LENGTH)
|
|
202
210
|
add('apple.copyright', `en çok ${COPYRIGHT_MAX_LENGTH} karakter`);
|
|
211
|
+
const ads = isObject(settings.ads) ? settings.ads : {};
|
|
212
|
+
const units = (Array.isArray(ads.units) ? ads.units : []).filter(isObject);
|
|
213
|
+
const appIdExample = 'ca-app-pub-0000000000000000~0000000000';
|
|
214
|
+
const unitIdExample = 'ca-app-pub-0000000000000000/0000000000';
|
|
215
|
+
for (const field of ['iosAppId', 'androidAppId']) {
|
|
216
|
+
if (!isBlank(ads[field]) && !AD_APP_ID_PATTERN.test(text(ads[field])))
|
|
217
|
+
add(`ads.${field}`, `AdMob App ID biçiminde olmalı (${appIdExample}; birim kimliğindeki '/' değil '~')`);
|
|
218
|
+
}
|
|
219
|
+
if (ads.enabled === true && isBlank(ads.iosAppId) && isBlank(ads.androidAppId))
|
|
220
|
+
add('ads.enabled', 'reklam açıkken en az bir platformun AdMob App ID\'si gerekir (ads.iosAppId / ads.androidAppId)');
|
|
221
|
+
if (units.length > AD_UNITS_MAX_COUNT)
|
|
222
|
+
add('ads.units', `en çok ${AD_UNITS_MAX_COUNT} yerleşim`);
|
|
223
|
+
const invalidNames = [...new Set(units.map(unit => text(unit.name)).filter(name => !AD_UNIT_NAME_PATTERN.test(name) || units.filter(unit => text(unit.name) === name).length > 1))];
|
|
224
|
+
for (const name of invalidNames)
|
|
225
|
+
add(`ads.units.${name}`, `yerleşim adı ${AD_UNIT_NAME_PATTERN.source} biçiminde ve tekil olmalı (adBanner.unit ve ad-show.value bu adla anar)`);
|
|
226
|
+
for (const unit of units.filter(entry => !invalidNames.includes(text(entry.name)))) {
|
|
227
|
+
const name = text(unit.name);
|
|
228
|
+
if (!AD_FORMATS.includes(text(unit.format)))
|
|
229
|
+
add(`ads.units.${name}.format`, `kabul edilen biçimler: ${AD_FORMATS.join(' | ')}`);
|
|
230
|
+
for (const platform of ['ios', 'android']) {
|
|
231
|
+
if (!isBlank(unit[platform]) && !AD_UNIT_ID_PATTERN.test(text(unit[platform])))
|
|
232
|
+
add(`ads.units.${name}.${platform}`, `AdMob reklam birimi kimliği biçiminde olmalı (${unitIdExample})`);
|
|
233
|
+
}
|
|
234
|
+
if (isBlank(unit.ios) && isBlank(unit.android))
|
|
235
|
+
add(`ads.units.${name}`, 'en az bir platformun reklam birimi kimliği gerekir (ios / android)');
|
|
236
|
+
}
|
|
237
|
+
if (!isBlank(ads.appOpenUnit) && text(units.find(unit => text(unit.name) === text(ads.appOpenUnit))?.format) !== 'appOpen')
|
|
238
|
+
add('ads.appOpenUnit', `'${text(ads.appOpenUnit)}' appOpen biçiminde tanımlı bir yerleşim değil`);
|
|
239
|
+
if (ads.enabled === true && isObject(settings.permissionModes) && settings.permissionModes[ADS_PERMISSION] === 'off')
|
|
240
|
+
add(`permissionModes.${ADS_PERMISSION}`, "reklam açıkken izleme izni (tracking) kapatılamaz — iOS'ta kişiselleştirilmiş reklam ATT ister; kipi auto ya da on yapın");
|
|
203
241
|
return errors;
|
|
204
242
|
}
|
|
205
243
|
/**
|
|
@@ -288,6 +326,43 @@ export function permissionLaunchShellWarnings(permissions, shell) {
|
|
|
288
326
|
return [];
|
|
289
327
|
return [`${asked.join(', ')}: açılışta sorulacak diye ayarlı ama yayındaki kabuk paketi (${text(shell.version)}) açılışta izin sormuyor — ayar saklanır, mağaza derlemesi sormaz (ön kontrol P-12 uyarır). Daha yeni bir kabuk yayınlanana kadar izni bir akış adımından isteyin (MOB-025, ${guide}).`];
|
|
290
328
|
}
|
|
329
|
+
/**
|
|
330
|
+
* MOB-027 — izleme izni (kök ADR 0011): mağaza derlemesi `tracking`'i taşıyorsa App Store Connect App Privacy'de
|
|
331
|
+
* "Device ID — Used to Track You" ve Play Data safety'de "Advertising ID" beyanı insanın işidir; Apple izin penceresinden
|
|
332
|
+
* önce teşvik ya da ön pencereyi yasaklar (5.1.2). Taşınmayan izin için söylenecek bir şey yoktur.
|
|
333
|
+
*/
|
|
334
|
+
export function trackingDeclarationWarnings(permissions) {
|
|
335
|
+
const tracking = permissions.find(entry => entry.key === ADS_PERMISSION);
|
|
336
|
+
return isObject(tracking?.build) && tracking.build.carried === true
|
|
337
|
+
? [`${ADS_PERMISSION}: mağaza derlemesi izleme iznini (ATT) taşıyor — App Store Connect App Privacy'de "Device ID — Used to Track You", Play Console Data safety'de "Advertising ID" beyanı insanın işidir; izin penceresinden önce ödül/teşvik ya da kendi ön pencerenizi göstermeyin (App Store 5.1.2). Sonuçtaki advertisingId yalnız granted iken doludur (MOB-027, ${guide}).`]
|
|
338
|
+
: [];
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* MOB-026 — reklam açıkken ajanın bilmesi gereken, kayıttan okunabilen durumlar (kök ADR 0012). Satırlar `<konu>: …`
|
|
342
|
+
* biçimindedir. Test kipi mağaza yüklemesinde de test reklamı gösterir (ön kontrol P-13 `manual`); yerleşimsiz reklam
|
|
343
|
+
* SDK'yı bağlar ama hiçbir şey göstermez; App ID'si verilmiş platformda birimi olmayan yerleşim o platformda yer kaplamaz
|
|
344
|
+
* (`adBanner`) ya da `invalid` döner (`ad-show`). Beyan satırı her zaman gelir: App Privacy, Data safety ve AdMob rıza
|
|
345
|
+
* mesajı konsolda insanın işidir, gateway yazamaz.
|
|
346
|
+
*/
|
|
347
|
+
export function adsStoreWarnings(settings) {
|
|
348
|
+
const ads = isObject(settings.ads) ? settings.ads : {};
|
|
349
|
+
if (ads.enabled !== true)
|
|
350
|
+
return [];
|
|
351
|
+
const units = (Array.isArray(ads.units) ? ads.units : []).filter(isObject);
|
|
352
|
+
const platforms = [['ios', 'iOS', ads.iosAppId], ['android', 'Android', ads.androidAppId]];
|
|
353
|
+
const lines = [];
|
|
354
|
+
if (ads.testMode !== false)
|
|
355
|
+
lines.push(`testMode: test kipi açık — mağaza yüklemesinde de gerçek reklam yerine Google test reklamı gösterilir; yayından önce ads.testMode: false (ön kontrol P-13, MOB-026, ${guide}).`);
|
|
356
|
+
if (units.length === 0)
|
|
357
|
+
lines.push(`yerleşim: reklam açık ama hiç yerleşim yok — SDK bağlanır, hiçbir reklam gösterilmez; ads.units'e banner ya da tam ekran yerleşim ekleyin (MOB-026, ${guide}).`);
|
|
358
|
+
for (const unit of units) {
|
|
359
|
+
const missing = platforms.filter(([field, , appId]) => !isBlank(appId) && isBlank(unit[field])).map(([, label]) => label);
|
|
360
|
+
if (missing.length > 0)
|
|
361
|
+
lines.push(`${text(unit.name)}: ${missing.join(' ve ')}'de birim yok — o platformda adBanner yer kaplamaz, ad-show 'invalid' döner (MOB-026).`);
|
|
362
|
+
}
|
|
363
|
+
lines.push(`beyan: reklam izleme sayılır — App Store Connect App Privacy'de "Device ID — Used to Track You" (ve reklam verisi), Play Console Data safety'de "Advertising ID", AdMob konsolunda rıza (GDPR/UMP) mesajı insanın işidir; gateway yazamaz. Beyanı kullanıcı onaylayınca mobile_store_update { declarations } (MOB-026, ${guide}).`);
|
|
364
|
+
return lines;
|
|
365
|
+
}
|
|
291
366
|
/** MOB-022 — `definition` birleşmiş kayıttır; biçim hatası backend'in 422'siyle aynı ret (block), eksik alan uyarı. */
|
|
292
367
|
export const mobileStorePolicyRules = [
|
|
293
368
|
{
|
|
@@ -4,6 +4,7 @@ import { ensureWorkflowErrorPath } from './errorPath.js';
|
|
|
4
4
|
import { DOCUMENT_STYLE_TYPES, TYPED_STYLE_COMPONENTS } from './styleResolve.js';
|
|
5
5
|
import { STATE_STORAGE_KINDS, STEP_ALIASES } from './platform/profile.js';
|
|
6
6
|
import { inlineStyleFields } from './componentCatalog.js';
|
|
7
|
+
import { FUNCTIONS_NAMESPACE } from './functionGuards.js';
|
|
7
8
|
const SORT_DEFAULT = { name: 'sortField', type: 'sortType', source: 'query-string' };
|
|
8
9
|
const LIMIT_DEFAULT = { name: 'limit', offset: 'offset', source: 'query-string' };
|
|
9
10
|
function asArray(value) {
|
|
@@ -362,6 +363,7 @@ const REQUIRED_COLLECTIONS = ['events', 'validations', 'stateBindings', 'childre
|
|
|
362
363
|
*
|
|
363
364
|
* - `states`: DesignComponentManagementSchema.ToDomain:47 `States.IsSticky`'yi guard'sız okur;
|
|
364
365
|
* Appearance'ın satır 24'te varsayılanı var, States'in yok.
|
|
366
|
+
* - `blocks` doldurulmaz: konum ebeveynin düzenine bağlıdır, eksikse PAGE-037 ile reddedilir.
|
|
365
367
|
* - `events` / `validations` / `stateBindings`: yalnız **micro-op yolunda** zorunlu.
|
|
366
368
|
* `page_create` (POST /pages) bunlarsız 200 döner, `PATCH …/schema` NRE verir; canlı deneyde
|
|
367
369
|
* üçü birlikte verilince geçti, her biri tek başına verilince patladı (klinik H-005 eki).
|
|
@@ -675,6 +677,11 @@ export function styleKeyNote(fixed) {
|
|
|
675
677
|
+ 'değer kaydedilir ama hiç uygulanmaz ve tek işareti tarayıcı konsoludur (F-06). '
|
|
676
678
|
+ "Seçici anahtarlara ('&__body', ':hover', '--token') dokunulmadı."];
|
|
677
679
|
}
|
|
680
|
+
/** PAGE-009 ile aynı kural: JS tanımlayıcısı (tireler çıkarılınca) ve ayrılmış `Functions` değil. */
|
|
681
|
+
function isDerivableComponentName(name) {
|
|
682
|
+
const bare = name.replace(/-/g, '');
|
|
683
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(bare) && bare !== FUNCTIONS_NAMESPACE;
|
|
684
|
+
}
|
|
678
685
|
/**
|
|
679
686
|
* Kabın `states.isDroppable`ı KATALOĞUN kararıdır. Gateway düğümü kurarken bu alanı
|
|
680
687
|
* yazmıyordu (`DEFAULT_STATES` hepsini `false` bırakır) ve Studio kanvası düğümü kap saymıyordu:
|
|
@@ -721,6 +728,7 @@ export function droppableStatesNote(fixed) {
|
|
|
721
728
|
* hiç denetlemez (`PageSchemaValidator.cs`); Studio her düğüme `name = elementId` yazar
|
|
722
729
|
* (`ScreenBuilderDroppable.svelte`). Kural deterministik olduğu için reddetmek yerine dolduruyoruz.
|
|
723
730
|
* Doldurulan ad da PAGE-009'dan geçmek zorunda: çağıran bunu denetimden ÖNCE koşar. Dolu ada dokunulmaz.
|
|
731
|
+
* `normalizeComponentDefaults` aynı boşluğu yalnız geçerli bir elementId için ikinci kez kapatır (güvenlik ağı).
|
|
724
732
|
*/
|
|
725
733
|
export function fillComponentNames(nodes) {
|
|
726
734
|
const filled = [];
|
|
@@ -745,6 +753,16 @@ export function normalizeComponentDefaults(nodes) {
|
|
|
745
753
|
const fixed = [];
|
|
746
754
|
for (const node of asObjects(nodes)) {
|
|
747
755
|
const missing = [];
|
|
756
|
+
// name'siz düğümü platform kabul eder ama sayfada bir akış ya da `{{ }}` kod prop'u varsa
|
|
757
|
+
// FlowEngine runCode "Cannot read properties of undefined (reading 'catch')" ile düşer, akış
|
|
758
|
+
// `running:1`'de takılır ve hata adsız düğümü işaret etmez (image ölçüm turu, 2026-10-04).
|
|
759
|
+
// Yalnız PAGE-009'un geçerli saydığı elementId'den türetilir: page_create/page_update/layout
|
|
760
|
+
// yollarında PAGE-009 denetimi bu normalizasyondan ÖNCE koşar; bozuk bir elementId'den türeyen
|
|
761
|
+
// ad denetimi atlatıp "Missing initializer in const declaration" ile sayfayı öldürürdü.
|
|
762
|
+
if ((typeof node.name !== 'string' || node.name.trim() === '') && typeof node.elementId === 'string' && isDerivableComponentName(node.elementId)) {
|
|
763
|
+
node.name = node.elementId;
|
|
764
|
+
missing.push('name');
|
|
765
|
+
}
|
|
748
766
|
if (!node.states || typeof node.states !== 'object') {
|
|
749
767
|
node.states = { ...DEFAULT_STATES };
|
|
750
768
|
missing.push('states');
|
|
@@ -1012,7 +1030,7 @@ export const PAGE_CONTAINER_ELEMENT_ID = 'page_container';
|
|
|
1012
1030
|
* Düzen prop'ları da kurulur (2026-08-25): `ContainerComponent` şemasının `layout` varsayılanı
|
|
1013
1031
|
* `grid` → `flex` oldu ve Studio bırakma yolu katalogdaki `definition.defaults`'u düğümün
|
|
1014
1032
|
* props'una YAZAR (`ScreenBuilderDroppable.svelte`); sayfa kökünü kuran öbür üreticiler de aynı seti
|
|
1015
|
-
* yazar (Studio `DEFAULT_CONTAINER_LAYOUT_PROPS`,
|
|
1033
|
+
* yazar (Studio `DEFAULT_CONTAINER_LAYOUT_PROPS`, platform
|
|
1016
1034
|
* `CreatePageWhenApplicationCreatedEventHandler` + `SeedData`). Renderer varsayılanı
|
|
1017
1035
|
* BİLMEZ — `isFlexLayout` yalnız düğümün kendi prop'una bakar — bu yüzden gateway'in kurduğu
|
|
1018
1036
|
* kapsayıcı da seti açıkça taşır; yoksa Studio'dan açılan sayfa flex, MCP'den açılan ızgara olurdu.
|
|
@@ -1502,10 +1520,9 @@ function collectLayout(nodes, collected) {
|
|
|
1502
1520
|
*/
|
|
1503
1521
|
const MAX_REORDER_PASSES = 8;
|
|
1504
1522
|
/**
|
|
1505
|
-
* Studio'da komponent tuvale bırakıldığında ızgara çakışmasını
|
|
1506
|
-
*
|
|
1507
|
-
* `
|
|
1508
|
-
* `apps/studio-api/src/domain/page/shared/page-schema.helper.ts:28`). Gateway o servisten geçmez —
|
|
1523
|
+
* Studio'da komponent tuvale bırakıldığında ızgara çakışmasını Studio'nun ızgara motoru çözer:
|
|
1524
|
+
* `moveItem` çakışan kardeşleri aşağı iter (`@ng-apps/screen-builder`
|
|
1525
|
+
* `src/draggable/draggable.element.ts`). Gateway o motordan geçmez —
|
|
1509
1526
|
* `page_create`/`page_update` şemayı POST/PUT ile KOMPLE yazar (`page_build` tool'u yok) — bu yüzden
|
|
1510
1527
|
* ajanın verdiği koordinatlar olduğu gibi kayda giriyor ve aynı hücreleri isteyen komponentler
|
|
1511
1528
|
* Studio tuvalinde üst üste biniyor. Burada aynı algoritma şemanın TAMAMINA uygulanır: en dıştaki
|
|
@@ -1626,12 +1643,34 @@ function gapWalk(parentLabel, children, code, onlyParents, isRoot, warnings, par
|
|
|
1626
1643
|
* sayfadan ayrılmadan koşar (`Runtime.svelte initDestroyGuard`). Platform uydurma adı 200 ile
|
|
1627
1644
|
* kaydeder ama runtime eşleştiremez ve bağ sessizce ölü kalır (PAGE-008'in sayfa/layout ikizi).
|
|
1628
1645
|
*/
|
|
1629
|
-
const SCREEN_EVENT_NAMES =
|
|
1630
|
-
|
|
1646
|
+
const SCREEN_EVENT_NAMES = ['mount', 'destroy'];
|
|
1647
|
+
/**
|
|
1648
|
+
* 38 S-15 / kök ADR 0015: mobil SAYFA bir olay daha taşır, `focus` — ekran ilk göründüğünde ve her geri
|
|
1649
|
+
* dönüşte (yığından geri, sekme değişimi) koşar; yığındaki ekran bağlı kaldığı için `mount` geri dönüşte
|
|
1650
|
+
* koşmaz. Web her gezinmede sayfayı baştan kurar (`Runtime.svelte` focus'u tanımaz), layout'un focus'u
|
|
1651
|
+
* yoktur. Ayna: `ng-apps-ui-native` `SCREEN_LIFECYCLE_EVENTS` (parite: `test/page-meta.test.ts`).
|
|
1652
|
+
*/
|
|
1653
|
+
const MOBILE_PAGE_EVENT_NAMES = [...SCREEN_EVENT_NAMES, 'focus'];
|
|
1654
|
+
const SCREEN_EVENT_HINTS = {
|
|
1655
|
+
mount: 'mount (yüklendiğinde)',
|
|
1656
|
+
destroy: 'destroy (ayrılırken; temizlik akışları için)',
|
|
1657
|
+
focus: 'focus (ekran her göründüğünde — ilk açılış ve geri dönüş; listeyi yeniden okumak için)'
|
|
1658
|
+
};
|
|
1659
|
+
export function screenEventNames(kind, platform) {
|
|
1660
|
+
return kind === 'page' && platform === 'mobile' ? MOBILE_PAGE_EVENT_NAMES : SCREEN_EVENT_NAMES;
|
|
1661
|
+
}
|
|
1662
|
+
/** Olaylardan biri yalnız mobil sayfada geçerliyse karar uygulamanın platformunu ister (page_update şemasız yazımı). */
|
|
1663
|
+
export function screenEventsNeedPlatform(events) {
|
|
1664
|
+
return asObjects(events).some(event => !SCREEN_EVENT_NAMES.includes(String(event.name ?? '')) && MOBILE_PAGE_EVENT_NAMES.includes(String(event.name ?? '')));
|
|
1665
|
+
}
|
|
1666
|
+
export function invalidScreenEventNames(events, code, names = SCREEN_EVENT_NAMES) {
|
|
1667
|
+
const valid = names.map(name => SCREEN_EVENT_HINTS[name] ?? name).join(' · ');
|
|
1631
1668
|
return asObjects(events)
|
|
1632
1669
|
.map(event => String(event.name ?? ''))
|
|
1633
|
-
.filter(name => name !== '' && !
|
|
1634
|
-
.map(name =>
|
|
1670
|
+
.filter(name => name !== '' && !names.includes(name))
|
|
1671
|
+
.map(name => MOBILE_PAGE_EVENT_NAMES.includes(name)
|
|
1672
|
+
? `${code}: '${name}' yalnız mobil uygulamanın SAYFASINDA vardır (layout'ta ve web sayfasında yok; web sayfayı her gezinmede baştan kurar, mount yeter) — burada geçerli adlar: ${valid}.`
|
|
1673
|
+
: `${code}: sayfa/layout düzeyinde '${name}' diye bir olay YOK — geçerli adlar: ${valid}. Platform kaydı kabul eder ama bağ sessizce ölü kalır.`);
|
|
1635
1674
|
}
|
|
1636
1675
|
const GUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1637
1676
|
/**
|
|
@@ -1699,9 +1738,9 @@ function collectDesktopPositions(nodes, into) {
|
|
|
1699
1738
|
* görünen sıraya yerleştir, x=0, birikimli y). Burada aynısı yapılır: akış kabında `index` GÖRÜNEN
|
|
1700
1739
|
* sıradaki yerdir (eski kayıtlarda dizi sırası çizimle aynı değildir, Studio sürüklerken diziyi
|
|
1701
1740
|
* değil y'yi yazar). Op'lar okunan şemanın kopyasına sırayla uygulanır; eklenen düğümün son konumu
|
|
1702
|
-
* kendi gövdesine, var olan kardeşlerinki yamanın sonuna `move_component {target,
|
|
1703
|
-
* yazılır.
|
|
1704
|
-
*
|
|
1741
|
+
* kendi gövdesine, var olan kardeşlerinki yamanın sonuna `move_component {target, breakpoint: 1024,
|
|
1742
|
+
* position}` olarak yazılır. `breakpoint` açık yazılır: ilk bloğu mobil kırılım olan kardeşte mobil
|
|
1743
|
+
* konum ezilmez (PAGE-033, K9). Izgara kapta çizim mutlak koordinattır; sona eklemeyen `index` orada PAGE-033 uyarısıdır.
|
|
1705
1744
|
* `index` verilmemiş op'a dokunulmaz.
|
|
1706
1745
|
*/
|
|
1707
1746
|
export function flexIndexOrder(schema, ops) {
|
|
@@ -1814,11 +1853,7 @@ export function flexIndexOrder(schema, ops) {
|
|
|
1814
1853
|
const before = original.get(elementId);
|
|
1815
1854
|
if (!body && !moveTargets.has(elementId) && before && before.x === final.x && before.y === final.y)
|
|
1816
1855
|
continue;
|
|
1817
|
-
|
|
1818
|
-
warnings.push(`PAGE-033 (uyarı): '${elementId}' flex sırasına göre y:${final.y}'e taşınmalıydı ama ilk bloğu desktop (1024) değil — platform move_component'in position'ını İLK bloğa yazar, yanlış kırılımı bozmamak için konum yazılmadı. Düğümü page_update(schema) ile düzeltin.`);
|
|
1819
|
-
continue;
|
|
1820
|
-
}
|
|
1821
|
-
trailing.push({ op: 'move_component', target: elementId, position: { x: final.x, y: final.y } });
|
|
1856
|
+
trailing.push({ op: 'move_component', target: elementId, breakpoint: DESKTOP_BREAKPOINT, position: { x: final.x, y: final.y } });
|
|
1822
1857
|
moved.push(`${elementId} y:${before?.y ?? '?'}→${final.y}`);
|
|
1823
1858
|
}
|
|
1824
1859
|
return { ops: [...sent, ...trailing], moved, warnings };
|
|
@@ -116,7 +116,7 @@ export const mobileProfile = Object.freeze({
|
|
|
116
116
|
},
|
|
117
117
|
style: { freeStyle: false, hoverStyle: false, resolver: 'native' },
|
|
118
118
|
steps: {
|
|
119
|
-
registered: new Set([...ENGINE_STEP_TYPES, 'alert', 'confirm', 'redirect', 'navigate', 'storage', 'setcookie', 'download', 'share', 'haptic', 'location', 'notification-permission', 'notification-schedule', 'notification-cancel', 'permission', 'open-settings']),
|
|
119
|
+
registered: new Set([...ENGINE_STEP_TYPES, 'alert', 'confirm', 'redirect', 'navigate', 'storage', 'setcookie', 'download', 'share', 'haptic', 'location', 'notification-permission', 'notification-schedule', 'notification-cancel', 'permission', 'open-settings', 'ad-show', 'back']),
|
|
120
120
|
aliases: STEP_ALIASES,
|
|
121
121
|
infoOnly: new Set(['download', 'setcookie'])
|
|
122
122
|
},
|
|
@@ -180,7 +180,7 @@ export const MOBILE_WEB_ONLY = {
|
|
|
180
180
|
'layout-region': "tek slot'lu sıradan layout",
|
|
181
181
|
'auth-layout': "tek slot'lu sıradan layout",
|
|
182
182
|
popover: "'modal' widget'ı",
|
|
183
|
-
'theme-toggle': "uygulama içi tema ayarı: 'choice-chips' (variant: segmented, Sistem/Açık/Koyu) → storage adımı name: 'zeyrek:theme-mode', value: system | light | dark; barındırıcı temayı anında değiştirir, cihazda kalıcı (seçim yazılmadıysa cihaz teması)",
|
|
183
|
+
'theme-toggle': "uygulama içi tema ayarı: 'choice-chips' (variant: segmented, Sistem/Açık/Koyu) → storage adımı name: 'zeyrek:theme-mode', value: system | light | dark; barındırıcı temayı anında değiştirir, cihazda kalıcı (seçim yazılmadıysa açık tema; cihaz teması yalnız system seçilince)",
|
|
184
184
|
pagination: "'repeater' + daha fazla yükle düğmesi",
|
|
185
185
|
breadcrumb: "yığın gezinmesi (app-bar geri düğmesi)",
|
|
186
186
|
fileUpload: "'fileInput' (alan) ya da 'filebutton' (düğme) — dokunmatikte sürükle-bırak yok; ikisi de aynı base64 değeri yazar"
|
|
@@ -20,7 +20,7 @@ export const webProfile = Object.freeze({
|
|
|
20
20
|
},
|
|
21
21
|
style: { freeStyle: true, hoverStyle: true, resolver: 'css' },
|
|
22
22
|
steps: {
|
|
23
|
-
registered: new Set([...ENGINE_STEP_TYPES, 'alert', 'confirm', 'redirect', 'download', 'storage', 'setcookie', 'share', 'haptic', 'location', 'notification-permission', 'notification-schedule', 'notification-cancel', 'permission', 'open-settings']),
|
|
23
|
+
registered: new Set([...ENGINE_STEP_TYPES, 'alert', 'confirm', 'redirect', 'download', 'storage', 'setcookie', 'share', 'haptic', 'location', 'notification-permission', 'notification-schedule', 'notification-cancel', 'permission', 'open-settings', 'ad-show', 'back']),
|
|
24
24
|
aliases: STEP_ALIASES,
|
|
25
25
|
infoOnly: new Set()
|
|
26
26
|
},
|