@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
|
@@ -12,7 +12,23 @@ export const mobileComponentEventContracts = {
|
|
|
12
12
|
'choice-chips': {
|
|
13
13
|
change: {
|
|
14
14
|
payload: {
|
|
15
|
-
value: 'string —
|
|
15
|
+
value: 'string | number | boolean | Array — seçenek ÖZGÜN tipinde (değeri `34` yazılmış seçenek sayı yayar). `multiple` açıkken seçili değerlerin DİZİSİ (`[34, "a"]`); tek kipte seçili çipe yeniden dokunmak boş metin (`""`) yayar. Devre dışı seçenek olay yaymaz.'
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
// Web ikizinin yükü metindir (`e.currentTarget.value`); mobil seçeneği ÖZGÜN tipinde yayar (f-06, `optionEmitValues`).
|
|
20
|
+
// İki platformda aynı koşul için karşılaştırmayı metne çevirin: `String(self.value) === '34'`.
|
|
21
|
+
radio: {
|
|
22
|
+
change: {
|
|
23
|
+
payload: {
|
|
24
|
+
value: 'string | number | boolean — seçilen seçeneğin ÖZGÜN değeri (değeri `34` yazılmış seçenek sayı yayar; web aynı seçimi `"34"` metni olarak yayar)'
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
checkboxgroup: {
|
|
29
|
+
change: {
|
|
30
|
+
payload: {
|
|
31
|
+
value: 'Array — seçili seçeneklerin ÖZGÜN değerleri (`["su", 9]`; web metin dizisi yayar). Seçeneklerde olmayan eski değer metin kalır'
|
|
16
32
|
}
|
|
17
33
|
}
|
|
18
34
|
},
|
|
@@ -56,9 +72,31 @@ export const mobileComponentEventContracts = {
|
|
|
56
72
|
mount: NO_DATA,
|
|
57
73
|
load: { payload: {}, reads: "her tamamlanan yüklemede yayılır (izin verilen gezinmeden sonra da); cihazda yüklenemeyen sayfada yayılmaz (yerine error), web'de tarayıcı engellenen çerçevede de yayar" },
|
|
58
74
|
navigate: { payload: { url: "string — sayfanın gitmek istediği adres; makine url prop'una yazar (self.url). allowNavigation kapalıyken (varsayılan) gezinme engellenir; adres yalnız kullanıcının dokunduğu bağlantıda sistemde açılır" } },
|
|
59
|
-
message: {
|
|
75
|
+
message: {
|
|
76
|
+
payload: { data: "string — sayfanın window.ReactNativeWebView.postMessage(metin) ile yolladığı metin, olduğu gibi; makine data prop'una yazar (self.data)" },
|
|
77
|
+
reads: "iki yönlü köprü: sayfa window.ngApps.emit(name, data) çağırırsa yük olayın params'ında gelir — akış event.name ve event.data okur (self.data yazılmaz)"
|
|
78
|
+
},
|
|
60
79
|
error: { payload: {}, reads: "cihazda sayfa yüklenemeyince (ağ hatası) yayılır, görünüm yer tutucu çizer; web'de pratikte gelmez (iframe gibi)" }
|
|
61
80
|
},
|
|
81
|
+
html: {
|
|
82
|
+
mount: NO_DATA,
|
|
83
|
+
message: {
|
|
84
|
+
payload: {},
|
|
85
|
+
reads: "belgenin window.ngApps.emit(name, data) çağrısı; yük olayın params'ındadır — akış event.name (ileti adı) ve event.data (veri) okur. actions satırının akışı ise fireEvent(key, data) ile koşar: event.name = key, event.data = veri"
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
map: {
|
|
89
|
+
mount: NO_DATA,
|
|
90
|
+
markerClick: { payload: { markerId: "string — dokunulan işaretçinin `id`si (boşsa listedeki sırası); makine markerId prop'una yazar (self.markerId). Başlığı ya da açıklaması olan işaretçide balon da açılır" } },
|
|
91
|
+
regionChange: {
|
|
92
|
+
payload: {
|
|
93
|
+
latitude: 'number — kullanıcının bıraktığı merkezin enlemi (6 basamak)',
|
|
94
|
+
longitude: 'number — merkezin boylamı (6 basamak, −180…180)',
|
|
95
|
+
zoom: 'number — web harita ölçeği (2 basamak, 1–19)'
|
|
96
|
+
},
|
|
97
|
+
reads: "yalnız kullanıcının sürüklemesi ya da yakınlaştırması bitince yayılır; akışın yazdığı görünüm ve kutu boyu değişimi yaymaz. Makine üç prop'u da yazar (self.latitude, self.longitude, self.zoom)"
|
|
98
|
+
}
|
|
99
|
+
},
|
|
62
100
|
markdown: {
|
|
63
101
|
mount: NO_DATA,
|
|
64
102
|
linkClick: {
|
|
@@ -75,10 +113,92 @@ export const mobileComponentEventContracts = {
|
|
|
75
113
|
'safe-area': {
|
|
76
114
|
mount: NO_DATA
|
|
77
115
|
},
|
|
116
|
+
// Web `container` yalnız `mount` yayar; `click` mobile-only (plans/mobil-plantcare-acigi f-04, K1).
|
|
117
|
+
container: {
|
|
118
|
+
click: NO_DATA
|
|
119
|
+
},
|
|
120
|
+
// Arama varyantının "Vazgeç"i (plans/mobil-plantcare-acigi f-05): değer temizlenir, klavye kapanır.
|
|
121
|
+
textinput: {
|
|
122
|
+
cancel: NO_DATA
|
|
123
|
+
},
|
|
78
124
|
'scroll-view': {
|
|
79
125
|
mount: NO_DATA,
|
|
80
126
|
scrollEnd: NO_DATA,
|
|
81
|
-
refresh: NO_DATA
|
|
127
|
+
refresh: NO_DATA,
|
|
128
|
+
scroll: {
|
|
129
|
+
payload: {
|
|
130
|
+
y: 'number — dikey kaydırma konumu (yatay kaydırıcıda x)',
|
|
131
|
+
contentHeight: 'number — içeriğin boyu (yatayda contentWidth)',
|
|
132
|
+
viewportHeight: 'number — görünen alanın boyu (yatayda viewportWidth)'
|
|
133
|
+
},
|
|
134
|
+
reads: '100 ms seyreltmeli; yalnız bu olaya akış bağlıyken yayılır. Düğüme scrollY (scrollX) de yazılır'
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
'grouped-list': {
|
|
138
|
+
mount: NO_DATA,
|
|
139
|
+
change: {
|
|
140
|
+
payload: {
|
|
141
|
+
selectedIndex: 'number — dokunulan öğenin rows içindeki DÜZ indeksi (bölüm içi değil); aynı öğeye yeniden dokununca yayılmaz. Makine selectedIndex\'i yazar, akışta tıklanan satır <liste>.selectedItem'
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
itemPress: {
|
|
145
|
+
payload: {
|
|
146
|
+
index: 'number (öğenin rows içindeki düz indeksi, 0 tabanlı)',
|
|
147
|
+
row: 'object (öğenin satır verisi)'
|
|
148
|
+
},
|
|
149
|
+
reads: 'yalnız itemPressable açıkken; önce change { selectedIndex } yayılır. Öğe şablonu basılabilir bir bileşense (list-item, button) dokunuş onun click\'ine gider, itemPress yayılmaz'
|
|
150
|
+
},
|
|
151
|
+
swipe: {
|
|
152
|
+
payload: {
|
|
153
|
+
key: 'string (kaydırma eyleminin key alanı)',
|
|
154
|
+
index: 'number (öğenin rows içindeki düz indeksi, 0 tabanlı)',
|
|
155
|
+
row: 'object (öğenin satır verisi)'
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
sectionPress: {
|
|
159
|
+
payload: {
|
|
160
|
+
key: 'string — bölüm anahtarı (tarih değerinde yerel gün YYYY-AA-GG)',
|
|
161
|
+
title: 'string — bölüm başlığı (anahtar; boş alanlı satırların bölümünde otherSectionTitle)',
|
|
162
|
+
count: 'number — bölümdeki öğe sayısı',
|
|
163
|
+
items: 'object[] — bölümdeki satırların verisi'
|
|
164
|
+
},
|
|
165
|
+
reads: 'bölüm başlığına dokununca; yük başlık şablonunun Section ifadesiyle aynı nesnedir'
|
|
166
|
+
},
|
|
167
|
+
selectionChange: {
|
|
168
|
+
payload: {
|
|
169
|
+
selectedIndices: 'number[] — seçili satırların rows içindeki DÜZ indeksleri, sıralı',
|
|
170
|
+
count: 'number — seçili satır sayısı'
|
|
171
|
+
},
|
|
172
|
+
reads: 'yalnız selectionMode: multiple (ya da longPressSelects ile açılınca); akış event.data.selectedIndices / event.data.count okur, düğümün selectedIndices/selectedItems değeri de yazılır'
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
eventcalendar: {
|
|
176
|
+
mount: NO_DATA,
|
|
177
|
+
dateClick: {
|
|
178
|
+
payload: { date: "string 'yyyy-MM-dd'", rows: 'object[] — o güne düşen kaynak satırlar (mobil)' },
|
|
179
|
+
reads: "self'e yazılmaz — event.data ile oku; ay ve hafta görünümünde güne dokununca"
|
|
180
|
+
},
|
|
181
|
+
eventClick: {
|
|
182
|
+
payload: { id: 'string', title: 'string', row: 'kaynak satırın TAMAMI' },
|
|
183
|
+
reads: "self'e yazılmaz — event.data ile oku; gün çizelgesinde bloğa dokununca"
|
|
184
|
+
},
|
|
185
|
+
monthChange: {
|
|
186
|
+
payload: {
|
|
187
|
+
month: 'number (1-TABANLI)',
|
|
188
|
+
year: 'number',
|
|
189
|
+
view: "'month' | 'week' | 'day'",
|
|
190
|
+
start: "string 'yyyy-MM-dd' (görünen ilk gün)",
|
|
191
|
+
end: "string 'yyyy-MM-dd' (görünen son gün)"
|
|
192
|
+
},
|
|
193
|
+
reads: "self'e yazılmaz — event.data ile oku; oklar, yatay kaydırma ve Bugün ile her görünümde"
|
|
194
|
+
},
|
|
195
|
+
viewChange: {
|
|
196
|
+
payload: { view: "'month' | 'week' | 'day'", start: "string 'yyyy-MM-dd'", end: "string 'yyyy-MM-dd'" },
|
|
197
|
+
reads: "self'e yazılmaz — event.data ile oku; görünüm düğmesiyle"
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
'floating-container': {
|
|
201
|
+
mount: NO_DATA
|
|
82
202
|
},
|
|
83
203
|
repeater: {
|
|
84
204
|
swipe: {
|
|
@@ -93,6 +213,13 @@ export const mobileComponentEventContracts = {
|
|
|
93
213
|
index: 'number (satırın sırası, 0 tabanlı)',
|
|
94
214
|
row: 'object (satırın verisi)'
|
|
95
215
|
}
|
|
216
|
+
},
|
|
217
|
+
selectionChange: {
|
|
218
|
+
payload: {
|
|
219
|
+
selectedIndices: 'number[] — seçili satırların rows içindeki DÜZ indeksleri, sıralı',
|
|
220
|
+
count: 'number — seçili satır sayısı'
|
|
221
|
+
},
|
|
222
|
+
reads: 'yalnız selectionMode: multiple (ya da longPressSelects ile açılınca); akış event.data.selectedIndices / event.data.count okur, düğümün selectedIndices/selectedItems değeri de yazılır'
|
|
96
223
|
}
|
|
97
224
|
}
|
|
98
225
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { logStep } from '../observability/trace.js';
|
|
2
2
|
import { PlatformApiError } from '../platform/errors.js';
|
|
3
3
|
import { ensureWorkflowErrorPath } from './errorPath.js';
|
|
4
|
-
import { TYPED_STYLE_COMPONENTS } from './styleResolve.js';
|
|
4
|
+
import { DOCUMENT_STYLE_TYPES, TYPED_STYLE_COMPONENTS } from './styleResolve.js';
|
|
5
5
|
import { STEP_ALIASES } from './platform/profile.js';
|
|
6
6
|
const SORT_DEFAULT = { name: 'sortField', type: 'sortType', source: 'query-string' };
|
|
7
7
|
const LIMIT_DEFAULT = { name: 'limit', offset: 'offset', source: 'query-string' };
|
|
@@ -1886,12 +1886,22 @@ export function nestedRepeaterNodes(nodes) {
|
|
|
1886
1886
|
walk(asObjects(nodes), false);
|
|
1887
1887
|
return found;
|
|
1888
1888
|
}
|
|
1889
|
-
|
|
1890
|
-
|
|
1889
|
+
/**
|
|
1890
|
+
* Mobil profilde gruplu listenin (gün/kategori başlığı altında satırlar) hazır bileşeni vardır:
|
|
1891
|
+
* `grouped-list` (ui-native, ADR 0007). Web'de karşılığı yok — web mesajı textarea tarifinde kalır.
|
|
1892
|
+
*/
|
|
1893
|
+
export function nestedRepeaterMessage(ids, platform = 'web') {
|
|
1894
|
+
const measured = `PAGE-013 — repeater içinde repeater (istek gönderilmedi): ${ids.join(' · ')}. `
|
|
1891
1895
|
+ 'Ölçüldü (2026-08-19): iç repeater OKUMADA çalışır ama girilen değer dış satıra GERİ YAZILMAZ '
|
|
1892
1896
|
+ '(_repeaterValues null kalır) ve tek düzenlemeden sonra DOM bozulur. Sebep yapısal: şemada iç '
|
|
1893
|
-
+ 'repeater tek düğümdür, dış satır başına ayrı düğüm yoktur.
|
|
1894
|
-
|
|
1897
|
+
+ 'repeater tek düğümdür, dış satır başına ayrı düğüm yoktur. ';
|
|
1898
|
+
const textarea = "Satır içi listeyi tek bir textarea ile topla (satır başına bir öğe) ve kaydederken split('\n') ile böl — platform_guide('page') Kural 1g.";
|
|
1899
|
+
if (platform !== 'mobile')
|
|
1900
|
+
return measured + textarea;
|
|
1901
|
+
return measured
|
|
1902
|
+
+ 'Başlıklar altında gruplanmış liste (güne/kategoriye göre) için mobilde tek `grouped-list` kur: düz `rows` + '
|
|
1903
|
+
+ '`groupBy` alan yolu; çocuk 0 bölüm başlığı (`{{ Section.title }}`), çocuk 1 öğe şablonu (`Row`, `{{ item.<alan> }}`) — '
|
|
1904
|
+
+ "platform_guide('mobile') §Güne göre görev listesi. Satır içinde düzenlenen alt liste gerekiyorsa: " + textarea;
|
|
1895
1905
|
}
|
|
1896
1906
|
/**
|
|
1897
1907
|
* `style` prop'u düz CSS STRING verilmiş düğümler. Çalışır (emotion string'i CSS metni sayar) ama
|
|
@@ -1905,7 +1915,7 @@ export function stringStyleNodes(nodes) {
|
|
|
1905
1915
|
for (const node of list) {
|
|
1906
1916
|
const props = (node.props ?? {});
|
|
1907
1917
|
const style = props.style;
|
|
1908
|
-
if (style && typeof style.data === 'string' && style.data.includes(':')) {
|
|
1918
|
+
if (style && typeof style.data === 'string' && style.data.includes(':') && !DOCUMENT_STYLE_TYPES.has(String(node.type))) {
|
|
1909
1919
|
found.push(String(node.elementId ?? node.name ?? '?'));
|
|
1910
1920
|
}
|
|
1911
1921
|
walk(asObjects(node.children));
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { MOBILE_RULES, REDIRECT_PRESENTATIONS, missingComponentAdvice, styleReplacement } from './mobileRules.js';
|
|
1
|
+
import { IMAGE_SOURCE_PROPS, IMAGE_SOURCE_PROP_NAMES, MOBILE_RULES, REDIRECT_PRESENTATIONS, fileIdImageExpression, missingComponentAdvice, styleReplacement } from './mobileRules.js';
|
|
2
2
|
import { findingLine } from './profile.js';
|
|
3
|
+
import { SURFACE_KEYS } from '../styleResolveNative.js';
|
|
4
|
+
import { DOCUMENT_STYLE_TYPES } from '../styleResolve.js';
|
|
3
5
|
function asRecord(value) {
|
|
4
6
|
return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
5
7
|
}
|
|
@@ -18,10 +20,28 @@ function walk(value, visit) {
|
|
|
18
20
|
walk(node.children, visit);
|
|
19
21
|
}
|
|
20
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Koda ya da çeviriye bağlı prop: değeri çalışma anında `ref`'ten gelir, `data` yalnız tasarım yedeğidir.
|
|
25
|
+
* Belge CSS'i alan tipte (`DOCUMENT_STYLE_TYPES`) bağlı `style` metin sayılır — şema onu uzun metin ilan eder.
|
|
26
|
+
*/
|
|
27
|
+
function boundWrapper(value) {
|
|
28
|
+
const wrapped = asRecord(value);
|
|
29
|
+
return wrapped !== undefined && (wrapped.type === 'code' || wrapped.type === 'translation');
|
|
30
|
+
}
|
|
21
31
|
function propValue(value) {
|
|
22
32
|
const wrapped = asRecord(value);
|
|
23
33
|
return wrapped && 'data' in wrapped && typeof wrapped.type === 'string' ? wrapped.data : value;
|
|
24
34
|
}
|
|
35
|
+
/** Prop'un bağlı ifadesi: code sarmalının `ref`i (yoksa `data`sı) ya da çıplak şablon metni. */
|
|
36
|
+
function boundExpression(value) {
|
|
37
|
+
if (typeof value === 'string')
|
|
38
|
+
return value;
|
|
39
|
+
const wrapped = asRecord(value);
|
|
40
|
+
if (!wrapped || wrapped.type !== 'code')
|
|
41
|
+
return undefined;
|
|
42
|
+
const expression = wrapped.ref ?? wrapped.data;
|
|
43
|
+
return typeof expression === 'string' ? expression : undefined;
|
|
44
|
+
}
|
|
25
45
|
function label(node) {
|
|
26
46
|
return String(node.elementId ?? node.name ?? node.type ?? '?');
|
|
27
47
|
}
|
|
@@ -42,6 +62,10 @@ export function profileFindings(profile, input) {
|
|
|
42
62
|
const unknownTypes = new Map();
|
|
43
63
|
const shellNodes = new Map();
|
|
44
64
|
const legacyNodes = [];
|
|
65
|
+
const fileIdSources = [];
|
|
66
|
+
const nodeTypes = new Map();
|
|
67
|
+
const floorImages = [];
|
|
68
|
+
const namelessIconButtons = [];
|
|
45
69
|
const hit = (map, key, target) => map.set(key, [...(map.get(key) ?? []), target]);
|
|
46
70
|
const inspectAppearance = (target, appearance) => {
|
|
47
71
|
if (!appearance)
|
|
@@ -57,20 +81,45 @@ export function profileFindings(profile, input) {
|
|
|
57
81
|
hit(styleHits, `${key} (${value})`, target);
|
|
58
82
|
}
|
|
59
83
|
};
|
|
84
|
+
// Mobil derleyici yüzey grubunun yalnız bilinen alanlarını okur; kalan alt anahtar cihazda düşer.
|
|
85
|
+
// `data`sız sarmal (`{ type: 'code', ref }`) bütün grubu bir ifadeye bağlar: denetlenecek alt anahtar yoktur.
|
|
86
|
+
const inspectSurface = (target, value) => {
|
|
87
|
+
const raw = asRecord(value);
|
|
88
|
+
if (raw && typeof raw.type === 'string' && !('data' in raw))
|
|
89
|
+
return;
|
|
90
|
+
for (const [key, wrapped] of Object.entries(asRecord(propValue(value)) ?? {})) {
|
|
91
|
+
const value = propValue(wrapped);
|
|
92
|
+
if (!SURFACE_KEYS.has(key) && value !== undefined && value !== null && value !== '')
|
|
93
|
+
hit(styleHits, `surfaceStyle.${key}`, target);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
60
96
|
const inspectNode = (node) => {
|
|
61
97
|
const target = label(node);
|
|
62
98
|
const type = String(node.type ?? '');
|
|
63
99
|
const props = asRecord(node.props) ?? {};
|
|
64
100
|
nodes.push(node);
|
|
101
|
+
nodeTypes.set(target, type);
|
|
102
|
+
for (const prop of IMAGE_SOURCE_PROPS[type] ?? []) {
|
|
103
|
+
const expression = fileIdImageExpression(boundExpression(props[prop]) ?? '');
|
|
104
|
+
if (expression)
|
|
105
|
+
fileIdSources.push({ target: `${target}.${prop}`, expression });
|
|
106
|
+
}
|
|
65
107
|
if (Array.isArray(node.blocks) && node.blocks.length > 0 && !profile.layout.blocksMeaningful)
|
|
66
108
|
blockNodes.push(target);
|
|
67
109
|
const layout = propValue(props.layout);
|
|
68
110
|
if (typeof layout === 'string' && GRID_LAYOUTS.has(layout))
|
|
69
111
|
gridNodes.push(`${target} (layout:${layout})`);
|
|
70
112
|
inspectAppearance(target, asRecord(node.appearance));
|
|
113
|
+
inspectSurface(target, props.surfaceStyle);
|
|
114
|
+
if (type === 'image' && !imageHasDefiniteHeight(asRecord(node.appearance)))
|
|
115
|
+
floorImages.push(target);
|
|
116
|
+
if (type === 'button' && propValue(props.size) === 'icon' && !String(propValue(props.ariaLabel) ?? '').trim() && !String(propValue(props.text) ?? '').trim()) {
|
|
117
|
+
namelessIconButtons.push(target);
|
|
118
|
+
}
|
|
71
119
|
if (!profile.style.freeStyle) {
|
|
72
120
|
const style = propValue(props.style);
|
|
73
|
-
|
|
121
|
+
const documentCss = DOCUMENT_STYLE_TYPES.has(type) && (typeof style === 'string' || boundWrapper(props.style));
|
|
122
|
+
if (!documentCss && ((asRecord(style) && Object.keys(style).length > 0) || (typeof style === 'string' && style.trim() !== '')))
|
|
74
123
|
hit(styleHits, 'style', target);
|
|
75
124
|
}
|
|
76
125
|
if (!profile.style.hoverStyle) {
|
|
@@ -86,6 +135,8 @@ export function profileFindings(profile, input) {
|
|
|
86
135
|
legacyNodes.push(`${target} (${type})`);
|
|
87
136
|
};
|
|
88
137
|
walk(input.nodes, inspectNode);
|
|
138
|
+
const savedTypes = new Map();
|
|
139
|
+
walk(input.savedNodes, node => savedTypes.set(label(node), String(node.type ?? '')));
|
|
89
140
|
for (const op of input.ops ?? []) {
|
|
90
141
|
const target = typeof op.target === 'string' ? op.target : '?';
|
|
91
142
|
if (op.op === 'add_component')
|
|
@@ -97,12 +148,22 @@ export function profileFindings(profile, input) {
|
|
|
97
148
|
if (op.op === 'replace_prop' || op.op === 'set_binding') {
|
|
98
149
|
const prop = String(op.prop ?? '');
|
|
99
150
|
const value = propValue(op.value);
|
|
151
|
+
const targetType = nodeTypes.get(target) ?? savedTypes.get(target) ?? '';
|
|
152
|
+
const documentCss = DOCUMENT_STYLE_TYPES.has(targetType) && (typeof value === 'string' || op.op === 'set_binding' || boundWrapper(op.value));
|
|
100
153
|
if (prop === 'layout' && typeof value === 'string' && GRID_LAYOUTS.has(value))
|
|
101
154
|
gridNodes.push(`${target} (layout:${value})`);
|
|
102
|
-
if (prop === 'style' && !profile.style.freeStyle)
|
|
155
|
+
if (prop === 'style' && !profile.style.freeStyle && !documentCss)
|
|
103
156
|
hit(styleHits, 'style', target);
|
|
104
157
|
if (prop === 'hoverStyle' && !profile.style.hoverStyle)
|
|
105
158
|
hit(styleHits, 'hoverStyle', target);
|
|
159
|
+
if (prop === 'surfaceStyle')
|
|
160
|
+
inspectSurface(target, op.value);
|
|
161
|
+
// MOB-011: var olan düğümün tipi op'ta yok — tip biliniyorsa (aynı yazımda eklendi) ona, yoksa prop adına bakılır.
|
|
162
|
+
const knownType = nodeTypes.get(target);
|
|
163
|
+
const imageProp = knownType === undefined ? IMAGE_SOURCE_PROP_NAMES.has(prop) : (IMAGE_SOURCE_PROPS[knownType] ?? []).includes(prop);
|
|
164
|
+
const expression = imageProp ? fileIdImageExpression(boundExpression(typeof op.expression === 'string' ? op.expression : op.value) ?? '') : undefined;
|
|
165
|
+
if (expression)
|
|
166
|
+
fileIdSources.push({ target: `${target}.${prop}`, expression });
|
|
106
167
|
}
|
|
107
168
|
}
|
|
108
169
|
if (blockNodes.length > 0 || gridNodes.length > 0) {
|
|
@@ -141,6 +202,24 @@ export function profileFindings(profile, input) {
|
|
|
141
202
|
guide: "examples/mobile-app-shell-{tab,stack,drawer}.json"
|
|
142
203
|
});
|
|
143
204
|
}
|
|
205
|
+
if (floorImages.length > 0) {
|
|
206
|
+
findings.push({
|
|
207
|
+
code: MOBILE_RULES['MOB-012'].id,
|
|
208
|
+
severity: 'info',
|
|
209
|
+
target: floorImages.join(', '),
|
|
210
|
+
message: "görselin boyu belirsiz, 96 dp taban boyla çizilir — boy verin (heightType: 'fixed' + heightString ya da 'fill': görsel kabuğu doldurur) ya da oran verin (appearance.aspectRatio + heightType: 'auto' + widthType 'fill'/'fixed')",
|
|
211
|
+
guide: "platform_guide('mobile')"
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
if (namelessIconButtons.length > 0) {
|
|
215
|
+
findings.push({
|
|
216
|
+
code: MOBILE_RULES['MOB-013'].id,
|
|
217
|
+
severity: 'info',
|
|
218
|
+
target: namelessIconButtons.join(', '),
|
|
219
|
+
message: "ikon boyundaki düğmenin adı yok — size: 'icon' metni çizmez ve ekran okuyucu ikonun ligatür adını (ör. 'notifications') okur; ariaLabel verin (ör. 'Bildirimler')",
|
|
220
|
+
guide: "platform_guide('mobile')"
|
|
221
|
+
});
|
|
222
|
+
}
|
|
144
223
|
if (legacyNodes.length > 0) {
|
|
145
224
|
findings.push({
|
|
146
225
|
code: MOBILE_RULES['MOB-007'].id,
|
|
@@ -149,6 +228,17 @@ export function profileFindings(profile, input) {
|
|
|
149
228
|
message: "sekme çubuğu ve çekmece menüsü katalog bileşeni değildir, çizer bu düğümleri yok sayar — sekmeler Ayarlar › Sekmeler'den (application_update properties.mobile.tabs), çekmece 'drawer' widget'ı + app-bar.menuTarget"
|
|
150
229
|
});
|
|
151
230
|
}
|
|
231
|
+
if (fileIdSources.length > 0) {
|
|
232
|
+
const sample = fileIdSources[0]?.expression ?? '';
|
|
233
|
+
const object = sample.replace(/\??\.value(\s*\}\})$/, '$1');
|
|
234
|
+
findings.push({
|
|
235
|
+
code: MOBILE_RULES['MOB-011'].id,
|
|
236
|
+
severity: 'warn',
|
|
237
|
+
target: fileIdSources.map(source => source.target).join(', '),
|
|
238
|
+
message: `${[...new Set(fileIdSources.map(source => `'${source.expression}'`))].join(', ')} '.value' ile bitiyor — kayıttan okunan FILE alanı { type: 'ref', value: <dosya id>, absolute_path: <url> } döner ve '.value' dosya KİMLİĞİDİR (GUID), görsel çizilmez. Nesnenin kendisini verin ('${object}') ya da '.absolute_path' yazın. Gateway alan tipini sayfa şemasından bilemez: yol FILE alanı değilse (ör. URL taşıyan metin alanı) uyarıyı yok sayın. Yazım platforma gitti.`,
|
|
239
|
+
guide: "platform_guide('mobile') §Dosya ve fotoğraf"
|
|
240
|
+
});
|
|
241
|
+
}
|
|
152
242
|
const infoSteps = new Map();
|
|
153
243
|
const badPresentation = new Map();
|
|
154
244
|
walkSteps(input.steps, step => {
|
|
@@ -203,3 +293,20 @@ function walkSteps(value, visit) {
|
|
|
203
293
|
export function findingLines(findings) {
|
|
204
294
|
return findings.map(findingLine);
|
|
205
295
|
}
|
|
296
|
+
/**
|
|
297
|
+
* Görselin boyu belli mi — mobil `imageFrameStyle` sırası: kabuktan (`fixed` + ölçülebilir `heightString`,
|
|
298
|
+
* ya da `fill`) ya da orandan (`aspectRatio` + `heightType: 'auto'` + genişlik `fill`/`fixed`+ölçü).
|
|
299
|
+
* Değilse görsel 96 dp taban boyla çizilir (MOB-012). Koşu bulgusu: `fixed`le kurulan görseller boşluk
|
|
300
|
+
* bıraktı, doğru kalıp yalnız bileşen açıklamasında yazıyordu (plans/mobil-plantcare-acigi g-10 adım 3).
|
|
301
|
+
*/
|
|
302
|
+
function imageHasDefiniteHeight(appearance) {
|
|
303
|
+
if (!appearance)
|
|
304
|
+
return false;
|
|
305
|
+
// Çizicinin `cssLength`'i: sayı, `Npx` ya da `N%` — birimsiz dize ölçülemez.
|
|
306
|
+
const measurable = (value) => typeof value === 'number' || (typeof value === 'string' && /^\d+(\.\d+)?(px|%)$/.test(value.trim()));
|
|
307
|
+
const heightType = appearance.heightType;
|
|
308
|
+
if (heightType === 'fill' || (heightType === 'fixed' && measurable(appearance.heightString)))
|
|
309
|
+
return true;
|
|
310
|
+
const definiteWidth = appearance.widthType === 'fill' || (appearance.widthType === 'fixed' && measurable(appearance.widthString));
|
|
311
|
+
return Number(appearance.aspectRatio) > 0 && heightType === 'auto' && definiteWidth;
|
|
312
|
+
}
|
|
@@ -53,7 +53,7 @@ export const mobileProfile = Object.freeze({
|
|
|
53
53
|
},
|
|
54
54
|
style: { freeStyle: false, hoverStyle: false, resolver: 'native' },
|
|
55
55
|
steps: {
|
|
56
|
-
registered: new Set([...ENGINE_STEP_TYPES, 'alert', 'confirm', 'redirect', 'navigate', 'storage', 'setcookie', 'download']),
|
|
56
|
+
registered: new Set([...ENGINE_STEP_TYPES, 'alert', 'confirm', 'redirect', 'navigate', 'storage', 'setcookie', 'download', 'share']),
|
|
57
57
|
aliases: STEP_ALIASES,
|
|
58
58
|
infoOnly: new Set(['download', 'setcookie'])
|
|
59
59
|
},
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MOBILE_COMPONENT_NOTES, mobileComponentKnowledge } from '../mobileComponentCatalog.js';
|
|
2
|
+
import { STYLE_GROUP_FIELDS } from '../styleResolve.js';
|
|
2
3
|
export const MOBILE_RULES = {
|
|
3
4
|
'MOB-001': {
|
|
4
5
|
id: 'MOB-001',
|
|
@@ -10,7 +11,7 @@ export const MOBILE_RULES = {
|
|
|
10
11
|
id: 'MOB-002',
|
|
11
12
|
severity: 'warn',
|
|
12
13
|
sourceRef: 'zeyrek-frontend/packages/ng-apps-ui-native/src/style/compose-native-style.ts (yalnız tipli gruplar derlenir)',
|
|
13
|
-
summary:
|
|
14
|
+
summary: "Mobil sayfada CSS-only alan (className, tooltip, zIndex, serbest style, hoverStyle, em/rem/calc ölçü, surfaceStyle'ın tanımadığı alt anahtar)"
|
|
14
15
|
},
|
|
15
16
|
'MOB-003': {
|
|
16
17
|
id: 'MOB-003',
|
|
@@ -33,8 +34,8 @@ export const MOBILE_RULES = {
|
|
|
33
34
|
'MOB-006': {
|
|
34
35
|
id: 'MOB-006',
|
|
35
36
|
severity: 'warn',
|
|
36
|
-
sourceRef: 'zeyrek-backend/docs/harici-componentler (protokol v1 tarayıcı .mjs; mobil v2 Faz 3)',
|
|
37
|
-
summary:
|
|
37
|
+
sourceRef: 'zeyrek-backend/docs/harici-componentler (protokol v1 tarayıcı .mjs; mobil v2 Faz 3); plans/mobil-plantcare-acigi f-20 K7',
|
|
38
|
+
summary: "Harici bileşen mobil uygulamada desteklenmiyor — çağrı platforma gitmez; geçici yol 'html' bileşeni"
|
|
38
39
|
},
|
|
39
40
|
'MOB-007': {
|
|
40
41
|
id: 'MOB-007',
|
|
@@ -47,8 +48,57 @@ export const MOBILE_RULES = {
|
|
|
47
48
|
severity: 'warn',
|
|
48
49
|
sourceRef: 'zeyrek-frontend/apps/mobile-runtime/src/navigation/navigation.ts (hrefFor); zeyrek-backend FlowDtoValidator',
|
|
49
50
|
summary: "redirect.presentation kümede değil — push | modal | replace dışı değer backend'de 422, çizer push'a düşer"
|
|
51
|
+
},
|
|
52
|
+
'MOB-011': {
|
|
53
|
+
id: 'MOB-011',
|
|
54
|
+
severity: 'warn',
|
|
55
|
+
sourceRef: 'zeyrek-backend FilePathMapper.MapFileProperties (okunan FILE alanı { type: ref, value: <dosya id>, absolute_path }); zeyrek-frontend ng-apps-ui-native image-logic.ts resolveImageSource (plans/mobil-kosu-acik-yuksekler f-01)',
|
|
56
|
+
summary: "Görsel kaynağı ifadesi '.value' ile bitiyor — kayıttan okunan FILE alanında bu dosya kimliğidir (GUID), görsel çizilmez"
|
|
57
|
+
},
|
|
58
|
+
'MOB-012': {
|
|
59
|
+
id: 'MOB-012',
|
|
60
|
+
severity: 'info',
|
|
61
|
+
sourceRef: 'zeyrek-frontend/packages/ng-apps-ui-native/src/components/image/image-logic.ts (imageFrameStyle)',
|
|
62
|
+
summary: "Mobil görselin boyu ne kabuktan (fixed + ölçü, fill) ne orandan (aspectRatio + heightType auto + belli genişlik) geliyor — 96 dp taban boyla çizilir"
|
|
63
|
+
},
|
|
64
|
+
'MOB-013': {
|
|
65
|
+
id: 'MOB-013',
|
|
66
|
+
severity: 'info',
|
|
67
|
+
sourceRef: 'zeyrek-frontend/packages/ng-apps-ui-native/src/components/button/index.tsx (accessibleLabel: ariaLabel → text → ikon adı)',
|
|
68
|
+
summary: "İkon boyunda (size: icon) düğmenin adı yok — ne ariaLabel ne text; ekran okuyucu ikonun ligatür adını (notifications) okur"
|
|
50
69
|
}
|
|
51
70
|
};
|
|
71
|
+
/**
|
|
72
|
+
* MOB-011: dosya nesnesini görsel olarak çizen prop'lar (mobil `resolveImageSource` tüketicileri).
|
|
73
|
+
* Hedefin tipi bilinmiyorsa (var olan düğüme `set_binding`) prop adı yeter — `IMAGE_SOURCE_PROP_NAMES`.
|
|
74
|
+
*/
|
|
75
|
+
export const IMAGE_SOURCE_PROPS = {
|
|
76
|
+
image: ['src'],
|
|
77
|
+
avatar: ['src'],
|
|
78
|
+
'list-item': ['leadingImage'],
|
|
79
|
+
card: ['image']
|
|
80
|
+
};
|
|
81
|
+
export const IMAGE_SOURCE_PROP_NAMES = new Set(Object.values(IMAGE_SOURCE_PROPS).flat());
|
|
82
|
+
const TEMPLATE = /\{\{([\s\S]*?)\}\}/g;
|
|
83
|
+
const MEMBER_PATH = /^[A-Za-z_$][\w$]*(?:\??\.[A-Za-z_$][\w$]*|\[\s*(?:\d+|'[^']*'|"[^"]*")\s*\])+$/;
|
|
84
|
+
/**
|
|
85
|
+
* MOB-011'in desen denetimi. Gateway sayfa şemasından bir alanın FILE olduğunu BİLEMEZ (alan tipi
|
|
86
|
+
* entity'de, ifade yalnız bir yol) — bu yüzden kural metinseldir ve uyarı düzeyindedir: en az üç
|
|
87
|
+
* parçalı bir üye yolu (`item.foto.value`, `pages.x.params.kayit.foto.value`) `.value` ile bitiyorsa
|
|
88
|
+
* ilk eşleşen `{{ }}` şablonunu döner. İki parçalı yol (`urlInput.value` — bileşenin kendi değeri) ve
|
|
89
|
+
* `.value.value` (dosya alanının base64'ü: `fileInput1.value.value`) eşleşmez.
|
|
90
|
+
*/
|
|
91
|
+
export function fileIdImageExpression(template) {
|
|
92
|
+
for (const match of template.matchAll(TEMPLATE)) {
|
|
93
|
+
const body = (match[1] ?? '').trim();
|
|
94
|
+
if (!MEMBER_PATH.test(body))
|
|
95
|
+
continue;
|
|
96
|
+
const segments = body.match(/[A-Za-z_$][\w$]*|\[[^\]]*\]/g) ?? [];
|
|
97
|
+
if (segments.length >= 3 && segments[segments.length - 1] === 'value' && segments[segments.length - 2] !== 'value')
|
|
98
|
+
return match[0];
|
|
99
|
+
}
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
52
102
|
/** `redirect` adımının tanıdığı sunum biçimleri (b-01 `StepArgument.Presentation`). */
|
|
53
103
|
export const REDIRECT_PRESENTATIONS = new Set(['push', 'modal', 'replace']);
|
|
54
104
|
/**
|
|
@@ -58,15 +108,19 @@ export const REDIRECT_PRESENTATIONS = new Set(['push', 'modal', 'replace']);
|
|
|
58
108
|
* Mobil pakete giren bileşen Dalga 2/3 listesinden DÜŞER: yayında olduğunu mobil sözlükteki kaydı
|
|
59
109
|
* söyler (`mobileComponentKnowledge`, parite testiyle `components.json`'a kilitli). Katalog A
|
|
60
110
|
* (2026-09-24) sekiz web ikizini buradan indirdi: `radio`, `checkboxgroup`, `slider`, `tabs`, `badge`,
|
|
61
|
-
* `avatar`, `progress`, `collapsible-container`; Katalog C (09) Dalga 3'ten `video
|
|
62
|
-
*
|
|
111
|
+
* `avatar`, `progress`, `collapsible-container`; Katalog C (09) Dalga 3'ten `video`, `signature`, `chart`,
|
|
112
|
+
* `fileInput` ve `filebutton`ı, Dalga 2'den `datepicker` ve `timepicker`ı (`fileUpload`un sürükle-bırak alanı
|
|
113
|
+
* dokunmatikte yok: web'e özgü, karşılığı iki dosya alanı); dil dalgası (plans/mobil-coklu-dil F1, 2026-09-25)
|
|
114
|
+
* Dalga 2'den `language-selector`ı; faz 10b (2026-09-27) Dalga 3'ten `datatable`ı; plans/mobil-plantcare-acigi f-11
|
|
115
|
+
* (2026-09-27) listede olmayan `daterangepicker`i doğrudan pakete aldı; f-20 (2026-09-28) Dalga 3'ten `html`ı (mobilde
|
|
116
|
+
* geçici çözüm kapısı, K7); f-12 (2026-09-28) listede olmayan `eventcalendar`ı (web ikizi, ay/hafta/gün). Dalga 1
|
|
63
117
|
* listesi artık yalnız `mobileNotes`'un bilgi satırıdır — MOB-003'te yayındaki bileşeni sözlük tanır
|
|
64
118
|
* (`box-section` Dalga 1'de ama pakette yok).
|
|
65
119
|
*/
|
|
66
120
|
export const MOBILE_COMPONENT_WAVES = Object.fromEntries([
|
|
67
121
|
...['container', 'box-section', 'form', 'slot', 'text', 'icon', 'image', 'divider', 'spacer', 'button', 'textinput', 'textarea', 'numberinput', 'password', 'checkbox', 'switch', 'dropdown', 'repeater', 'card', 'emptystate', 'form-validation-errors', 'modal', 'drawer', 'app-bar'].map(slug => [slug, 1]),
|
|
68
|
-
...['box', '
|
|
69
|
-
...['
|
|
122
|
+
...['box', 'phoneinput', 'statcard', 'detailview', 'page-header', 'autocomplete'].map(slug => [slug, 2]),
|
|
123
|
+
...['kanban', 'timeline', 'texteditor', 'stepper', 'tree-view'].map(slug => [slug, 3])
|
|
70
124
|
]);
|
|
71
125
|
export const MOBILE_WEB_ONLY = {
|
|
72
126
|
iframe: "'webview' (src ya da html; bağlantılar varsayılan olarak sistem tarayıcısında açılır)",
|
|
@@ -79,17 +133,22 @@ export const MOBILE_WEB_ONLY = {
|
|
|
79
133
|
popover: "'modal' widget'ı",
|
|
80
134
|
'theme-toggle': 'cihaz teması otomatik (koyu/açık)',
|
|
81
135
|
pagination: "'repeater' + daha fazla yükle düğmesi",
|
|
82
|
-
breadcrumb: "yığın gezinmesi (app-bar geri düğmesi)"
|
|
136
|
+
breadcrumb: "yığın gezinmesi (app-bar geri düğmesi)",
|
|
137
|
+
fileUpload: "'fileInput' (alan) ya da 'filebutton' (düğme) — dokunmatikte sürükle-bırak yok; ikisi de aynı base64 değeri yazar"
|
|
83
138
|
};
|
|
84
139
|
const STYLE_REPLACEMENTS = {
|
|
85
140
|
className: "yerine surfaceStyle (zemin, kenar, köşe, gölge) ve typographyStyle tipli gruplarını kullanın",
|
|
86
141
|
tooltip: "dokunmatikte üzerine gelme yok — düğümü 'tooltip' bileşenine sarın (dokunuşla açılır) ya da açıklamayı metin olarak koyun",
|
|
87
142
|
tooltipPosition: "dokunmatikte üzerine gelme yok — 'tooltip' bileşeninde balonun yanı `side` prop'udur",
|
|
88
|
-
zIndex: "katman için 'modal' ya da 'drawer' widget'ı
|
|
89
|
-
style: "serbest CSS nesnesi React Native'de çizilmez — surfaceStyle ve typographyStyle tipli gruplarına taşıyın",
|
|
143
|
+
zIndex: "görselin üstüne rozet/hap/etiket/açıklama bindirmek için 'stack' (ilk çocuk taban, ötekiler üst katman; katman olarak fill×fill bir container koyup kendi hizasıyla köşeye yerleştirin), ekrana sabit içerik (FAB, alt eylem çubuğu, üst şerit) için 'floating-container' (anchor 9 nokta, offsetX/offsetY, fullWidth), kaydırırken üstte kalan başlık için scroll-view çocuğunda states.isSticky, sayfayı örten katman için 'modal' ya da 'drawer' widget'ı",
|
|
144
|
+
style: "serbest CSS nesnesi React Native'de çizilmez — surfaceStyle ve typographyStyle tipli gruplarına taşıyın; position/zIndex ile ekrana sabitlemek yerine 'floating-container' kullanın",
|
|
90
145
|
hoverStyle: 'dokunmatikte üzerine gelme durumu yok'
|
|
91
146
|
};
|
|
92
147
|
export function styleReplacement(key) {
|
|
148
|
+
if (key.startsWith('surfaceStyle.')) {
|
|
149
|
+
const fields = (STYLE_GROUP_FIELDS.surfaceStyle ?? []).map(field => `\`${field}\``).join(', ');
|
|
150
|
+
return `yüzey grubunun alanları ${fields} (döndürme derece, ölçek yüzde — 100 aynı boy, kaydırma px)`;
|
|
151
|
+
}
|
|
93
152
|
return STYLE_REPLACEMENTS[key] ?? 'mobil çizer bu alanı okumaz';
|
|
94
153
|
}
|
|
95
154
|
export function missingComponentAdvice(slug) {
|
|
@@ -115,7 +174,7 @@ export function mobileComponentNotes(slug) {
|
|
|
115
174
|
const wave = slug === undefined ? undefined : MOBILE_COMPONENT_WAVES[slug];
|
|
116
175
|
return [
|
|
117
176
|
"Yerleşim yalnız flex: `layout` prop'u mobil şemalarda yoktur (grid/cssgrid yok), blocks/size yok sayılır, sıra dizi indeksidir; ölçü appearance ile verilir (widthType/heightType — MOB-001).",
|
|
118
|
-
"Görünüm tipli gruplarla: surfaceStyle ve typographyStyle React Native stiline derlenir; ham CSS `style` prop'u hiçbir mobil şemada yoktur, className, `tooltip` alanı (ipucu için `tooltip` bileşeni), zIndex ve hoverStyle çizilmez (MOB-002). Karşılığı olmayan değer sessizce düşmez, uyarıya yazılır; düğümün gerçek stili: page_runtime_get(resolveStyles) → nativeStyle.",
|
|
177
|
+
"Görünüm tipli gruplarla: surfaceStyle ve typographyStyle React Native stiline derlenir; ham CSS `style` prop'u `html` dışında hiçbir mobil şemada yoktur (`html`'in metin `style`'ı kendi belgesine kapsamlı girer), className, `tooltip` alanı (ipucu için `tooltip` bileşeni), zIndex ve hoverStyle çizilmez (MOB-002). Karşılığı olmayan değer sessizce düşmez, uyarıya yazılır; düğümün gerçek stili: page_runtime_get(resolveStyles) → nativeStyle.",
|
|
119
178
|
"Basılabilir bileşen role=\"button\" taşır; page_screenshot click onu kabuğun içinde bulur.",
|
|
120
179
|
...MOBILE_COMPONENT_NOTES,
|
|
121
180
|
...(wave === undefined ? [] : [`Mobil bileşen dalgası: ${wave} (plans/mobil-uygulama-expo §11).`])
|
|
@@ -20,7 +20,7 @@ export const webProfile = Object.freeze({
|
|
|
20
20
|
},
|
|
21
21
|
style: { freeStyle: true, hoverStyle: true, resolver: 'css' },
|
|
22
22
|
steps: {
|
|
23
|
-
registered: new Set([...ENGINE_STEP_TYPES, 'alert', 'confirm', 'redirect', 'download', 'storage', 'setcookie']),
|
|
23
|
+
registered: new Set([...ENGINE_STEP_TYPES, 'alert', 'confirm', 'redirect', 'download', 'storage', 'setcookie', 'share']),
|
|
24
24
|
aliases: STEP_ALIASES,
|
|
25
25
|
infoOnly: new Set()
|
|
26
26
|
},
|