@appaflytech/zeyrek-ai-mcp 0.0.22 → 0.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/.env.example +15 -0
  2. package/dist/config.js +9 -0
  3. package/dist/index.js +2 -0
  4. package/dist/knowledge/componentCatalog.js +4 -2
  5. package/dist/knowledge/componentEvents.js +3 -3
  6. package/dist/knowledge/mobileComponentCatalog.js +308 -50
  7. package/dist/knowledge/mobileComponentEvents.js +130 -3
  8. package/dist/knowledge/normalize.js +16 -6
  9. package/dist/knowledge/platform/lint.js +110 -3
  10. package/dist/knowledge/platform/mobile.js +1 -1
  11. package/dist/knowledge/platform/mobileRules.js +70 -11
  12. package/dist/knowledge/platform/web.js +1 -1
  13. package/dist/knowledge/rules.js +106 -11
  14. package/dist/knowledge/styleResolve.js +49 -3
  15. package/dist/knowledge/styleResolveNative.js +104 -4
  16. package/dist/observability/trace.js +1 -1
  17. package/dist/platform/argumentAliases.js +128 -0
  18. package/dist/platform/bodyFile.js +103 -0
  19. package/dist/platform/capture/capture.js +29 -5
  20. package/dist/platform/capture/console-log.js +80 -0
  21. package/dist/platform/capture/index.js +1 -0
  22. package/dist/platform/diagnostics.js +10 -4
  23. package/dist/platform/http.js +23 -7
  24. package/dist/platform/responses.js +6 -2
  25. package/dist/platform/schemaReadMemory.js +60 -0
  26. package/dist/platform/secrets.js +9 -0
  27. package/dist/platform/strictArguments.js +68 -12
  28. package/dist/platform/tokenVault.js +187 -4
  29. package/dist/resources/guides/expressions.md +1 -1
  30. package/dist/resources/guides/microservice.md +9 -2
  31. package/dist/resources/guides/middleware.md +5 -1
  32. package/dist/resources/guides/mobile.md +165 -10
  33. package/dist/resources/guides/page.md +161 -41
  34. package/dist/resources/guides/theme.md +4 -1
  35. package/dist/resources/guides/workflow.md +7 -1
  36. package/dist/resources/schema-fingerprint.json +2 -2
  37. package/dist/server.js +1 -1
  38. package/dist/tools/datasources.js +1 -1
  39. package/dist/tools/designSchemaPatch.js +4 -3
  40. package/dist/tools/externalComponents.js +1 -1
  41. package/dist/tools/flows.js +70 -4
  42. package/dist/tools/layouts.js +5 -5
  43. package/dist/tools/microservices.js +26 -10
  44. package/dist/tools/pages.js +192 -36
  45. package/dist/tools/platformInfo.js +4 -1
  46. package/dist/tools/rows.js +7 -1
  47. package/dist/tools/schemaOps.js +1 -1
  48. package/dist/tools/screenWrite.js +4 -3
  49. package/dist/tools/screens.js +81 -0
  50. package/package.json +1 -1
@@ -1,5 +1,8 @@
1
1
  import { PlatformApiError } from './errors.js';
2
2
  import { randomBytes } from 'node:crypto';
3
+ import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
4
+ import { dirname, join } from 'node:path';
5
+ import { logStep } from '../observability/trace.js';
3
6
  /**
4
7
  * `page_screenshot`/`page_runtime_get` açıklaması "jetonu uygulamanın kendi giriş ucundan alın
5
8
  * (`microservice_execute`)" diyor, ama gizli alan maskesi `access_token`'ı `***` yapıyor ve o yol
@@ -9,10 +12,19 @@ import { randomBytes } from 'node:crypto';
9
12
  * Kasa bu çelişkiyi jetonu bağlama HİÇ sokmadan çözer: maskelenen JWT gateway sürecinde tutulur,
10
13
  * yanıta yalnız kısa ömürlü bir referans (`tok_…`) döner ve kimlik isteyen tool'lar referansı
11
14
  * çözer. Jeton ne yanıtta ne günlükte açık görünür.
15
+ *
16
+ * g-09 (`plans/mobil-plantcare-acigi`, K9): kasa süreç sınırını aşar. `.env` `MCP_STATE_DIR`
17
+ * verilmişse kayıtlar `token-vault.json`'a (0600) yazılır ve süreç açılışta onu okur; süre
18
+ * `MCP_TOKEN_TTL_MINUTES`'tan gelir (verilmezse eski 15 dk). Dosya YALNIZ lokal hedefte yazılır —
19
+ * uzak bir org'un jetonu diske inmez. Test kullanıcısının (`asUser: 'test:<ad>'`) jetonu da burada,
20
+ * etiketiyle tutulur; aynı kullanıcı TTL boyunca yeniden giriş yapmaz.
12
21
  */
13
- const ttlMs = 15 * 60 * 1000;
22
+ const legacyTtlMinutes = 15;
14
23
  const maxEntries = 64;
15
24
  const vault = new Map();
25
+ let ttlMs = legacyTtlMinutes * 60 * 1000;
26
+ let vaultFile;
27
+ let warnedRemote = false;
16
28
  export const tokenRefPattern = /^tok_[0-9a-f]{16}$/;
17
29
  /** Üç base64url parçalı JWT — parola ya da API anahtarı bu şekle uymaz. */
18
30
  export function looksLikeJwt(value) {
@@ -30,29 +42,132 @@ function sweep(now) {
30
42
  vault.delete(oldest.value);
31
43
  }
32
44
  }
