@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.
- package/.env.example +15 -0
- package/dist/config.js +9 -0
- package/dist/index.js +2 -0
- package/dist/knowledge/componentCatalog.js +4 -2
- package/dist/knowledge/componentEvents.js +3 -3
- package/dist/knowledge/mobileComponentCatalog.js +308 -50
- package/dist/knowledge/mobileComponentEvents.js +130 -3
- package/dist/knowledge/normalize.js +16 -6
- package/dist/knowledge/platform/lint.js +110 -3
- package/dist/knowledge/platform/mobile.js +1 -1
- package/dist/knowledge/platform/mobileRules.js +70 -11
- package/dist/knowledge/platform/web.js +1 -1
- package/dist/knowledge/rules.js +106 -11
- package/dist/knowledge/styleResolve.js +49 -3
- package/dist/knowledge/styleResolveNative.js +104 -4
- package/dist/observability/trace.js +1 -1
- package/dist/platform/argumentAliases.js +128 -0
- package/dist/platform/bodyFile.js +103 -0
- package/dist/platform/capture/capture.js +29 -5
- package/dist/platform/capture/console-log.js +80 -0
- package/dist/platform/capture/index.js +1 -0
- package/dist/platform/diagnostics.js +10 -4
- package/dist/platform/http.js +23 -7
- package/dist/platform/responses.js +6 -2
- package/dist/platform/schemaReadMemory.js +60 -0
- package/dist/platform/secrets.js +9 -0
- package/dist/platform/strictArguments.js +68 -12
- package/dist/platform/tokenVault.js +187 -4
- package/dist/resources/guides/expressions.md +1 -1
- package/dist/resources/guides/microservice.md +9 -2
- package/dist/resources/guides/middleware.md +5 -1
- package/dist/resources/guides/mobile.md +165 -10
- package/dist/resources/guides/page.md +161 -41
- package/dist/resources/guides/theme.md +4 -1
- package/dist/resources/guides/workflow.md +7 -1
- package/dist/resources/schema-fingerprint.json +2 -2
- package/dist/server.js +1 -1
- package/dist/tools/datasources.js +1 -1
- package/dist/tools/designSchemaPatch.js +4 -3
- package/dist/tools/externalComponents.js +1 -1
- package/dist/tools/flows.js +70 -4
- package/dist/tools/layouts.js +5 -5
- package/dist/tools/microservices.js +26 -10
- package/dist/tools/pages.js +192 -36
- package/dist/tools/platformInfo.js +4 -1
- package/dist/tools/rows.js +7 -1
- package/dist/tools/schemaOps.js +1 -1
- package/dist/tools/screenWrite.js +4 -3
- package/dist/tools/screens.js +81 -0
- 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
|
|
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
|
-
|
|
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ş (
|
|
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:
|
|
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
|
|
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).
|
|
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'
|
|
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 `
|
|
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
|
|
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')`).
|