@appaflytech/zeyrek-ai-mcp 0.0.52 → 0.0.54
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 +3 -1
- package/dist/knowledge/componentEvents.js +13 -1
- package/dist/knowledge/mobileComponentCatalog.js +4 -1
- package/dist/knowledge/normalize.js +4 -3
- package/dist/platform/argumentAliases.js +13 -4
- package/dist/platform/bodyFile.js +195 -38
- package/dist/platform/capture/actions.js +24 -5
- package/dist/platform/capture/capture.js +80 -11
- package/dist/platform/capture/persistent-browser.js +23 -1
- package/dist/platform/capture/readiness.js +32 -1
- package/dist/platform/errors.js +45 -5
- package/dist/platform/http.js +17 -9
- package/dist/platform/responses.js +7 -1
- package/dist/platform/secrets.js +8 -0
- package/dist/platform/token.js +32 -21
- package/dist/resources/examples/mobile-offline-list.json +1 -1
- package/dist/resources/examples/mobile-offline-queue.json +1 -1
- package/dist/resources/examples/mobile-offline-seamless.json +12 -3
- package/dist/resources/guides/deployment.md +9 -0
- package/dist/resources/guides/merge.md +5 -0
- package/dist/resources/guides/microservice.md +13 -1
- package/dist/resources/guides/mobile-publish.md +7 -4
- package/dist/resources/guides/mobile-steps.md +40 -3
- package/dist/resources/guides/mobile.md +39 -8
- package/dist/resources/guides/page.md +39 -8
- package/dist/resources/guides/translation.md +3 -1
- package/dist/resources/prompts/mobile-publish-prepare.md +3 -2
- package/dist/resources/schema-fingerprint.json +2 -2
- package/dist/tools/applications.js +9 -4
- package/dist/tools/connectData.js +13 -4
- package/dist/tools/deployments.js +1 -1
- package/dist/tools/designSchemaPatch.js +7 -2
- package/dist/tools/environments.js +40 -0
- package/dist/tools/flows.js +30 -3
- package/dist/tools/microservices.js +25 -7
- package/dist/tools/mobile.js +56 -0
- package/dist/tools/mobileStore.js +2 -2
- package/dist/tools/pages.js +69 -16
- package/dist/tools/platformInfo.js +1 -1
- package/dist/tools/screenWrite.js +107 -1
- package/dist/tools/variables.js +1 -1
- package/package.json +1 -1
- package/dist/resources/guides/mobile-flow.md +0 -64
|
@@ -4,6 +4,7 @@ import { FUNCTIONS_NAMESPACE } from '../knowledge/functionGuards.js';
|
|
|
4
4
|
import { reservedExpressionName } from '../knowledge/expressionPrelude.js';
|
|
5
5
|
import { applyCatalogLayoutRules, applyCatalogLayoutRulesFrom, fetchCatalogComponents } from './components.js';
|
|
6
6
|
import { PlatformApiError } from '../platform/errors.js';
|
|
7
|
+
import { closestNames } from '../platform/argumentAliases.js';
|
|
7
8
|
const GUARD_CODES = {
|
|
8
9
|
page: { events: 'PAGE-011', widgets: 'PAGE-012', noun: 'sayfa', tree: 'schema' },
|
|
9
10
|
layout: { events: 'LAY-004', widgets: 'LAY-005', noun: 'layout', tree: 'views' },
|
|
@@ -102,7 +103,8 @@ export async function screenCatalogNotes(platform, project, environment, applica
|
|
|
102
103
|
*
|
|
103
104
|
* Neden yalnız bu yollarda: micro-op ucunda platformun kendi doğrulama duvarı zaten koşuyor
|
|
104
105
|
* (`invalid-enum-value` · `unknown-prop` · `invalid-appearance`), tam şema PUT/POST'unda ise
|
|
105
|
-
* koşmuyordu — aynı geçersiz değer bir yoldan 400, öbüründen 200 dönüyordu.
|
|
106
|
+
* koşmuyordu — aynı geçersiz değer bir yoldan 400, öbüründen 200 dönüyordu. Micro-op ucunda gateway
|
|
107
|
+
* yalnız tanınmayan prop'u önden adlandırır (`unknownPropOps`, g-21); enum ve görünüm kararı platformda kalır. Katalog okunamazsa
|
|
106
108
|
* doğrulama susar ve yazma eskisi gibi geçer: bilinmeyen sözlük yüzünden geçerli bir gövdeyi
|
|
107
109
|
* bloklamak, denetimi atlamaktan kötüdür.
|
|
108
110
|
*/
|
|
@@ -116,3 +118,107 @@ export async function assertScreenCatalog(platform, project, environment, applic
|
|
|
116
118
|
const catalog = applyCatalogLayoutRulesFrom(components, nodes);
|
|
117
119
|
return [...blockSizeNote(catalog.clamped), ...heightTypeNote(catalog.heights), ...sizeStringNote(catalog.sizes), ...droppableStatesNote(catalog.states)];
|
|
118
120
|
}
|
|
121
|
+
/** Düğümün `props[prop]`'unu yazan op'lar (`PageSchemaMicroOpApplier.cs` `replace_prop` · `set_binding` · `set_prop_type`). */
|
|
122
|
+
const PROP_WRITING_OPS = new Set(['replace_prop', 'set_binding', 'set_prop_type']);
|
|
123
|
+
function registerInstances(nodes, parent, byId) {
|
|
124
|
+
return (Array.isArray(nodes) ? nodes : []).flatMap(node => {
|
|
125
|
+
if (!node || typeof node !== 'object')
|
|
126
|
+
return [];
|
|
127
|
+
const record = node;
|
|
128
|
+
const instance = { type: typeof record.type === 'string' ? record.type : undefined, parent, children: [], removed: false };
|
|
129
|
+
instance.children = registerInstances(record.children, instance, byId);
|
|
130
|
+
if (typeof record.elementId === 'string')
|
|
131
|
+
byId.set(record.elementId, instance);
|
|
132
|
+
return [instance];
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
function detach(instance) {
|
|
136
|
+
if (instance.parent)
|
|
137
|
+
instance.parent.children = instance.parent.children.filter(child => child !== instance);
|
|
138
|
+
}
|
|
139
|
+
function markRemoved(instance) {
|
|
140
|
+
instance.removed = true;
|
|
141
|
+
instance.children.forEach(markRemoved);
|
|
142
|
+
}
|
|
143
|
+
function collectSavedProps(nodes, into) {
|
|
144
|
+
for (const node of Array.isArray(nodes) ? nodes : []) {
|
|
145
|
+
if (!node || typeof node !== 'object')
|
|
146
|
+
continue;
|
|
147
|
+
const record = node;
|
|
148
|
+
if (typeof record.elementId === 'string' && record.props && typeof record.props === 'object') {
|
|
149
|
+
for (const prop of Object.keys(record.props))
|
|
150
|
+
into.add(`${record.elementId}/${prop}`);
|
|
151
|
+
}
|
|
152
|
+
collectSavedProps(record.children, into);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* g-21 (D-21, plans/defter-saha-bulgulari) — micro-op yamasında tanınmayan prop'un ön denetimi. Platform
|
|
157
|
+
* `PageSchemaValidator.cs` `ValidateProp` aynı kararı `unknown-prop` ile verir ama BÜTÜN yamayı reddeder ve hangi op
|
|
158
|
+
* olduğunu ajan hata metninden ayıklar (Defter: `app-bar`'da `ariaLabel`). Burada op'un sırası, hedefi ve
|
|
159
|
+
* komponentin en yakın üç prop adı istek gitmeden söylenir; yama yine bütün reddedilir (op'u sessizce düşürmek
|
|
160
|
+
* yazanın beklediğini değiştirirdi). Sözlük `assertScreenCatalog`'unkiyle aynıdır (`GET /components/{app}`, yalnız
|
|
161
|
+
* üst düzey prop'lar) ve aynı kuralla susar: katalog okunamazsa, hedefin tipi bilinmiyorsa ya da tip katalogda
|
|
162
|
+
* yoksa op denetlenmez. Kayıtlı ağaç yalnız prop yazan op varsa okunur. `ops` batch zarfı açılmış listedir; indeks o
|
|
163
|
+
* listeye göredir.
|
|
164
|
+
*
|
|
165
|
+
* Platformdan GENİŞ reddetmemek için iki kural (g-21 incelemesi): (1) Kayıtlı düğüm prop'u zaten taşıyorsa karar
|
|
166
|
+
* platformundur — `ValidateProp` kayıtlı ağaçta aynı değerle duran tanımsız prop'u muaf tutar (`savedProps`) ve aynı
|
|
167
|
+
* yazım `noop`'tur (`PatchPageSchemaCommand.cs`). (2) Op'lar platformun uyguladığı SIRAYLA yürünür: hedefin tipi o
|
|
168
|
+
* andaki düğümünkidir (`add_component` ekler, `remove_component` alt ağacıyla siler, `move_component` taşır) ve aynı
|
|
169
|
+
* yamada sonra silinen düğüme yazan op denetlenmez — silinen düğüm doğrulanacak son ağaçta yoktur.
|
|
170
|
+
*/
|
|
171
|
+
export async function unknownPropOps(platform, project, environment, applicationId, savedNodes, ops) {
|
|
172
|
+
if (!ops.some(entry => PROP_WRITING_OPS.has(String(entry.op)) && typeof entry.prop === 'string'))
|
|
173
|
+
return [];
|
|
174
|
+
const rules = catalogTypeRules(await fetchCatalogComponents(platform, project, environment, applicationId).catch(() => []));
|
|
175
|
+
if (rules.size === 0)
|
|
176
|
+
return [];
|
|
177
|
+
const nodes = await savedNodes().catch(() => []);
|
|
178
|
+
const byId = new Map();
|
|
179
|
+
const savedProps = new Set();
|
|
180
|
+
const writes = [];
|
|
181
|
+
registerInstances(nodes, undefined, byId);
|
|
182
|
+
collectSavedProps(nodes, savedProps);
|
|
183
|
+
ops.forEach((entry, index) => {
|
|
184
|
+
const target = typeof entry.target === 'string' ? entry.target : undefined;
|
|
185
|
+
if (entry.op === 'add_component') {
|
|
186
|
+
const parent = typeof entry.parent === 'string' ? byId.get(entry.parent) : undefined;
|
|
187
|
+
const added = registerInstances([entry.component], parent, byId);
|
|
188
|
+
parent?.children.push(...added);
|
|
189
|
+
}
|
|
190
|
+
else if (entry.op === 'remove_component' && target !== undefined) {
|
|
191
|
+
const instance = byId.get(target);
|
|
192
|
+
if (instance) {
|
|
193
|
+
detach(instance);
|
|
194
|
+
markRemoved(instance);
|
|
195
|
+
byId.delete(target);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else if (entry.op === 'move_component' && target !== undefined) {
|
|
199
|
+
const instance = byId.get(target);
|
|
200
|
+
const parent = typeof entry.parent === 'string' ? byId.get(entry.parent) : undefined;
|
|
201
|
+
if (instance) {
|
|
202
|
+
detach(instance);
|
|
203
|
+
instance.parent = parent;
|
|
204
|
+
parent?.children.push(instance);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
else if (PROP_WRITING_OPS.has(String(entry.op)) && target !== undefined && typeof entry.prop === 'string') {
|
|
208
|
+
const instance = byId.get(target);
|
|
209
|
+
if (instance)
|
|
210
|
+
writes.push({ index, entry, target, prop: entry.prop, instance });
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
return writes.flatMap(({ index, entry, target, prop, instance }) => {
|
|
214
|
+
const type = instance.type;
|
|
215
|
+
const declared = type === undefined ? undefined : rules.get(type)?.props;
|
|
216
|
+
if (instance.removed || declared === undefined || declared.has(prop) || savedProps.has(`${target}/${prop}`))
|
|
217
|
+
return [];
|
|
218
|
+
const closest = closestNames(prop, [...declared.keys()], 3);
|
|
219
|
+
return [{
|
|
220
|
+
op: entry,
|
|
221
|
+
message: `ops[${index}] ${target}.${prop} — '${type}' komponentinin '${prop}' diye bir prop'u yok; en yakın: ${closest.join(' · ')}. Bütün liste component_get('${type}') ile okunur.`
|
|
222
|
+
}];
|
|
223
|
+
});
|
|
224
|
+
}
|
package/dist/tools/variables.js
CHANGED
|
@@ -9,7 +9,7 @@ const variablesParam = z.array(z.object({
|
|
|
9
9
|
key: z.string().min(1).describe("Değişken adı; sayfada state.page.<key> ile okunur"),
|
|
10
10
|
value: z.unknown().describe('Herhangi bir JSON değeri; nesne verirseniz alanlarına state.page.<key>.<alan> ile erişilir'),
|
|
11
11
|
storage: z.enum(['memory', ...STATE_STORAGE_KINDS]).optional()
|
|
12
|
-
.describe("Değerin yaşadığı yer (yalnız mobil uygulamada uygulanır, MOB-014). Verilmezse kayıttaki tür korunur; yeni değişkende bellektir — state her sayfa açılışında buradaki value'dan başlar. 'device': cihaz deposu (AsyncStorage karşılığı), uygulama kapanıp açılınca son yazılan değer geri gelir. 'secure': güvenli depo (expo-secure-store), token gibi gizli ve küçük değerler için. 'memory': türü kaldırır. Çıkışta ikisi de silinir.")
|
|
12
|
+
.describe("Değerin yaşadığı yer (yalnız mobil uygulamada uygulanır, MOB-014). Verilmezse kayıttaki tür korunur; yeni değişkende bellektir — state.page her sayfa açılışında buradaki value'dan başlar; mobil kabukta state.app uygulama başına tek bellek deposudur (açılan sayfanın value'su yalnız depoda olmayan anahtarı doldurur, kök ADR 0031). 'device': cihaz deposu (AsyncStorage karşılığı), uygulama kapanıp açılınca son yazılan değer geri gelir. 'secure': güvenli depo (expo-secure-store), token gibi gizli ve küçük değerler için. 'memory': türü kaldırır. Çıkışta ikisi de silinir.")
|
|
13
13
|
})).min(1).describe("Değişken listesi: [{ key: 'MyTest', value: { isActive: true } }]. Gateway bunu platformun beklediği JSON string'e çevirir.");
|
|
14
14
|
const replaceParam = z.boolean().optional()
|
|
15
15
|
.describe('true: seti komple bunlarla değiştir (diğer değişkenler silinir). Varsayılan false — gateway mevcut setle birleştirir.');
|
package/package.json
CHANGED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
# Mobil akış adımları rehberi
|
|
2
|
-
|
|
3
|
-
Mobil uygulamada (`type:'mobile'`) akış motoru web'deki motorun aynısıdır (`platform_guide('page')`, `flow_create`).
|
|
4
|
-
Bu rehber yalnız barındırıcıya — cihaza — dokunan adımların telefondaki sözleşmesini ve tariflerini anlatır.
|
|
5
|
-
Ekranın kendisi, sayfa olayları (`mount` · `focus` · `destroy`) ve uzak push `platform_guide('mobile')`'dadır.
|
|
6
|
-
|
|
7
|
-
## Adımlar
|
|
8
|
-
|
|
9
|
-
Motorun kendi adımları (`setter`, `condition`, `microservice`, `code`, `function`, `flow-trigger`,
|
|
10
|
-
`validation`) iki platformda aynıdır. Barındırıcıya dokunan adımlar:
|
|
11
|
-
|
|
12
|
-
| Adım | Mobilde |
|
|
13
|
-
|---|---|
|
|
14
|
-
| `redirect` | yığına iter (göreli yol) ya da dışarıda açar (tam adres) |
|
|
15
|
-
| `navigate` | eski mobil şemaların adı — `redirect`'in takma adıdır; gateway `redirect`'e çevirir |
|
|
16
|
-
| `alert` | tipli uyarı (`success`/`error`/`info`/`warning`) toast'tur ve beklemez; düz uyarı onay bekler. `message`/`description` metin şablonudur (`"{{ t('kayit.tamam') }}"`; `platform_guide('page')` §alert). Eylem düğmeli bildirim ("Silindi · Geri al") `alert`le kurulmaz: sayfaya bir `toast` bileşeni koyun (`actions: [{ text, flow }]`, gerekirse `keepOpen`), akış setter'la `isOpen: true` yazar; basış eylemin kendi akışını koşar (`actionClick { text, index }`) |
|
|
17
|
-
| `confirm` | `yes` / `no` dalları; `message`/`title`/`confirmText`/`cancelText` metin şablonudur (`{{ }}`, çeviri `t('anahtar')`) |
|
|
18
|
-
| `download` | adres cihazın açma sayfasında açılır; ikili gövde (JSON değil) önbelleğe dosya olarak yazılıp paylaşım sayfasında (`expo-sharing`) açılır, Studio mobil önizlemesi dosyayı indirir (`MOB-005`, bilgi) |
|
|
19
|
-
| `storage` | `args.name` `state.page.<key>` / `state.app.<key>` ise o state'i yazar (`platform_guide('mobile')` §Kalıcı state), değilse cihaz deposuna |
|
|
20
|
-
| `setcookie` | güvenli cihaz deposuna (SecureStore) yazar; değer sonraki her istekte `X-Flow-Cookie` başlığıyla gider ve middleware/workflow onu web'deki gibi `cookie.<ad>` olarak okur — `Cookie` başlığıyla gitmez, çünkü iOS onu yerel çerez kavanozuyla virgülle birleştirip bozuyordu (`MOB-005`, bilgi); aynı adlı sunucu çerezi varsa o kazanır. Studio paleti mobilde de sunar |
|
|
21
|
-
| `share` | RN paylaşım sayfası (metin + bağlantı; Android'de bağlantı metne eklenir, `title` iOS'ta e-posta konusu, Android'de seçici başlığı); akış `always` ile sürer — iOS'ta sayfa kapanınca, Android'de seçici açılır açılmaz (RN kapanışı bildirmez). Argümanlar `platform_guide('page')` §`share` adımı |
|
|
22
|
-
| `haptic` | dokunsal geri bildirim (`expo-haptics`, izin istemez). `args.value` stil, **düz metin**: `success` · `warning` · `error` · `light` · `medium` · `heavy` · `selection`; boş → `medium`. Değer yazmaz, hep `always`. Web'de titreşim varsa kısa titreşim, yoksa hiçbir şey (paletten yalnız mobilde sunulur) |
|
|
23
|
-
| `location` | cihaz konumu (`expo-location`; ilk kullanımda izin sorulur; derleme konumu taşımıyorsa sorulmaz, `error` dalı `status: 'unavailable'`). `args.value` doğruluk, düz metin: `low` · `balanced` · `high` (boş → `balanced`); `args.isRaw: true` → adres çözülmez. Sonuç `Steps.<adım>.data = { latitude, longitude, accuracy, address }`, `address = { country, region, subregion, city, district, street, postalCode }` ya da `null` (Türkiye'de il `region`, ilçe `subregion`, yoksa `city`). İzin reddi `Steps.<adım>.error = { status: 'denied', message }`, okunamayan konum `unavailable`: adımın `error` dalı bağlıysa o koşar (`always` koşmaz), yoksa akışın hata akışı, o da yoksa `always` (`data: null`). Web'de tarayıcı konumu, adres hep `null` |
|
|
24
|
-
| `notification-permission` | bildirim izni ister (sorulmuşsa yeniden sormaz, kalıcı red yeniden sorulmaz). Sonuç `Steps.<adım>.data.status`: `granted` · `denied` · `undetermined`; red hata değildir, hep `always` |
|
|
25
|
-
| `notification-schedule` | yerel bildirim planlar (`expo-notifications`; uzak push değil). `args.name` kimlik ve `args.title` başlık **metin şablonudur** — yalnız `{{ }}` içi çözülür (`'ilac-{{ item.id }}-0800'` kayıt başına ayrı kimlik, `"{{ t('ilac.vakti') }}"` çeviri; `{{ }}` dışındaki ifade çözülmez, FLOW-022); aynı kimlik eskisinin yerini alır. `args.message` gövde (**ifade**), `args.value` zaman (**ifade**): sonucu `'HH:mm'` → her gün, `{ weekday: 1–7, time: 'HH:mm' }` → her hafta (1 = Pazartesi), ISO tarih-saat → bir kez. `args.path` dokununca açılacak sayfanın slug'ı, düz metin — şablon çözmez (+ `args.query` ifade), boşsa uygulama açılır. İzin yoksa `error.status: 'denied'`, geçmiş ya da okunamayan zaman `invalid`: `error` dalı → hata akışı → `always`. Web'de desteklenmez (`unsupported`) — sayfa kapalıyken tarayıcı gösteremez |
|
|
26
|
-
| `notification-cancel` | `args.name` kimlikli planlı bildirimi siler (planlamadaki gibi metin şablonu: `'ilac-{{ item.id }}-0800'`), `'*'` hepsini; hep `always` |
|
|
27
|
-
| `permission` | bir cihaz iznine bakar ya da ister. `args.value` izin türü, **düz metin — ifade olamaz** (mağaza derlemesinin taşıyacağı izinler bu değerden, akış koşmadan hesaplanır; `FLOW-038`); `args.name` kip: `check` yalnız okur (sistem penceresi açılmaz), `request` verilmemiş ve hâlâ sorulabilen izni ister (boş → `request`). Sonuç `Steps.<adım>.data = { status, canAskAgain }`: `granted` · `denied` · `undetermined` · `unavailable` (derleme bu izni taşımıyor ya da web önizlemesi); `canAskAgain: false` → yalnız Ayarlar'dan değişir. `tracking` (iOS App Tracking Transparency) sonucu ayrıca `advertisingId` taşır: izin `granted` iken IDFA (iOS) / AAID (Android), değilse `null`; oynatıcı `tracking`'i hiç taşımaz (`unavailable`). Red hata değildir, hep `always`. Türler: `mobile_store_get { detail: ['permissions'] }` (`support: supported` satırlarının `key`'i) |
|
|
28
|
-
| `open-settings` | uygulamanın sistem ayar sayfasını açar; `Steps.<adım>.data.opened`. Argüman almaz |
|
|
29
|
-
| `back` | gezinme yığınında bir geri (iOS geri kaydırmasıyla aynı sonuç; kök ADR 0013). Argüman almaz (yazılan okunmaz), sonuç yazmaz, hata üretmez, hep `always`. Yığının ilk ekranında hiçbir şey yapmaz; Studio önizlemesinde runtime'ın kendi gezinmesi yoksa yine hiçbir şey, web uygulamasında tarayıcının geri'sidir. Formu kaydedip önceki ekrana dönmek için `redirect` yerine bunu kullanın: `redirect` yığına yeni ekran iter, geri kaydırması kaydedilen forma döner. 2026-10-08 öncesi runtime ve backend `back`'i tanımaz |
|
|
30
|
-
| `ad-show` | tam ekran reklam (kök ADR 0012). `args.value` mağaza ayarındaki yerleşimin **adı**, düz metin (`FLOW-039`); biçimi `interstitial` · `rewarded` · `rewardedInterstitial` olmalı. Reklam yüklenip gösterilir, kullanıcı kapatınca `Steps.<adım>.data = { shown: true, reward }` ile sürer — `reward` ödüllü reklamı sonuna kadar izleyende `{ type, amount }`, değilse `null` (ödül ayrı dal değil, `condition` ile okunur). 10 sn'de reklam yoksa, SDK hata verirse, derleme reklam taşımıyorsa ya da rıza yoksa `error.status: 'unavailable'`; tanımsız ya da banner yerleşimi `invalid`: `error` dalı → hata akışı → `always`. Oynatıcı ve web önizlemesi her zaman `unavailable` — reklamsız dalı orada sınayın. Paletten yalnız mobilde |
|
|
31
|
-
|
|
32
|
-
## Bildirim ve izin
|
|
33
|
-
|
|
34
|
-
Bildirim adımları paletten yalnız mobil uygulamada sunulur. Tarif — S04 "her gün hatırlat": önce
|
|
35
|
-
`notification-permission` (S03 düğmesi), sonra `timepicker`'ın değeriyle `notification-schedule`
|
|
36
|
-
(`value: "<saat>.value"`, değer `HH:mm`) ve `error` dalında kullanıcıya mesaj. Çevrimdışı bandı adım değil
|
|
37
|
-
ifadedir: `isHidden: {{ Device.isOnline }}` (`platform_guide('expressions')`).
|
|
38
|
-
|
|
39
|
-
**İzin tarifi** (izin, ihtiyaç anında — düğmeye basılınca — istenir; sayfa açılışında değil): `permission`
|
|
40
|
-
`check` → `condition`, **bu sırayla**: `status === 'granted'` ise işe devam; `'unavailable'` ise özelliği sunma
|
|
41
|
-
(Ayarlar'da açılacak izin yoktur — `canAskAgain` burada da `false` gelir); `canAskAgain` ise `permission` `request`
|
|
42
|
-
ve sonucuna göre devam; değilse nedenini söyleyip `open-settings`. `location` adımı, `map.showUser` ve dosya alanının
|
|
43
|
-
kamera/galeri kaynağı izni kendileri de ister — `permission` adımı *ne zaman* sorulacağını seçmek ve sonucu
|
|
44
|
-
okumak içindir. `unavailable` bir hata değil, derlemenin o izni taşımadığının haberidir
|
|
45
|
-
(`platform_guide('mobile-publish')` §İzinler).
|
|
46
|
-
|
|
47
|
-
## Reklam
|
|
48
|
-
|
|
49
|
-
**Reklam tarifi** (kök ADR 0012): (1) `mobile_store_update { settings: { ads: { enabled: true, iosAppId, androidAppId,
|
|
50
|
-
testMode: true, units: [{ name, format, ios, android }] } } }` — reklam, App ID'si olan platformun derlemesine `tracking`'i de sokar (ATT metni
|
|
51
|
-
gider, kip `off` olamaz); (2) banner için sayfaya `adBanner` `unit: '<banner yerleşimi>'`; (3) geçiş/ödüllü reklam için
|
|
52
|
-
düğme akışına `ad-show` `value: '<yerleşim>'`, `error` dalında reklamsız devam; (4) ödül `condition`:
|
|
53
|
-
`Steps.<adım>.data?.reward` doluysa ödülü ver. Açılış reklamı adım değil ayardır (`ads.appOpenUnit`). Sıra cihazda:
|
|
54
|
-
rıza formu → açılış izinleri (ATT) → SDK; iOS'ta izleme izni yoksa reklam kişiselleştirilmeden istenir. Yerleşim adının
|
|
55
|
-
ayarda tanımlı olduğu sayfa/akış yazımında denetlenemez — `mobile_store_get` `settings.ads.units` ve `adsWarnings`
|
|
56
|
-
(`MOB-026`); yayın ayrıntısı `platform_guide('mobile-publish')` §Reklamlar.
|
|
57
|
-
|
|
58
|
-
## Konum ve harita
|
|
59
|
-
|
|
60
|
-
`style`, `accuracy`, `reverseGeocode` gibi anahtarlar sözleşmede yoktur ve platform onları sessizce düşürür
|
|
61
|
-
(`FLOW-029`). "Haritada aç" için adım yoktur: `map`'in `press` olayı düğüme `mapsUrl` yazar, akış
|
|
62
|
-
`redirect` `isRaw: true`, `path: '<harita>.mapsUrl'` ile açar. Tarif — L05 "mevcut konumumu kullan":
|
|
63
|
-
düğmenin `click`'i → `location` (`always` → setter `il.value = Steps.konum.data.address?.region`, `error` →
|
|
64
|
-
setter hata metni). Uzak push (sunucudan gelen bildirim): `platform_guide('mobile')` §Uzak push.
|