33
- export function rememberToken(token) {
45
+ function isLocalTarget(baseUrl) {
46
+ try {
47
+ const host = new URL(baseUrl).hostname;
48
+ return host === 'localhost' || host === '127.0.0.1' || host === '[::1]' || host.endsWith('.localhost');
49
+ }
50
+ catch {
51
+ return false;
52
+ }
53
+ }
54
+ /**
55
+ * Aynı dosyayı birden çok gateway süreci paylaşır (her Claude Code oturumu kendi stdio sürecini açar).
56
+ * Yazma "son yazan kazanır" olursa bir sürecin kaydı ötekinin yazmasıyla silinirdi: yazmadan önce ve
57
+ * bellekte olmayan referans istenince dosya okunur, süresi dolmamış yabancı kayıtlar belleğe katılır.
58
+ */
59
+ function mergeFromFile() {
60
+ if (!vaultFile)
61
+ return;
62
+ try {
63
+ const stored = JSON.parse(readFileSync(vaultFile, 'utf8'));
64
+ const now = Date.now();
65
+ for (const [ref, entry] of Object.entries(stored.entries ?? {})) {
66
+ if (!vault.has(ref) && tokenRefPattern.test(ref) && typeof entry.token === 'string' && typeof entry.expires === 'number' && entry.expires > now) {
67
+ vault.set(ref, { token: entry.token, expires: entry.expires, ...(typeof entry.label === 'string' ? { label: entry.label } : {}) });
68
+ }
69
+ }
70
+ }
71
+ catch {
72
+ // dosya yok ya da okunamadı: bellekteki kasa geçerli
73
+ }
74
+ }
75
+ function persist() {
76
+ if (!vaultFile)
77
+ return;
78
+ mergeFromFile();
79
+ sweep(Date.now());
80
+ try {
81
+ const temporary = `${vaultFile}.${process.pid}.tmp`;
82
+ mkdirSync(dirname(vaultFile), { recursive: true, mode: 0o700 });
83
+ writeFileSync(temporary, JSON.stringify({ entries: Object.fromEntries(vault) }), { mode: 0o600 });
84
+ renameSync(temporary, vaultFile);
85
+ }
86
+ catch (error) {
87
+ logStep('jeton kasası ✕', `kasa dosyası yazılamadı (${vaultFile}) — jetonlar bu süreçte bellekte kalır`, [error instanceof Error ? error.message : String(error)]);
88
+ }
89
+ }
90
+ /**
91
+ * Süreç açılışında bir kez çağrılır (`index.ts`); testler yeniden başlamayı `clearTokenVault` +
92
+ * bu çağrıyla benzetir. Dosyadaki süresi dolmuş kayıtlar yüklenmez ve dosya onlarsız yeniden yazılır.
93
+ */
94
+ export function configureTokenVault(config) {
95
+ ttlMs = (config.tokenTtlMinutes ?? legacyTtlMinutes) * 60 * 1000;
96
+ vaultFile = undefined;
97
+ if (!config.stateDirectory)
98
+ return;
99
+ if (!isLocalTarget(config.orgApiBaseUrl)) {
100
+ if (!warnedRemote) {
101
+ warnedRemote = true;
102
+ logStep('jeton kasası', `ORG_API_BASE_URL lokal değil — kasa dosyaya YAZILMAZ, jetonlar yalnız süreç belleğinde tutulur (MCP_STATE_DIR yok sayıldı)`);
103
+ }
104
+ return;
105
+ }
106
+ vaultFile = join(config.stateDirectory, 'token-vault.json');
107
+ persist();
108
+ }
109
+ /** Kasanın geçerli ömrü (dakika) — yanıt notları sabit bir süre yazmasın. */
110
+ export const tokenTtlMinutes = () => Math.round(ttlMs / 60000);
111
+ export function rememberToken(token, label) {
34
112
  const now = Date.now();
35
113
  sweep(now);
36
114
  for (const [ref, entry] of vault) {
37
115
  if (entry.token === token) {
38
116
  entry.expires = now + ttlMs;
117
+ if (label !== undefined)
118
+ entry.label = label;
119
+ persist();
39
120
  return ref;
40
121
  }
41
122
  }
42
123
  const ref = `tok_${randomBytes(8).toString('hex')}`;
43
- vault.set(ref, { token, expires: now + ttlMs });
124
+ vault.set(ref, { token, expires: now + ttlMs, ...(label !== undefined ? { label } : {}) });
125
+ persist();
44
126
  return ref;
45
127
  }
46
128
  export function resolveToken(reference) {
129
+ if (!vault.has(reference))
130
+ mergeFromFile();
47
131
  const entry = vault.get(reference);
48
132
  if (!entry)
49
133
  return undefined;
50
134
  if (entry.expires <= Date.now()) {
51
135
  vault.delete(reference);
136
+ persist();
52
137
  return undefined;
53
138
  }
54
139
  return entry.token;
55
140
  }
141
+ /**
142
+ * Jetonun kendi süresi (`exp`) kasanın TTL'inden kısa olabilir: uygulamanın jetonu 15 dk yaşarken kasa
143
+ * 60 dk tutuyordu ve süresi dolmuş jetonla çekilen kare sessizce çıkış yapmış sayfayı gösterirdi.
144
+ * 30 sn pay bırakılır; `exp` okunamayan jeton geçerli sayılır (karar uygulamanındır).
145
+ */
146
+ const jwtExpired = (token) => {
147
+ try {
148
+ const payload = JSON.parse(Buffer.from(token.split('.')[1] ?? '', 'base64url').toString('utf8'));
149
+ return typeof payload.exp === 'number' && payload.exp * 1000 <= Date.now() + 30_000;
150
+ }
151
+ catch {
152
+ return false;
153
+ }
154
+ };
155
+ function labeledToken(label) {
156
+ const find = () => {
157
+ const now = Date.now();
158
+ for (const entry of vault.values()) {
159
+ if (entry.label === label && entry.expires > now && !jwtExpired(entry.token))
160
+ return entry.token;
161
+ }
162
+ return undefined;
163
+ };
164
+ const found = find();
165
+ if (found || !vaultFile)
166
+ return found;
167
+ mergeFromFile();
168
+ return find();
169
+ }
170
+ /** Bellekteki kasayı boşaltır (süreç yeniden başlamasının benzetimi); dosyaya dokunmaz. */
56
171
  export function clearTokenVault() {
57
172
  vault.clear();
58
173
  }
@@ -66,8 +181,76 @@ export function resolveIdentityToken(value) {
66
181
  return value;
67
182
  const token = resolveToken(value);
68
183
  if (token === undefined) {
69
- throw new PlatformApiError(`'${value}' referansı geçersiz ya da süresi dolmuş (15 dk). Uygulamanın giriş ucunu microservice_execute ile yeniden çağırıp yanıttaki tokenRefs değerini kullanın.`, 400);
184
+ throw new PlatformApiError(`'${value}' referansı geçersiz ya da süresi dolmuş (${Math.round(ttlMs / 60000)} dk). Tercih edilen yol asUser: 'test:<ad>' — gateway kök .env'deki test kullanıcısıyla kendisi giriş yapar. Yoksa uygulamanın giriş ucunu microservice_execute ile yeniden çağırıp yanıttaki tokenRefs değerini kullanın.`, 400);
185
+ }
186
+ return token;
187
+ }
188
+ /**
189
+ * Giriş yanıtındaki erişim jetonu: yanıt birden çok JWT taşıyabilir (erişim + yenileme). Anahtarı
190
+ * `access` içeren önce, sonra `refresh` içermeyen, en son ilk bulunan — yenileme jetonuyla istek atmak
191
+ * her uçta 401 demekti.
192
+ */
193
+ function accessJwt(value) {
194
+ const found = [];
195
+ const collect = (node, key, depth) => {
196
+ if (depth > 12)
197
+ return;
198
+ if (looksLikeJwt(node)) {
199
+ found.push({ key, token: node });
200
+ return;
201
+ }
202
+ if (Array.isArray(node))
203
+ node.forEach(entry => collect(entry, key, depth + 1));
204
+ else if (node && typeof node === 'object')
205
+ Object.entries(node).forEach(([name, entry]) => collect(entry, name, depth + 1));
206
+ };
207
+ collect(value, '', 0);
208
+ return (found.find(entry => /access/i.test(entry.key)) ?? found.find(entry => !/refresh/i.test(entry.key)) ?? found[0])?.token;
209
+ }
210
+ /**
211
+ * g-09: `asUser` üç şekil alır — ham JWT, `tok_…` referansı ve `test:<ad>`. Üçüncüsünde gateway kök
212
+ * `.env`'deki `MCP_TEST_USER_<AD>` satırının tarif ettiği giriş ucunu (uygulamanın kendi uç'u)
213
+ * anonim çağırır, yanıttaki ilk JWT'yi kasaya etiketiyle koyar ve TTL boyunca yeniden kullanır.
214
+ * Parola ne yanıta, ne günlüğe (`hideRequestBody`), ne hata metnine girer; satırın değeri hiçbir
215
+ * mesajda gösterilmez. AI `.env`'i düzenlemez: tanımsız kullanıcı, insanın ekleyeceği değişkenin
216
+ * adıyla reddedilir.
217
+ */
218
+ export async function resolveIdentity(value, platform, project, environment) {
219
+ if (value === undefined || !value.startsWith('test:'))
220
+ return resolveIdentityToken(value);
221
+ const name = value.slice('test:'.length).trim().toLowerCase();
222
+ const variable = `MCP_TEST_USER_${name.toUpperCase()}`;
223
+ const label = `test:${name}@${platform.resolveProject(project)}/${environment ?? platform.config.defaultEnvironment}`;
224
+ const cached = labeledToken(label);
225
+ if (cached)
226
+ return cached;
227
+ const raw = platform.config.testUsers[name];
228
+ if (!raw) {
229
+ const defined = Object.keys(platform.config.testUsers);
230
+ throw new PlatformApiError(`Test kullanıcısı '${name}' tanımlı değil. Kök .env'e insan ekler: ${variable}={"application":"<uygulama slug>","microservice":"<giriş ucu slug>","body":{…giriş gövdesi…}} (biçim: docs/ortam.md §Test kullanıcıları); gateway yeniden başlatılır. AI .env'i düzenlemez. Tanımlı olanlar: ${defined.length > 0 ? defined.join(', ') : '(yok)'}.`, 400);
231
+ }
232
+ let login;
233
+ try {
234
+ login = JSON.parse(raw);
235
+ }
236
+ catch {
237
+ throw new PlatformApiError(`${variable} JSON olarak okunamadı (değer gösterilmez). Biçim: {"application":"…","microservice":"…","body":{…}} — docs/ortam.md §Test kullanıcıları.`, 400);
238
+ }
239
+ if (typeof login.application !== 'string' || typeof login.microservice !== 'string') {
240
+ throw new PlatformApiError(`${variable} 'application' ve 'microservice' alanlarını taşımalı (değer gösterilmez) — docs/ortam.md §Test kullanıcıları.`, 400);
241
+ }
242
+ const endpoint = `${login.application}/${login.microservice}`;
243
+ const response = await platform.tenantRaw(typeof login.method === 'string' ? login.method : 'POST', project, `/m/${endpoint}`, {
244
+ environment,
245
+ body: login.body,
246
+ anonymous: true,
247
+ hideRequestBody: true
248
+ });
249
+ const token = accessJwt(response.body);
250
+ if (!token) {
251
+ throw new PlatformApiError(`Test kullanıcısı '${name}' girişi jeton döndürmedi: ${endpoint} HTTP ${response.httpStatus}. Kök .env'deki ${variable} satırını (uç ve gövde) ve kullanıcının uygulamada var olduğunu kontrol edin; gövde ve kimlik bilgisi gösterilmez.`, 400);
70
252
  }
253
+ rememberToken(token, label);
71
254
  return token;
72
255
  }
73
256
  /**
@@ -99,7 +99,7 @@ dalından devam eder.
99
99
  | **Komponent adları** | Her komponent adıyla doğrudan değişkendir: `specialties_table.rows = data` hem prop'u yazar hem UI'ı günceller (change event). Addaki `-` silinir: `tickets-table` → `ticketstable` |
100
100
  | `state.page` / `state.app` | Kalıcı state (oku/yaz): `state.page.selectedId = row.id`. **Tanımlı değişkenler de buradadır:** `variable_create` ile yazılan her `key` sayfa düzeyinde `state.page.<key>`, uygulama düzeyinde `state.app.<key>` olur; `value` nesneyse alanlarına inilir (`state.page.MyTest.isActive`) |
101
101
  | `Steps` | Adım çıktıları: `Steps.load_specialties` — anahtar adımın **ADIdır**, `stepId` değil (`setStorage(task.name, …)`); `Steps.<adım>.error` o adımın hatası — ama YALNIZ HTTP durum metnidir (401 için `'Unauthorized'`), ucun mesajı değil. `Steps.error` ise hata akışı için doldurulur (`onError` koşmadan önce `setStorage('error', …)` yapar) ama **YAPIŞKANDIR**: storage sayfa başına tek `FlowMachine`'de yaşar ve `'error'` anahtarı hiç temizlenmez, yani ilk hatadan sonra o sayfadaki her akışta dolu kalır — normal akışta `Steps.error` okuyan bir ifade bayat hatayı görür ve şekli `{ message, code, step: { id, name, type, data } }`'dir: kullanıcıya gösterilecek metin `Steps.error.message`'tır (`err.response.data.message`, boşsa durum metni). **Başarılı bir adımda `Steps.<adım>.data` gerçek bir `Array`'dir** — `.map` · `.filter` · `.concat` · `.length` çalışır. Adım HATA dönerse aynı alan ucun hata gövdesini taşır; aşağıdaki kutuya bakın |
102
- | `self` · `item` · `Row` / `RowIndex` | Tetikleyen komponent (yazılabilir) · repeater/tablo satırı (yazmak satırı günceller) |
102
+ | `self` · `item` · `Row` / `RowIndex` | Tetikleyen komponent (yazılabilir) · repeater/tablo satırı (yazmak YALNIZ o satırı günceller). Satır AKIŞINDA (satırdaki düğümün olayı) ARC-121 düzeltmesini (2026-09-27) taşıyan runtime gerekir, eskisinde boştu — sürümden bağımsız yol `<liste>.selectedItem` / `.selectedIndex`. Tekrarlayıcının KENDİ `change`/`select` akışında satır bağlamı yoktur. `item.x = …` yazımı tekrarlayıcının `change` olayı olarak iletilir: `change`'e bağlı akış her yazımda koşar, o akış da `item`'a yazarsa döngü olur |
103
103
  | `event` | `{name, data, source: {id, props}}` |
104
104
  | `cookie` | `cookie.get(name)` / `cookie.set(name, value, {expiresInHours, path})` / `cookie.remove(name)` — tarayıcı cookie'si |
105
105
  | `setComponentProperty(name, key, value)` / `getComponentProperty(name, key)` | fonksiyon alternatifi |
@@ -242,9 +242,16 @@ DTO'dan süzülmez (WF-026 — `platform_guide('workflow')` §Hata yolu). Sözle
242
242
  - **Operatör sözlüğü kapalıdır:** `eq · neq · lt · lte · gt · gte · contains · icontains ·
243
243
  in · notin · startswith · endswith · istartswith · iendswith · isnull · isnotnull`.
244
244
  **`like` YOKTUR** — metin
245
- araması `contains`/`icontains` iledir. Kritik tuzak: platform bilinmeyen operatörü 400 ile
245
+ araması `contains`/`icontains` iledir. Kritik tuzak: eski backend bilinmeyen operatörü 400 ile
246
246
  reddetmez, **sessizce `eq`'e düşürür** (enum çözümlemesi unknown'da default'a düşer) — uç
247
- testten geçer ama arama hiçbir şey döndürmez. Gateway MS-012/WF-015 bunu istek gitmeden keser.
247
+ testten geçer ama yanlış sonuç döner (koşuda `ne` böyle geçti). 2026-09-27'den sonraki backend
248
+ uç çağrısının `filters` parametresinde (JSON dizisi) 400 verir; workflow tablo adımının filtresi
249
+ hâlâ sessizce `eq`'e düşer. Ad büyük/küçük harfe duyarlıdır: `NEQ` geçersizdir. Gateway her iki
250
+ sürümde istek gitmeden keser: uç tanımında ve
251
+ workflow tablo adımında MS-012/WF-015 (blok), çalışma anında `row_list` / `row_export` filtresinde ve
252
+ `microservice_execute` / `microservice_debug`'ın `filters` parametresinde hata — sık yanlışlara öneriyle
253
+ (`ne`/`!=` → `neq`, `like` → `contains`, `=` → `eq`). Studio'nun filtre ARAYÜZÜNDEKİ adlar
254
+ (`notContains`, `between`…) ayrı bir sözlüktür; arayüz onları bu işleçlere çevirir.
248
255
  - **Write operasyonunda (`create`/`update`) yanıt yalnız primary key'dir** — platform sonucu
249
256
  PK'ya indirger ve `selectFields`'ı kaydederken sessizce boşaltır. Yanıtın başka alan da
250
257
  dönmesi gerekiyorsa **`returnFields`** kullanılır (öğeler `selectFields` ile aynı şekildedir:
@@ -57,7 +57,11 @@ cevabı adımın ifadesinde gizlidir. Ayrıntı ve yazım ölçütü: `platform_
57
57
  Gateway `set-user` adımı olmadan `user` okuyan middleware'i **MW-003** ile uyarır.
58
58
  4. **`input` YOKTUR.** Middleware girdisini HTTP bağlamından okur: `cookie`, `header`,
59
59
  `queryString`, `body`, `triggerSource` (hangi microservice/sayfa tetikledi: type, name,
60
- slug, roles, impersonatedUserId). Hazır fonksiyonlar: `system.validateToken(...)`,
60
+ slug, roles, impersonatedUserId). `impersonatedUserId` ve start adımının `impersonation`
61
+ dalı yalnız Studio önizlemesindeki doğrulanmış platform kullanıcısı için dolar; anonim ya da
62
+ yetkisiz istekte başlık platformda silinir. `impersonation` dalı bağlı değilse start
63
+ `success`'e düşer — uygulamanın kendi auth yolu koşar (ZM-22, 2026-09-26). Hazır
64
+ fonksiyonlar: `system.validateToken(...)`,
61
65
  `system.generateToken(...)`, `crypto.JWT.encode/validate`, `Authorized(user.roles)`,
62
66
  `request.url.searchParams`, `GenerateGuid()`, `UtcNow()`, `system.getPageUrl(slug)` —
63
67
  **tam sözlük ve gerçek örnekler: `platform_guide('expressions')`**.
@@ -37,7 +37,7 @@ Mobil uygulamanın bileşen kataloğu ayrıdır (backend satırları `(slug, typ
37
37
  mobil farkları: düşen prop, değişen etkileşim, mobile özgü prop notları) ve `mobileNotes` (bütün
38
38
  mobil bileşenlere geçen notlar). Web'de karşılığı olmayan bileşende (`app-bar`, `safe-area`,
39
39
  `scroll-view`, `choice-chips`, `tooltip`, `markdown`, `calendar`, `stack`, `list-item`, `fab`, `timer`,
40
- `audio`, `lottie`, `webview`) bilgi yalnız `mobileKnowledge`'tadır. Olay yükleri
40
+ `audio`, `lottie`, `webview`, `map`, `grouped-list`, `floating-container`) bilgi yalnız `mobileKnowledge`'tadır. Olay yükleri
41
41
  (`events[].payload`) önce mobile özgü sözleşmeden okunur (`app-bar.clickAction`, `repeater.swipe`,
42
42
  `choice-chips.change`…), yoksa web'inkinden.
43
43
 
@@ -46,7 +46,7 @@ mobil bileşenlere geçen notlar). Web'de karşılığı olmayan bileşende (`ap
46
46
  olup mobilde henüz olmayan için ne zaman geleceğini söyler (Dalga 2, Faz 3).
47
47
  - **Katalog A** (FlutterFlow paritesi, 2026-09-24) on bileşen ekledi: `badge`, `avatar`, `progress`,
48
48
  `radio`, `checkboxgroup`, `choice-chips`, `collapsible-container`, `slider`, `tabs`, `tooltip`;
49
- `button` mobilde `secondary`/`ghost`/`link` varyantlarını ve `icon` boyunu alır. Tam liste
49
+ `button` mobilde `secondary`/`ghost`/`link`/`tonal` varyantlarını ve `icon` boyunu alır. Tam liste
50
50
  `component_list`'tir.
51
51
  - **Katalog B** (FlutterFlow paritesi) önce dört prop'la başladı; hepsi YALNIZ mobil şemadadır:
52
52
  `scroll-view.stickyHeader` (ilk çocuk dikey kaydırmada üstte kalır), `numberinput.variant: stepper`
@@ -74,11 +74,42 @@ mobil bileşenlere geçen notlar). Web'de karşılığı olmayan bileşende (`ap
74
74
  İkisinde çalma `isPlaying` prop'udur: akış `true`/`false` yazar, bileşen `play`/`pause`/`ended` ya da
75
75
  `finish` olayında `{ isPlaying }` yayar ve prop'u gerçeğe döndürür.
76
76
  İkinci adım: `signature` web ikizidir (her vuruşta `change { value }` PNG data URL; vuruş sürerken sayfa
77
- kaymaz; iOS'ta itilmiş sayfada sağa vuruş sayfayı kapatır, ARC-116) ve `webview` mobile özgüdür (web
77
+ kaymaz; iOS 26'da itilmiş sayfada sağa vuruş sayfayı kapatır — platformun geri jesti) ve `webview` mobile özgüdür (web
78
78
  `iframe`inin karşılığı): `src` ya da `html`; yüklemeden sonra başka belgeye giden gezinme varsayılan olarak
79
79
  engellenir ve `navigate { url }` yayar, adres yalnız kullanıcının dokunduğu bağlantıda sistem tarayıcısında
80
80
  açılır (`allowNavigation` görünümde açar); sayfa akışa `window.ReactNativeWebView.postMessage(metin)` →
81
81
  `message { data }` ile yazar, yüklenemeyen sayfa `error` yayar.
82
+ Üçüncü adım: `chart` web ikizidir — seçenek ve olay eşlemesi web'in modülünden gelir, iki platform aynı
83
+ ECharts seçeneğini çizer (cihazda SVG); dışa aktarma, tip değiştirici, yenile ve kırıntı araç çubuğu
84
+ düşer. `map` mobile özgüdür (cihazda Apple/Google Haritalar, kanvas ve önizlemede Leaflet +
85
+ OpenStreetMap): görünüm `latitude`/`longitude`/`zoom` sayılarıdır ve akış yazınca harita oraya gider;
86
+ kullanıcı haritayı hareket ettirince `regionChange { latitude, longitude, zoom }` düğüme geri yazılır,
87
+ işaretçiye dokunmak `markerClick { markerId }` yayar.
88
+ Dördüncü adım: `datepicker`, `timepicker` ve `datetimepicker` web ikizleridir — kutuya dokununca cihazda
89
+ işletim sisteminin seçicisi (iOS alttan açılan sayfa, seçim Tamam'la yazılır; Android iletişim kutusu),
90
+ önizlemede tarayıcının seçicisi açılır; değer ve `change` yükü web ile aynıdır. Sistem seçicisi tek tek
91
+ saati kapatamadığı için `minTime`/`maxTime`/`disabledTimes` dışındaki seçim izinli en yakın saate yaslanır;
92
+ `datetimepicker` tek kutudur (`timePlaceholder` yok). `page_screenshot` `set` kökte değil
93
+ `<elementId>__input`ta, HTML biçimiyle çalışır (`2026-08-01`, `09:30`, `2026-08-01T09:30`). Tasarım takvim
94
+ istiyorsa (Pazartesi başlangıcı, "Bugün / Dün" çipleri, `24.09.2026`): `datepicker` `presentation: calendar` +
95
+ `quickPicks` + `displayFormat`; aralık için `daterangepicker` (web ikizi, `valueFrom`/`valueTo`, hep takvim
96
+ sayfası). `fileInput` ve `filebutton` de web ikizidir:
97
+ değer web gibi base64 veri URL'sidir — bileşen kendisi yüklemez, kayda Table microservice'in FILE
98
+ parametresiyle yazılır (§Dosya ve fotoğraf); tür/boyut/adet kapısı ve uyarılar web kuralı.
99
+ Seçiciyi mobile özgü `source` seçer — `files` (belge seçici), `library` (fotoğraf arşivi), `camera`,
100
+ `ask` (her seferinde sorar; `filebutton`'ın varsayılanı); fotoğraflar `maxImageDimension` (varsayılan
101
+ 1600 px uzun kenar) ile küçültülüp `quality` ile sıkıştırılır ve iOS'ta HEIC JPEG'e çevrilir.
102
+ Önizlemede tarayıcının dosya penceresi açılır (kanvasta alan kilitli); kırpma yoktur.
103
+ - **Liste ve sabit konum** (2026-09-26): `grouped-list` (başlıklı gruplu liste — §Güne göre görev
104
+ listesi) ve `floating-container` (ekranın 9 noktasından birine sabit kap — §Ekrana sabit içerik)
105
+ mobile özgüdür. `scroll-view`'de ilk çocuk dışındaki doğrudan çocuklar `states.isSticky: true` ile
106
+ kaydırırken üstte kalır (Studio panelinde alanı yok; şemaya MCP yazar).
107
+ - **Faz 10** (FlutterFlow D listesi): `datatable` web ikizidir — kolon biçimi, sıralama ve sayfa hesabı web'in
108
+ modülünden, akış sözleşmesi aynı (sıralama/sayfa değişince `sortField`/`sortType`/`offset` düğüme yazılır ve
109
+ `mount` yeniden koşar; satır eylemi `selectedRow`u yazıp eylemin akışını koşturur). Mobile özgü `variant`:
110
+ `table` (varsayılan, yatay kaydırılan tablo; başlığa dokunuş `__sort-<kolon>`) ya da `cards` (satır başına
111
+ kart). Eylemler `__action-<satır>-<sıra>`, `dropdown` kipinde `__more-<satır>` → alttan eylem sayfası;
112
+ sayfa çubuğu `__page-prev`/`__page-next`. Filtre düzenleyicisi, özet satırı ve sabit eylem sütunu yok.
82
113
  - Web'e özgü bileşenlerin mobil karşılığı:
83
114
 
84
115
  | Web | Mobilde |
@@ -89,9 +120,124 @@ mobil bileşenlere geçen notlar). Web'de karşılığı olmayan bileşende (`ap
89
120
  | `breadcrumb` | yığın gezinmesi (app-bar geri düğmesi) |
90
121
  | `iframe` | `webview` (`src` ya da `html`) |
91
122
  | `theme-toggle` | cihaz teması |
123
+ | `fileUpload` | `fileInput` (alan) ya da `filebutton` (düğme) — sürükle-bırak yok |
92
124
 
93
125
  **Harici bileşen mobilde yoktur** (`MOB-006`): protokol v1 tarayıcı `.mjs` paketidir;
94
- `external_component_*` araçları mobil uygulamada platforma gitmez.
126
+ `external_component_*` araçları mobil uygulamada platforma gitmez. **Geçici yol `html`** (2026-09-28):
127
+ katalogda karşılığı olmayan küçük görünüm (özel rozet, SVG, biçimli metin) `html` ile konur — içerik
128
+ kendi korumalı belgesinde çizilir (uygulamanın çerezine/jetonuna ulaşamaz), tema `:root` değişkeni
129
+ olarak gelir (`hsl(var(--primary))`, `var(--ng-…)`), boy içeriğe göre ayarlanır, bağlantı sistem
130
+ tarayıcısında açılır. Akışla iki yön: belgede `fireEvent(key, data)` → `actions` satırının akışı,
131
+ `window.ngApps.emit(name, data)` → `message` olayı (`event.name`/`event.data`); akışın `message`
132
+ prop'una yazdığı değer belgede `window.ngApps.on(name, fn)` ile alınır. Aynı köprü `webview`de de var.
133
+ - **Etkileşim yüzeyleri** (2026-09-27): bileşik kart `container` + `click` ile kurulur (mobile-only olay;
134
+ bağlıysa kap runtime'da basılır, içteki düğme kabın `click`ini tetiklemez) — `card` çocuk almaz.
135
+ `button` ikonu (`icon` + `iconPosition: left|right|top`) ve rozeti (`badgeValue`, `'•'` nokta) çizer;
136
+ `iconPosition: top` + `heightType: fixed` + `variant: tonal` dikey hızlı eylem karosudur. `size: icon`
137
+ yalnız glif çizer: `ariaLabel` verin (MOB-013). `icon` `click`/`href` ile basılır, `isDisabled` soldurur.
138
+ `modal`/`drawer` `actions` düğme satırı çizer (`action` olayı, `currentItem`; `type: submit` formu
139
+ gönderir). Dış bağlantı (`href`) sistem tarayıcısında açılır; uygulama içi yol akışın `redirect`'idir.
140
+
141
+ ## Dosya ve fotoğraf
142
+
143
+ "Fotoğraf çek → kaydet → listede göster" üç parçadır; yükleme ucu yoktur, dosya kaydın yazıldığı
144
+ istekle gider.
145
+
146
+ 1. **Tablo.** Entity'ye `dbType: 'FILE'` + `commonType: 'file'` alanı (ör. `foto`; tekil dosya,
147
+ `relationId` verilmez — `platform_guide('entity')`).
148
+ 2. **Yazma ucu.** Table microservice `operation: 'create'` (ya da `upsert`); `fields`'ta alanı
149
+ `{ fieldSlug: 'foto' }` ile listeleyin. Uç gövdede alanın slug'ıyla `type: 'file'` bir parametre
150
+ bekler ve değeri `{ type, value, fileName }` biçiminde okur (`TableFileParameter`): `type: 'file'`
151
+ base64'ü depoya yazar ve kayda dosya kimliğini koyar, `type: 'ref'` var olan dosyayı (`value` =
152
+ dosya id'si) bağlar, `value: null` alanı boşaltır. Dosya alanının DEĞERİ bu biçimdedir — dönüştürmeyin.
153
+ 3. **Form sayfası.** `fileInput` (`source: 'camera' | 'library' | 'ask'`) ya da `filebutton` + kaydet
154
+ düğmesi. Düğmenin akışında `microservice` adımı, parametre alanın değerini olduğu gibi taşır:
155
+
156
+ ```json
157
+ { "name": "foto", "type": "file", "source": "body",
158
+ "value": { "type": "property", "ref": { "element": "foto_in", "property": "value" } } }
159
+ ```
160
+
161
+ Base64 gövdeyle gider: fotoğraflar istemcide `maxImageDimension` (1600 px) ile küçültülür,
162
+ `maxFileSizeMb` mobilde varsayılan 10'dur — ikisini de sıfırlamayın. Kamera izni reddedilirse alan
163
+ Ayarlar'a yönlendiren bir uyarı gösterir.
164
+ 4. **Liste.** `get` ucu FILE alanını `{ id, type: 'ref', value: <dosya id>, absolute_path: <url>,
165
+ fileName }` olarak döndürür. `image.src`, `avatar.src`, `card.image`, `list-item.leadingImage`'a
166
+ **nesnenin kendisini** verin: `{ "type": "code", "ref": "{{ item.foto }}" }` (`.absolute_path` da
167
+ çalışır). `{{ item.foto.value }}` dosya **kimliğidir** (GUID), görsel çizilmez — `MOB-011` uyarır.
168
+ Gateway alan tipini şemadan bilemediği için uyarı metinseldir: `.value` ile biten en az üç parçalı
169
+ yol (`urlInput.value` gibi bileşen değeri ve `fileInput1.value.value` uyarı almaz).
170
+
171
+ Workflow'un dosya yükleme adımı yalnız `{ id }` döndürür (URL yok); görsel adresi kaydı okuyan uçtan
172
+ gelir. `page_screenshot` ile listede görseli doğrulayın.
173
+
174
+ ## Güne göre görev listesi
175
+
176
+ Başlıklar altında gruplanmış liste (güne, odaya, kategoriye göre) **tek `grouped-list`** ile kurulur.
177
+ İç içe repeater `PAGE-013` ile reddedilir; `textarea` hilesi de gerekmez — liste düz satır dizisidir,
178
+ bölümleri bileşen render anında çıkarır.
179
+
180
+ 1. **Veri.** Düz satırlar (ör. `q_tasks` sorgusu: `title`, `dueDate`, `done`). Sıralama sorguda
181
+ değil bileşende: `sectionOrder` `asc` (varsayılan) · `desc` · `asis`.
182
+ 2. **Düğüm.** `rows: { type: 'code', ref: '{{ q_tasks.data }}' }`, `groupBy: 'dueDate'` (noktalı yol
183
+ olabilir: `plant.room`). Tarih/saat değeri cihazın yerel gününe (`2026-09-26`) indirgenir; alanı
184
+ boş satırlar en sonda `otherSectionTitle` (varsayılan `Other`) bölümündedir.
185
+ 3. **Çocuklar konumsaldır:** çocuk 0 bölüm başlığı, çocuk 1 öğe şablonu; fazlası çizilmez.
186
+
187
+ ```json
188
+ { "elementId": "tasks", "name": "tasks", "type": "grouped-list",
189
+ "props": { "rows": { "type": "code", "ref": "{{ q_tasks.data }}" },
190
+ "groupBy": { "type": "static", "data": "dueDate" },
191
+ "itemPressable": { "type": "static", "data": true } },
192
+ "children": [
193
+ { "elementId": "day_title", "name": "day_title", "type": "text",
194
+ "props": { "content": { "type": "code", "ref": "{{ Section.title }} · {{ Section.count }}" } } },
195
+ { "elementId": "task_row", "name": "task_row", "type": "container", "props": {},
196
+ "children": [ { "elementId": "task_title", "name": "task_title", "type": "text",
197
+ "props": { "content": { "type": "code", "ref": "{{ item.title }}" } } } ] } ] }
198
+ ```
199
+
200
+ Başlık `Section` okur (`{ key, title, count, items }`), satır kapsamı taşımaz; bölüm dışında
201
+ `Section` boş nesnedir (`Row` gibi). Öğe repeater satırıyla aynıdır: `Row`, `RowIndex`,
202
+ `{{ item.<alan> }}`; akışta tıklanan satır `tasks.selectedItem`. Şablon çocukları setter hedefi
203
+ değildir — satır dışına (sayfadaki bir metne, modala) yazın.
204
+ 4. **Olaylar.** `itemPress { index, row }` (yalnız `itemPressable`; öğe şablonu `list-item`/`button`
205
+ ise dokunuş onun `click`'ine gider), `swipe { key, index, row }` (`swipeActions`),
206
+ `change { selectedIndex }`, `sectionPress` (yük `Section`). `index` `rows` içindeki DÜZ indekstir.
207
+ 5. **Yapışkan gün başlığı.** `stickySectionHeaders: true` + `appearance.heightType: 'fixed'` +
208
+ `heightString`. Sayfanın kendisi kaydırıcıdır; `auto` ya da `fill` liste içerik kadar uzar ve başlık
209
+ yapışmaz. Başlık kabına kendi zeminini verin.
210
+
211
+ ## Ekrana sabit içerik — FAB, alt eylem çubuğu, üst şerit
212
+
213
+ Mobil şemada `style.position`/`zIndex` yoktur (`MOB-002`); sayfa kayarken yerinde duran içerik iki
214
+ bileşenle kurulur. İkisi de sayfanın neresine konursa konsun runtime'da ekran katmanına taşınır —
215
+ düğümü sayfa kök kabının SONUNA koyun (akıştaki yeri boş kalır).
216
+
217
+ - **Sağ alt tek düğme:** `fab` (`position: bottom-right | bottom-center | bottom-left`, `icon`,
218
+ `click`). Sekme çubuğu varsa onun üstünde, kenardan 16 dp.
219
+ - **Alt eylem çubuğu / birden çok düğme / üst ya da orta çapa:** `floating-container`.
220
+ `anchor` 9 nokta (`top-left` … `bottom-right`, varsayılan `bottom-right`), `offsetX`/`offsetY` (dp,
221
+ varsayılan 16, çapalı kenardan içeri), `fullWidth` (alt/üst orta satırı kaplar; `offsetX` yok
222
+ sayılır), `respectSafeArea` (varsayılan açık). Alt eylem çubuğu:
223
+
224
+ ```json
225
+ { "elementId": "action_bar", "name": "action_bar", "type": "floating-container",
226
+ "props": { "anchor": { "type": "static", "data": "bottom-center" },
227
+ "fullWidth": { "type": "static", "data": true } },
228
+ "children": [ { "elementId": "bar", "name": "bar", "type": "container",
229
+ "props": { "direction": { "type": "static", "data": "row" } }, "children": [] } ] }
230
+ ```
231
+
232
+ Kabın kendi yüzeyi yoktur: zemin, köşe, gölge ve iç boşluk içerideki kaba (`surfaceStyle`) verilir.
233
+ Dip sekme çubuğunun tepesidir — `aboveTabBar` diye bir prop YOKTUR. App-bar sayfayla kayar; üst
234
+ çapalı kap app-bar'ın üstüne biner, sabit app-bar'ın altına `offsetY` ile inilir (ör. 72).
235
+ - **Kaydırırken üstte kalan başlık:** `scroll-view` içinde ilk çocuk `stickyHeader: true`, diğer
236
+ doğrudan çocuklar düğüm durumu `states: { isSticky: true }` ile (yalnız dikey; kaydırıcıya `fixed`
237
+ boy verin). Sayfayı örten katman için `modal` / `drawer` widget'ı.
238
+
239
+ `page_screenshot` (viewport karesi, `layoutReport: true`) sabit içeriğin çapalı noktada, sekme çubuğunun
240
+ üstünde durduğunu gösterir; uzun sayfada içerik kabın altından akar.
95
241
 
96
242
  ## Yerleşim — yalnız flex
97
243
 
@@ -109,6 +255,9 @@ Mobil çizer 12 sütunlu ızgarayı **okumaz**: sıra dizi indeksidir, yerleşim
109
255
  alır (`container`, `form`, `safe-area`, `scroll-view`) ve akışları oranlı boyu doldurur; öteki
110
256
  bırakılabilir tipler, widget örneği, sayfa kökü ve `modal`/`drawer` almaz; `image` oranı kendi
111
257
  çizimine yazar.
258
+ - `image` boyu: `heightType: 'fixed'` + `heightString` (`px`/`%`) ya da `'fill'` → görsel kabuğu doldurur
259
+ (`%` ve `fill`de 96 dp taban boyun altına inmez: atası içerik boyundaki kapta yüzde çözülmez); yoksa `aspectRatio` + `heightType: 'auto'` + belli genişlik → boy orandan; ikisi de yoksa 96 dp taban boy
260
+ ve MOB-012 bilgi satırı. Küçük resim için `widthType: 'fixed'` + `widthString` + oran `1` kullanın.
112
261
  - Hiza (`verticalAlign` `top | middle | bottom`, `horizontalAlign` `left | center | right`) web ile
113
262
  aynıdır. Dikey hizanın boşluğu kabın boyundan gelir: `heightType` `fill`, `fixed` ya da oranlı kapta
114
263
  akış kabın boyunu doldurur; boyu içerikten gelen sütun kapta dikey hiza etkisizdir (satır kapta farklı
@@ -121,7 +270,11 @@ Mobil çizer 12 sütunlu ızgarayı **okumaz**: sıra dizi indeksidir, yerleşim
121
270
  React Native'de CSS yoktur; tipli stil grupları **derlenir** (web'in `composeTypedStyle` ikizi,
122
271
  aynı katman sırası: tema taban → gruplar → serbest `style`).
123
272
 
124
- - `surfaceStyle` → zemin, köşe, kenarlık, gölge (tek katman), opaklık, taşma.
273
+ - `surfaceStyle` → zemin, köşe, kenarlık, gölge (tek katman), opaklık, taşma; döndürme `rotate`
274
+ (derece), ölçek `scale` (yüzde, 100 aynı boy), kaydırma `translateX`/`translateY` (px). Sıra web
275
+ ile aynı (kaydır → döndür → ölçekle, köken merkez), düğüm çocuklarıyla döner. Değer birimsiz sayıdır
276
+ (`'15deg'` yazılmaz); aralık dışı (`rotate` ±360, `scale` 10–500, kaydırma ±1000) yazılmaz ve
277
+ `nativeStyle.warnings`'e düşer. Katman ve kabukta (`modal`, `drawer`, `app-bar`) bu dört alan yoktur.
125
278
  - `typographyStyle` → renk, aile (tek ad), punto, kalınlık, hizalama, satır yüksekliği.
126
279
  - Tema token'ları (`var(--ng-*)`) sayılara çözülür; proje teması değişince mobil de değişir.
127
280
 
@@ -130,10 +283,11 @@ aynı katman sırası: tema taban → gruplar → serbest `style`).
130
283
  | Alan | Yerine |
131
284
  |---|---|
132
285
  | `className` | `surfaceStyle` / `typographyStyle` |
133
- | serbest `style` nesnesi (alt seçici `&:hover`, `&__body`) | tipli gruplar; düz anahtarlar (ör. `padding`) çevrilir, alt seçici düşer |
286
+ | serbest `style` nesnesi (alt seçici `&:hover`, `&__body`) | tipli gruplar; düz anahtarlar (ör. `padding`) çevrilir, alt seçici düşer. İstisna `html`: metin (ya da koda bağlı) `style` kendi belgesinin CSS'idir ve çizilir; statik nesne `style` orada da düşer |
134
287
  | `hoverStyle` | yok — dokunmatikte üzerine gelme durumu yoktur |
135
288
  | `tooltip`, `tooltipPosition` | düğümü `tooltip` bileşenine sarın (dokunuşla açılır, yanı `side`) ya da açıklamayı metin olarak koyun |
136
- | `zIndex` | katman için `modal` ya da `drawer` widget'ı |
289
+ | `zIndex`, `style.position` | görsel üstüne bindirme (rozet, hap, tarih etiketi, açıklama bandı, arka plan görseli): `stack` — ilk çocuk taban, ötekiler üst katman; her köşe için `fill`×`fill` bir `container` katmanı kendi hizasıyla; görselin altına binen kart: kartın üst boşluğu negatif (`marginValues.top: -28`); ekrana sabit: `floating-container` / `fab`; yapışkan başlık: `scroll-view` çocuğunda `states.isSticky`; örten katman: `modal` / `drawer` (§Ekrana sabit içerik) |
290
+ | `surfaceStyle`'ın tanımadığı alt anahtar (ör. CSS `transform` metni, `padding`) | grubun tipli alanları; döndürme/ölçek/kaydırma `rotate`, `scale`, `translateX`/`translateY` |
137
291
 
138
292
  Düğümün gerçek stilini görmek: `page_runtime_get({ applicationSlug, pageSlug, resolveStyles: true })`
139
293
  → her düğümde `nativeStyle: { shell, text, warnings }` (`shell` kutunun, `text` içine akan metnin
@@ -199,8 +353,8 @@ Mobil uygulama web ile **aynı sözlüğü ve aynı motoru** kullanır (plans/mo
199
353
  → `translation_update`. Kurulum ve fallback zinciri `platform_guide('translation')`.
200
354
 
201
355
  - **Dil değiştirici:** `language-selector` mobil katalogdadır (`component_get` → `mobileKnowledge`):
202
- `dropdown`un tam ekran `Modal` listesi, tetikleyici `role="combobox"`, seçenekler dilin yerel adıyla
203
- (`tr` → Türkçe). Seçim `machine.setLanguage` ile bütün prop'ları yerinde çözer ve cihazda uygulama
356
+ `dropdown`un listesi (az dilde alanın altında, çok dilde alt sayfada), tetikleyici `role="combobox"`,
357
+ seçenekler `displayMode`'a göre dilin yerel adıyla (`tr` → Türkçe), koduyla (`TR`) ya da ikisiyle. Seçim `machine.setLanguage` ile bütün prop'ları yerinde çözer ve cihazda uygulama
204
358
  çapında yayılır: öteki sekmelerin ekranları ve sekme etiketleri de değişir, makine yeniden kurulmaz.
205
359
  - **Cihazda boot sırası:** uygulama içi seçim → cihazda kayıtlı seçim (`zeyrek:<app>:lang`) → cihaz dili
206
360
  → projenin varsayılanı. `?lang` CİHAZDA YOKTUR; `/preview`'da vardır, bu yüzden `page_screenshot(language:)`
@@ -225,11 +379,12 @@ Motorun kendi adımları (`setter`, `condition`, `microservice`, `code`, `functi
225
379
  |---|---|
226
380
  | `redirect` | yığına iter (göreli yol) ya da dışarıda açar (tam adres) |
227
381
  | `navigate` | eski mobil şemaların adı — `redirect`'in takma adıdır; gateway `redirect`'e çevirir |
228
- | `alert` | tipli uyarı (`success`/`error`/`info`/`warning`) toast'tur ve beklemez; düz uyarı onay bekler |
382
+ | `alert` | tipli uyarı (`success`/`error`/`info`/`warning`) toast'tur ve beklemez; düz uyarı onay bekler. 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 }`) |
229
383
  | `confirm` | `yes` / `no` dalları |
230
384
  | `download` | adres cihazın açma sayfasında açılır, gövde paylaşım sayfasına gider (`MOB-005`, bilgi) |
231
385
  | `storage` | cihaz deposu |
232
386
  | `setcookie` | cihaz deposuna yazar (`MOB-005`, bilgi); paletten yalnız web'de sunulur |
387
+ | `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ı |
233
388
 
234
389
  Sayfa ve layout `mount` akışları ilk çizimden sonra, `destroy` akışları sayfa kalkarken koşar.
235
390
  `Functions.<ad>()` uygulama ve sayfa fonksiyonlarını görür (`platform_guide('functions')`).