@appaflytech/zeyrek-ai-mcp 0.0.23 → 0.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.js +0 -2
- package/dist/knowledge/componentCatalog.js +24 -12
- package/dist/knowledge/componentEvents.js +6 -13
- package/dist/knowledge/normalize.js +11 -52
- package/dist/knowledge/rules.js +12 -56
- package/dist/knowledge/styleResolve.js +1 -1
- package/dist/knowledge/themeResolve.js +3 -3
- package/dist/platform/domSummary.js +1 -1
- package/dist/platform/{capture/capture.js → screenshot.js} +509 -23
- package/dist/resources/examples/workflow-http-step.json +2 -2
- package/dist/resources/guides/component.md +1 -6
- package/dist/resources/guides/expressions.md +1 -1
- package/dist/resources/guides/functions.md +1 -1
- package/dist/resources/guides/layout.md +7 -11
- package/dist/resources/guides/microservice.md +2 -1
- package/dist/resources/guides/page.md +16 -69
- package/dist/resources/guides/translation.md +1 -1
- package/dist/resources/guides/workflow.md +6 -4
- package/dist/resources/schema-fingerprint.json +2 -2
- package/dist/resources/schemas/CreateWorkflowCommand.json +3 -0
- package/dist/resources/schemas/PreviewWorkflowCommand.json +3 -0
- package/dist/resources/schemas/StepDto.json +12 -9
- package/dist/resources/schemas/UpdateWorkflowCommand.json +3 -0
- package/dist/server.js +0 -2
- package/dist/tools/applications.js +3 -60
- package/dist/tools/components.js +10 -50
- package/dist/tools/designSchemaPatch.js +4 -13
- package/dist/tools/externalComponents.js +4 -25
- package/dist/tools/flows.js +2 -9
- package/dist/tools/layouts.js +9 -41
- package/dist/tools/pages.js +35 -115
- package/dist/tools/platformInfo.js +1 -2
- package/dist/tools/screenWrite.js +2 -2
- package/dist/tools/screens.js +3 -9
- package/dist/tools/workflows.js +1 -1
- package/package.json +1 -1
- package/dist/knowledge/mobileComponentCatalog.js +0 -628
- package/dist/knowledge/mobileComponentEvents.js +0 -110
- package/dist/knowledge/platform/lint.js +0 -205
- package/dist/knowledge/platform/mobile.js +0 -78
- package/dist/knowledge/platform/mobileRules.js +0 -126
- package/dist/knowledge/platform/mobileTabs.js +0 -108
- package/dist/knowledge/platform/profile.js +0 -20
- package/dist/knowledge/platform/resolve.js +0 -122
- package/dist/knowledge/platform/web.js +0 -38
- package/dist/knowledge/styleResolveNative.js +0 -410
- package/dist/platform/capture/actions.js +0 -333
- package/dist/platform/capture/browser.js +0 -1
- package/dist/platform/capture/index.js +0 -10
- package/dist/platform/capture/readiness.js +0 -103
- package/dist/platform/capture/summary.js +0 -77
- package/dist/platform/capture/target.js +0 -5
- package/dist/platform/capture/targets/studio-mobile.js +0 -15
- package/dist/platform/capture/targets/studio-web.js +0 -5
- package/dist/resources/examples/mobile-app-shell-drawer.json +0 -209
- package/dist/resources/examples/mobile-app-shell-stack.json +0 -123
- package/dist/resources/examples/mobile-app-shell-tab.json +0 -174
- package/dist/resources/examples/mobile-list-detail-form.json +0 -656
- package/dist/resources/guides/mobile.md +0 -283
- package/dist/tools/mobile.js +0 -203
- package/dist/tools/profileWrite.js +0 -45
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MOBİLE ÖZGÜ olay yükleri. Mobil komponentlerin ÇOĞU web şemasını genişletir ve
|
|
3
|
-
* olayları web sözleşmesindedir (`componentEvents.ts`) — bu dosya yalnız web'de
|
|
4
|
-
* karşılığı OLMAYAN komponentler içindir (`app-bar` gibi kabuk komponentleri).
|
|
5
|
-
*
|
|
6
|
-
* Okuyucu önce buraya, sonra web sözlüğüne bakar; parity testi
|
|
7
|
-
* (`test/mobile-component-parity.test.ts`) her mobil olayın iki sözlükten
|
|
8
|
-
* birinde karşılanmasını zorunlu tutar.
|
|
9
|
-
*/
|
|
10
|
-
const NO_DATA = { payload: {} };
|
|
11
|
-
export const mobileComponentEventContracts = {
|
|
12
|
-
'choice-chips': {
|
|
13
|
-
change: {
|
|
14
|
-
payload: {
|
|
15
|
-
value: 'string — seçili çipin değeri. `multiple` açıkken seçili değerler virgülle ayrılmış tek dize olarak gelir.'
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
tooltip: {
|
|
20
|
-
open: NO_DATA
|
|
21
|
-
},
|
|
22
|
-
calendar: {
|
|
23
|
-
mount: NO_DATA,
|
|
24
|
-
change: {
|
|
25
|
-
payload: {
|
|
26
|
-
value: "string 'yyyy-MM-ddT00:00:00' | null — seçilen gün (aralıkta başlangıç); web datepicker'ın biçimi: yerel duvar saati, gece yarısı, dilim eki yok",
|
|
27
|
-
endValue: "string 'yyyy-MM-ddT00:00:00' | null — YALNIZ mode: 'range'; ilk basışta null, ikinci basışta bitiş günü"
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
stack: {
|
|
32
|
-
mount: NO_DATA
|
|
33
|
-
},
|
|
34
|
-
'list-item': {
|
|
35
|
-
click: NO_DATA
|
|
36
|
-
},
|
|
37
|
-
fab: {
|
|
38
|
-
click: NO_DATA
|
|
39
|
-
},
|
|
40
|
-
timer: {
|
|
41
|
-
mount: NO_DATA,
|
|
42
|
-
tick: { payload: { value: 'number — gösterilen saniye (geri sayımda kalan, kronometrede geçen); makine value prop\'una yazar' } },
|
|
43
|
-
finish: { payload: { isRunning: 'false — geri sayım 0\'a indi; makine isRunning\'i kapatır' } }
|
|
44
|
-
},
|
|
45
|
-
audio: {
|
|
46
|
-
mount: NO_DATA,
|
|
47
|
-
play: { payload: { isPlaying: "true — çalma başladı (denetim ya da akış); makine isPlaying'i yazar" } },
|
|
48
|
-
pause: { payload: { isPlaying: "false — çalma durdu (denetim, akış, bitiş ya da tarayıcının otomatik oynatma reddi); makine isPlaying'i yazar" } },
|
|
49
|
-
ended: { payload: { isPlaying: "false — ses sona vardı (loop kapalıyken, pause'tan sonra); makine isPlaying'i kapatır" } }
|
|
50
|
-
},
|
|
51
|
-
lottie: {
|
|
52
|
-
mount: NO_DATA,
|
|
53
|
-
finish: { payload: { isPlaying: "false — döngüsüz animasyon bitti; makine isPlaying'i kapatır, yeniden true yazmak baştan oynatır" } }
|
|
54
|
-
},
|
|
55
|
-
webview: {
|
|
56
|
-
mount: NO_DATA,
|
|
57
|
-
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
|
-
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: { payload: { data: "string — sayfanın window.ReactNativeWebView.postMessage(metin) ile yolladığı metin, olduğu gibi; makine data prop'una yazar (self.data)" } },
|
|
60
|
-
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
|
-
},
|
|
62
|
-
map: {
|
|
63
|
-
mount: NO_DATA,
|
|
64
|
-
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" } },
|
|
65
|
-
regionChange: {
|
|
66
|
-
payload: {
|
|
67
|
-
latitude: 'number — kullanıcının bıraktığı merkezin enlemi (6 basamak)',
|
|
68
|
-
longitude: 'number — merkezin boylamı (6 basamak, −180…180)',
|
|
69
|
-
zoom: 'number — web harita ölçeği (2 basamak, 1–19)'
|
|
70
|
-
},
|
|
71
|
-
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)"
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
markdown: {
|
|
75
|
-
mount: NO_DATA,
|
|
76
|
-
linkClick: {
|
|
77
|
-
payload: {
|
|
78
|
-
href: "string — dokunulan bağlantının ham adresi; şema süzülmeden gelir (dışarıda yalnız http(s), mailto, tel açılır)"
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
},
|
|
82
|
-
'app-bar': {
|
|
83
|
-
clickBack: NO_DATA,
|
|
84
|
-
clickMenu: NO_DATA,
|
|
85
|
-
clickAction: { payload: { key: 'string (eylemin key alanı; yoksa dizideki sırası)' } }
|
|
86
|
-
},
|
|
87
|
-
'safe-area': {
|
|
88
|
-
mount: NO_DATA
|
|
89
|
-
},
|
|
90
|
-
'scroll-view': {
|
|
91
|
-
mount: NO_DATA,
|
|
92
|
-
scrollEnd: NO_DATA,
|
|
93
|
-
refresh: NO_DATA
|
|
94
|
-
},
|
|
95
|
-
repeater: {
|
|
96
|
-
swipe: {
|
|
97
|
-
payload: {
|
|
98
|
-
key: 'string (kaydırma eyleminin key alanı)',
|
|
99
|
-
index: 'number (satırın sırası, 0 tabanlı)',
|
|
100
|
-
row: 'object (satırın verisi)'
|
|
101
|
-
}
|
|
102
|
-
},
|
|
103
|
-
itemPress: {
|
|
104
|
-
payload: {
|
|
105
|
-
index: 'number (satırın sırası, 0 tabanlı)',
|
|
106
|
-
row: 'object (satırın verisi)'
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
};
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
import { MOBILE_RULES, REDIRECT_PRESENTATIONS, missingComponentAdvice, styleReplacement } from './mobileRules.js';
|
|
2
|
-
import { findingLine } from './profile.js';
|
|
3
|
-
function asRecord(value) {
|
|
4
|
-
return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
5
|
-
}
|
|
6
|
-
function walk(value, visit) {
|
|
7
|
-
if (!Array.isArray(value)) {
|
|
8
|
-
const single = asRecord(value);
|
|
9
|
-
if (single)
|
|
10
|
-
walk([single], visit);
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
|
-
for (const entry of value) {
|
|
14
|
-
const node = asRecord(entry);
|
|
15
|
-
if (!node)
|
|
16
|
-
continue;
|
|
17
|
-
visit(node);
|
|
18
|
-
walk(node.children, visit);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
function propValue(value) {
|
|
22
|
-
const wrapped = asRecord(value);
|
|
23
|
-
return wrapped && 'data' in wrapped && typeof wrapped.type === 'string' ? wrapped.data : value;
|
|
24
|
-
}
|
|
25
|
-
function label(node) {
|
|
26
|
-
return String(node.elementId ?? node.name ?? node.type ?? '?');
|
|
27
|
-
}
|
|
28
|
-
const GRID_LAYOUTS = new Set(['grid', 'cssgrid']);
|
|
29
|
-
const RELATIVE_UNIT = /\b\d*\.?\d+(em|rem)\b|calc\(/i;
|
|
30
|
-
/**
|
|
31
|
-
* Profilin taşınabilirlik bulguları. Web profilinde HER ZAMAN boş dizi döner — web davranışı
|
|
32
|
-
* değişmez (G1 kilidi); bulgular yalnız mobil profilde üretilir.
|
|
33
|
-
*/
|
|
34
|
-
export function profileFindings(profile, input) {
|
|
35
|
-
if (profile.platform === 'web')
|
|
36
|
-
return [];
|
|
37
|
-
const findings = [];
|
|
38
|
-
const nodes = [];
|
|
39
|
-
const blockNodes = [];
|
|
40
|
-
const gridNodes = [];
|
|
41
|
-
const styleHits = new Map();
|
|
42
|
-
const unknownTypes = new Map();
|
|
43
|
-
const shellNodes = new Map();
|
|
44
|
-
const legacyNodes = [];
|
|
45
|
-
const hit = (map, key, target) => map.set(key, [...(map.get(key) ?? []), target]);
|
|
46
|
-
const inspectAppearance = (target, appearance) => {
|
|
47
|
-
if (!appearance)
|
|
48
|
-
return;
|
|
49
|
-
for (const key of profile.appearance.ignoredKeys) {
|
|
50
|
-
const value = appearance[key];
|
|
51
|
-
if (value !== undefined && value !== null && value !== '' && value !== 0)
|
|
52
|
-
hit(styleHits, key, target);
|
|
53
|
-
}
|
|
54
|
-
for (const key of ['widthString', 'heightString']) {
|
|
55
|
-
const value = appearance[key];
|
|
56
|
-
if (typeof value === 'string' && RELATIVE_UNIT.test(value))
|
|
57
|
-
hit(styleHits, `${key} (${value})`, target);
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
const inspectNode = (node) => {
|
|
61
|
-
const target = label(node);
|
|
62
|
-
const type = String(node.type ?? '');
|
|
63
|
-
const props = asRecord(node.props) ?? {};
|
|
64
|
-
nodes.push(node);
|
|
65
|
-
if (Array.isArray(node.blocks) && node.blocks.length > 0 && !profile.layout.blocksMeaningful)
|
|
66
|
-
blockNodes.push(target);
|
|
67
|
-
const layout = propValue(props.layout);
|
|
68
|
-
if (typeof layout === 'string' && GRID_LAYOUTS.has(layout))
|
|
69
|
-
gridNodes.push(`${target} (layout:${layout})`);
|
|
70
|
-
inspectAppearance(target, asRecord(node.appearance));
|
|
71
|
-
if (!profile.style.freeStyle) {
|
|
72
|
-
const style = propValue(props.style);
|
|
73
|
-
if ((asRecord(style) && Object.keys(style).length > 0) || (typeof style === 'string' && style.trim() !== ''))
|
|
74
|
-
hit(styleHits, 'style', target);
|
|
75
|
-
}
|
|
76
|
-
if (!profile.style.hoverStyle) {
|
|
77
|
-
const hover = propValue(props.hoverStyle);
|
|
78
|
-
if (asRecord(hover) && Object.keys(hover).length > 0)
|
|
79
|
-
hit(styleHits, 'hoverStyle', target);
|
|
80
|
-
}
|
|
81
|
-
if (type && input.catalog && input.catalog.size > 0 && !input.catalog.has(type) && !profile.shell.legacyShellNodes.has(type))
|
|
82
|
-
hit(unknownTypes, type, target);
|
|
83
|
-
if (type && profile.shell.suggestions[type])
|
|
84
|
-
hit(shellNodes, type, target);
|
|
85
|
-
if (type && profile.shell.legacyShellNodes.has(type))
|
|
86
|
-
legacyNodes.push(`${target} (${type})`);
|
|
87
|
-
};
|
|
88
|
-
walk(input.nodes, inspectNode);
|
|
89
|
-
for (const op of input.ops ?? []) {
|
|
90
|
-
const target = typeof op.target === 'string' ? op.target : '?';
|
|
91
|
-
if (op.op === 'add_component')
|
|
92
|
-
walk(op.component, inspectNode);
|
|
93
|
-
if (op.op === 'set_block' && !profile.layout.blocksMeaningful)
|
|
94
|
-
blockNodes.push(`${target} (set_block)`);
|
|
95
|
-
if (op.op === 'set_appearance')
|
|
96
|
-
inspectAppearance(target, asRecord(op.appearance));
|
|
97
|
-
if (op.op === 'replace_prop' || op.op === 'set_binding') {
|
|
98
|
-
const prop = String(op.prop ?? '');
|
|
99
|
-
const value = propValue(op.value);
|
|
100
|
-
if (prop === 'layout' && typeof value === 'string' && GRID_LAYOUTS.has(value))
|
|
101
|
-
gridNodes.push(`${target} (layout:${value})`);
|
|
102
|
-
if (prop === 'style' && !profile.style.freeStyle)
|
|
103
|
-
hit(styleHits, 'style', target);
|
|
104
|
-
if (prop === 'hoverStyle' && !profile.style.hoverStyle)
|
|
105
|
-
hit(styleHits, 'hoverStyle', target);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
if (blockNodes.length > 0 || gridNodes.length > 0) {
|
|
109
|
-
findings.push({
|
|
110
|
-
code: MOBILE_RULES['MOB-001'].id,
|
|
111
|
-
severity: 'warn',
|
|
112
|
-
message: [
|
|
113
|
-
blockNodes.length > 0 ? `blok (12 sütun ızgara) mobil çizerde yok sayılır: ${[...new Set(blockNodes)].join(', ')}` : '',
|
|
114
|
-
gridNodes.length > 0 ? `grid/cssgrid yerleşimi yok, kap flex çizilir: ${gridNodes.join(', ')}` : ''
|
|
115
|
-
].filter(Boolean).join(' · ') + ". Sıra dizi indeksinden, yerleşim flex'ten gelir (direction/gap/horizontalAlign). Yazım platforma gitti.",
|
|
116
|
-
guide: "platform_guide('mobile')"
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
for (const [key, targets] of styleHits) {
|
|
120
|
-
findings.push({
|
|
121
|
-
code: MOBILE_RULES['MOB-002'].id,
|
|
122
|
-
severity: 'warn',
|
|
123
|
-
target: [...new Set(targets)].join(', '),
|
|
124
|
-
message: `'${key}' mobilde çizilmez — ${styleReplacement(key.split(' ')[0] ?? key)}. Yazım platforma gitti.`
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
for (const [type, targets] of unknownTypes) {
|
|
128
|
-
findings.push({
|
|
129
|
-
code: MOBILE_RULES['MOB-003'].id,
|
|
130
|
-
severity: 'warn',
|
|
131
|
-
target: [...new Set(targets)].join(', '),
|
|
132
|
-
message: missingComponentAdvice(type)
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
for (const [type, targets] of shellNodes) {
|
|
136
|
-
findings.push({
|
|
137
|
-
code: MOBILE_RULES['MOB-004'].id,
|
|
138
|
-
severity: 'warn',
|
|
139
|
-
target: [...new Set(targets)].join(', '),
|
|
140
|
-
message: `'${type}' web kabuğudur, mobil uygulamada karşılığı: ${profile.shell.suggestions[type]}`,
|
|
141
|
-
guide: "examples/mobile-app-shell-{tab,stack,drawer}.json"
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
if (legacyNodes.length > 0) {
|
|
145
|
-
findings.push({
|
|
146
|
-
code: MOBILE_RULES['MOB-007'].id,
|
|
147
|
-
severity: 'warn',
|
|
148
|
-
target: legacyNodes.join(', '),
|
|
149
|
-
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
|
-
});
|
|
151
|
-
}
|
|
152
|
-
const infoSteps = new Map();
|
|
153
|
-
const badPresentation = new Map();
|
|
154
|
-
walkSteps(input.steps, step => {
|
|
155
|
-
const type = String(step.type ?? '');
|
|
156
|
-
if (profile.steps.infoOnly.has(type))
|
|
157
|
-
hit(infoSteps, type, String(step.stepId ?? step.name ?? '?'));
|
|
158
|
-
// MOB-008: sunum biçimi yalnız `redirect`te anlamlı ve kümesi kapalı.
|
|
159
|
-
// Alan YOKSA bulgu yok — varsayılan `push` ve yazımların çoğu onu yazmaz.
|
|
160
|
-
if (type !== 'redirect')
|
|
161
|
-
return;
|
|
162
|
-
const args = asRecord(step.args);
|
|
163
|
-
const presentation = args?.presentation;
|
|
164
|
-
if (presentation === undefined || presentation === null || presentation === '')
|
|
165
|
-
return;
|
|
166
|
-
if (!REDIRECT_PRESENTATIONS.has(String(presentation))) {
|
|
167
|
-
hit(badPresentation, String(presentation), String(step.stepId ?? step.name ?? '?'));
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
for (const [value, stepIds] of badPresentation) {
|
|
171
|
-
findings.push({
|
|
172
|
-
code: MOBILE_RULES['MOB-008'].id,
|
|
173
|
-
severity: 'warn',
|
|
174
|
-
target: stepIds.join(', '),
|
|
175
|
-
message: `redirect.presentation '${value}' tanınmıyor — ${[...REDIRECT_PRESENTATIONS].join(' | ')} bekleniyor. Backend doğrulayıcısı 422 döner; geçse bile çizer 'push'a düşer.`,
|
|
176
|
-
guide: "platform_guide('mobile')"
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
for (const [type, stepIds] of infoSteps) {
|
|
180
|
-
findings.push({
|
|
181
|
-
code: MOBILE_RULES['MOB-005'].id,
|
|
182
|
-
severity: 'info',
|
|
183
|
-
target: stepIds.join(', '),
|
|
184
|
-
message: type === 'download'
|
|
185
|
-
? "mobil uygulamada 'download' dosyayı cihazın paylaşım/açma sayfasında açar (Linking.openURL) — tarayıcı indirmesi yok"
|
|
186
|
-
: "mobil uygulamada 'setcookie' çerez yazmaz, cihaz deposuna (storage, expo-sqlite/kv-store) eşlenir — okuyan ifadeler aynı çalışır"
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
return findings;
|
|
190
|
-
}
|
|
191
|
-
function walkSteps(value, visit) {
|
|
192
|
-
if (!Array.isArray(value))
|
|
193
|
-
return;
|
|
194
|
-
for (const entry of value) {
|
|
195
|
-
const step = asRecord(entry);
|
|
196
|
-
if (!step)
|
|
197
|
-
continue;
|
|
198
|
-
visit(step);
|
|
199
|
-
walkSteps(step.steps, visit);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
/** Bulguları `layoutWarnings[]` satırlarına çevirir. */
|
|
203
|
-
export function findingLines(findings) {
|
|
204
|
-
return findings.map(findingLine);
|
|
205
|
-
}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { ENGINE_STEP_TYPES, STEP_ALIASES } from './profile.js';
|
|
2
|
-
import { PAGE_CONTAINER_ELEMENT_ID } from '../normalize.js';
|
|
3
|
-
/**
|
|
4
|
-
* Mobil uygulamanın kök kapsayıcısı. Web'in `pageContainerNode()`'undan üç farkı var:
|
|
5
|
-
*
|
|
6
|
-
* - `layout` prop'u YOK: mobil `container` şeması onu düşürür (`@ng-apps/ui-native`
|
|
7
|
-
* `components/container/schema.ts` `omitProps(['layout', …])`) — mobil kanvas yalnız flex akışıdır
|
|
8
|
-
* ve backend doğrulayıcısı mobil kataloğa bakar, yazılan `layout` bilinmeyen prop olurdu.
|
|
9
|
-
* - `direction: 'column'`, `wrap: false`: telefon ekranı dikey akar; web kökü satır + sarma ile
|
|
10
|
-
* 12 sütunu taklit eder.
|
|
11
|
-
* - `blocks: []` ve `heightType: 'fill'`: çizer blokları yok sayar (D3), kök ekranı doldurur.
|
|
12
|
-
*/
|
|
13
|
-
export function mobileRootContainer() {
|
|
14
|
-
return {
|
|
15
|
-
elementId: PAGE_CONTAINER_ELEMENT_ID,
|
|
16
|
-
kind: 'component',
|
|
17
|
-
name: PAGE_CONTAINER_ELEMENT_ID,
|
|
18
|
-
type: 'container',
|
|
19
|
-
props: {
|
|
20
|
-
title: { type: 'static', data: '' },
|
|
21
|
-
direction: { type: 'static', data: 'column' },
|
|
22
|
-
horizontalAlign: { type: 'static', data: 'left' },
|
|
23
|
-
verticalAlign: { type: 'static', data: 'top' },
|
|
24
|
-
size: { type: 'static', data: 'grow' },
|
|
25
|
-
gap: { type: 'static', data: 'medium' },
|
|
26
|
-
wrap: { type: 'static', data: false }
|
|
27
|
-
},
|
|
28
|
-
blocks: [],
|
|
29
|
-
states: { isSticky: false, isDroppable: true, isScrollable: true, isWrapper: true },
|
|
30
|
-
appearance: { heightType: 'fill', marginValue: 0, marginType: 'none', paddingType: 'none', paddingValue: 0 },
|
|
31
|
-
events: [],
|
|
32
|
-
validations: [],
|
|
33
|
-
stateBindings: [],
|
|
34
|
-
children: []
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Mobil profil = taşınabilir alt küme (`plans/mobil-uygulama-expo/01-mimari-tasarim.md` §5, §7).
|
|
39
|
-
* Yazım platforma YİNE gider (D3: şema aynı); alt kümenin dışı `MOB-*` bulgusu olarak uyarılır.
|
|
40
|
-
*/
|
|
41
|
-
export const mobileProfile = Object.freeze({
|
|
42
|
-
platform: 'mobile',
|
|
43
|
-
catalogType: 'mobile',
|
|
44
|
-
layout: {
|
|
45
|
-
modes: new Set(['flex']),
|
|
46
|
-
blocksMeaningful: false,
|
|
47
|
-
rootContainer: mobileRootContainer,
|
|
48
|
-
orderBy: 'array-index'
|
|
49
|
-
},
|
|
50
|
-
appearance: {
|
|
51
|
-
ignoredKeys: new Set(['className', 'tooltip', 'tooltipPosition', 'zIndex']),
|
|
52
|
-
sizeUnits: new Set(['px', '%'])
|
|
53
|
-
},
|
|
54
|
-
style: { freeStyle: false, hoverStyle: false, resolver: 'native' },
|
|
55
|
-
steps: {
|
|
56
|
-
registered: new Set([...ENGINE_STEP_TYPES, 'alert', 'confirm', 'redirect', 'navigate', 'storage', 'setcookie', 'download', 'share']),
|
|
57
|
-
aliases: STEP_ALIASES,
|
|
58
|
-
infoOnly: new Set(['download', 'setcookie'])
|
|
59
|
-
},
|
|
60
|
-
shell: {
|
|
61
|
-
components: new Set(['app-bar']),
|
|
62
|
-
suggestions: {
|
|
63
|
-
navigations: "sekmeler: application_update ile properties.mobile.tabs (Ayarlar › Sekmeler); üst çubuk için 'app-bar'",
|
|
64
|
-
sidebar: "çekmece: 'drawer' widget'ı + app-bar.leading:'menu' ve menuTarget",
|
|
65
|
-
header: "'app-bar' (başlık, leading: auto | none | back | menu, sağ eylemler)"
|
|
66
|
-
},
|
|
67
|
-
legacyShellNodes: new Set(['tab-bar', 'drawer-menu'])
|
|
68
|
-
},
|
|
69
|
-
externalComponents: false,
|
|
70
|
-
capture: {
|
|
71
|
-
target: 'studio-mobile',
|
|
72
|
-
devices: {
|
|
73
|
-
phone: { width: 390, height: 844, dpr: 2 },
|
|
74
|
-
tablet: { width: 820, height: 1180, dpr: 2 }
|
|
75
|
-
},
|
|
76
|
-
defaultDevice: 'phone'
|
|
77
|
-
}
|
|
78
|
-
});
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import { MOBILE_COMPONENT_NOTES, mobileComponentKnowledge } from '../mobileComponentCatalog.js';
|
|
2
|
-
export const MOBILE_RULES = {
|
|
3
|
-
'MOB-001': {
|
|
4
|
-
id: 'MOB-001',
|
|
5
|
-
severity: 'warn',
|
|
6
|
-
sourceRef: 'zeyrek-frontend/packages/ng-apps-ui-native/src/renderer (NativeRenderer blocks okumaz; sıra dizi indeksinden)',
|
|
7
|
-
summary: 'Mobil sayfada blok (12 sütun ızgara) ya da grid/cssgrid yerleşimi — çizer yok sayar'
|
|
8
|
-
},
|
|
9
|
-
'MOB-002': {
|
|
10
|
-
id: 'MOB-002',
|
|
11
|
-
severity: 'warn',
|
|
12
|
-
sourceRef: 'zeyrek-frontend/packages/ng-apps-ui-native/src/style/compose-native-style.ts (yalnız tipli gruplar derlenir)',
|
|
13
|
-
summary: 'Mobil sayfada CSS-only alan (className, tooltip, zIndex, serbest style, hoverStyle, em/rem/calc ölçü)'
|
|
14
|
-
},
|
|
15
|
-
'MOB-003': {
|
|
16
|
-
id: 'MOB-003',
|
|
17
|
-
severity: 'warn',
|
|
18
|
-
sourceRef: 'zeyrek-backend PageSchemaValidator unknown-type (mobil uygulama mobil kataloğa karşı doğrulanır)',
|
|
19
|
-
summary: 'Mobil katalogda olmayan bileşen tipi — backend 422 unknown-type ile reddeder'
|
|
20
|
-
},
|
|
21
|
-
'MOB-004': {
|
|
22
|
-
id: 'MOB-004',
|
|
23
|
-
severity: 'warn',
|
|
24
|
-
sourceRef: 'plans/mobil-uygulama-expo/05-mobil-ayarlar-ve-gezinme.md §2 (D14)',
|
|
25
|
-
summary: "Mobil layout/sayfada web kabuğu (navigations, sidebar, header) — app-bar, sekmeler ayarı ve drawer widget'ı var"
|
|
26
|
-
},
|
|
27
|
-
'MOB-005': {
|
|
28
|
-
id: 'MOB-005',
|
|
29
|
-
severity: 'info',
|
|
30
|
-
sourceRef: 'zeyrek-frontend/packages/ng-apps-ui-native/src/runtime/runtime-tasks.ts',
|
|
31
|
-
summary: "Mobil uygulamada download paylaşım/açma sayfası açar; setcookie storage'a eşlenir"
|
|
32
|
-
},
|
|
33
|
-
'MOB-006': {
|
|
34
|
-
id: 'MOB-006',
|
|
35
|
-
severity: 'warn',
|
|
36
|
-
sourceRef: 'zeyrek-backend/docs/harici-componentler (protokol v1 tarayıcı .mjs; mobil v2 Faz 3)',
|
|
37
|
-
summary: 'Harici bileşen mobil uygulamada desteklenmiyor — çağrı platforma gitmez'
|
|
38
|
-
},
|
|
39
|
-
'MOB-007': {
|
|
40
|
-
id: 'MOB-007',
|
|
41
|
-
severity: 'warn',
|
|
42
|
-
sourceRef: 'plans/mobil-uygulama-expo/05-mobil-ayarlar-ve-gezinme.md §2.2, §2.6 (K13)',
|
|
43
|
-
summary: "Sekme ayarı tutarsız ya da layout/sayfada eski tab-bar/drawer-menu düğümü"
|
|
44
|
-
},
|
|
45
|
-
'MOB-008': {
|
|
46
|
-
id: 'MOB-008',
|
|
47
|
-
severity: 'warn',
|
|
48
|
-
sourceRef: 'zeyrek-frontend/apps/mobile-runtime/src/navigation/navigation.ts (hrefFor); zeyrek-backend FlowDtoValidator',
|
|
49
|
-
summary: "redirect.presentation kümede değil — push | modal | replace dışı değer backend'de 422, çizer push'a düşer"
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
/** `redirect` adımının tanıdığı sunum biçimleri (b-01 `StepArgument.Presentation`). */
|
|
53
|
-
export const REDIRECT_PRESENTATIONS = new Set(['push', 'modal', 'replace']);
|
|
54
|
-
/**
|
|
55
|
-
* Bileşen dalgaları (`plans/mobil-uygulama-expo/01-mimari-tasarim.md` §11). MOB-003 bir web bileşeninin
|
|
56
|
-
* mobilde NE ZAMAN geleceğini söyler; `web-only` hiç gelmeyecekler ve karşılıkları.
|
|
57
|
-
*
|
|
58
|
-
* Mobil pakete giren bileşen Dalga 2/3 listesinden DÜŞER: yayında olduğunu mobil sözlükteki kaydı
|
|
59
|
-
* söyler (`mobileComponentKnowledge`, parite testiyle `components.json`'a kilitli). Katalog A
|
|
60
|
-
* (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`, `signature`, `chart`,
|
|
62
|
-
* `fileInput` ve `filebutton`ı, Dalga 2'den `datepicker` ve `timepicker`ı (`fileUpload`un sürükle-bırak alanı
|
|
63
|
-
* dokunmatikte yok: web'e özgü, karşılığı iki dosya alanı); dil dalgası (plans/mobil-coklu-dil F1, 2026-09-25)
|
|
64
|
-
* Dalga 2'den `language-selector`ı. Dalga 1
|
|
65
|
-
* listesi artık yalnız `mobileNotes`'un bilgi satırıdır — MOB-003'te yayındaki bileşeni sözlük tanır
|
|
66
|
-
* (`box-section` Dalga 1'de ama pakette yok).
|
|
67
|
-
*/
|
|
68
|
-
export const MOBILE_COMPONENT_WAVES = Object.fromEntries([
|
|
69
|
-
...['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]),
|
|
70
|
-
...['box', 'phoneinput', 'statcard', 'detailview', 'page-header', 'autocomplete'].map(slug => [slug, 2]),
|
|
71
|
-
...['datatable', 'kanban', 'timeline', 'texteditor', 'html', 'stepper', 'tree-view'].map(slug => [slug, 3])
|
|
72
|
-
]);
|
|
73
|
-
export const MOBILE_WEB_ONLY = {
|
|
74
|
-
iframe: "'webview' (src ya da html; bağlantılar varsayılan olarak sistem tarayıcısında açılır)",
|
|
75
|
-
sidebar: "'drawer' widget'ı + app-bar.menuTarget",
|
|
76
|
-
navigations: 'Ayarlar › Sekmeler (application_update properties.mobile.tabs)',
|
|
77
|
-
header: "'app-bar'",
|
|
78
|
-
'layout-shell': "tek slot'lu sıradan layout + 'app-bar'",
|
|
79
|
-
'layout-region': "tek slot'lu sıradan layout",
|
|
80
|
-
'auth-layout': "tek slot'lu sıradan layout",
|
|
81
|
-
popover: "'modal' widget'ı",
|
|
82
|
-
'theme-toggle': 'cihaz teması otomatik (koyu/açık)',
|
|
83
|
-
pagination: "'repeater' + daha fazla yükle düğmesi",
|
|
84
|
-
breadcrumb: "yığın gezinmesi (app-bar geri düğmesi)",
|
|
85
|
-
fileUpload: "'fileInput' (alan) ya da 'filebutton' (düğme) — dokunmatikte sürükle-bırak yok; ikisi de aynı base64 değeri yazar"
|
|
86
|
-
};
|
|
87
|
-
const STYLE_REPLACEMENTS = {
|
|
88
|
-
className: "yerine surfaceStyle (zemin, kenar, köşe, gölge) ve typographyStyle tipli gruplarını kullanın",
|
|
89
|
-
tooltip: "dokunmatikte üzerine gelme yok — düğümü 'tooltip' bileşenine sarın (dokunuşla açılır) ya da açıklamayı metin olarak koyun",
|
|
90
|
-
tooltipPosition: "dokunmatikte üzerine gelme yok — 'tooltip' bileşeninde balonun yanı `side` prop'udur",
|
|
91
|
-
zIndex: "katman için 'modal' ya da 'drawer' widget'ı kullanın",
|
|
92
|
-
style: "serbest CSS nesnesi React Native'de çizilmez — surfaceStyle ve typographyStyle tipli gruplarına taşıyın",
|
|
93
|
-
hoverStyle: 'dokunmatikte üzerine gelme durumu yok'
|
|
94
|
-
};
|
|
95
|
-
export function styleReplacement(key) {
|
|
96
|
-
return STYLE_REPLACEMENTS[key] ?? 'mobil çizer bu alanı okumaz';
|
|
97
|
-
}
|
|
98
|
-
export function missingComponentAdvice(slug) {
|
|
99
|
-
// Yayındaki bileşen: katalog senkronu bu uygulamaya henüz yazmamış olabilir — "gelecek" demek yanlış olur.
|
|
100
|
-
if (Object.hasOwn(mobileComponentKnowledge, slug))
|
|
101
|
-
return `'${slug}' mobil pakette var ama bu uygulamanın kataloğunda henüz yok — components-sync mobil kataloğu yazdı mı (component_list)? Yazmadıysa backend 422 unknown-type döner`;
|
|
102
|
-
const wave = Object.hasOwn(MOBILE_COMPONENT_WAVES, slug) ? MOBILE_COMPONENT_WAVES[slug] : undefined;
|
|
103
|
-
if (wave === 2)
|
|
104
|
-
return `'${slug}' web'de var, mobilde Dalga 2 (F6 sonrası) — şimdilik Dalga 1 bileşenleriyle kurun (component_list)`;
|
|
105
|
-
if (wave === 3)
|
|
106
|
-
return `'${slug}' web'de var, mobilde Faz 3 (ayrı iş) — bu planda mobil karşılığı yok`;
|
|
107
|
-
const alternative = Object.hasOwn(MOBILE_WEB_ONLY, slug) ? MOBILE_WEB_ONLY[slug] : undefined;
|
|
108
|
-
if (alternative)
|
|
109
|
-
return `'${slug}' web'e özgüdür, mobilde gelmeyecek — ${alternative}`;
|
|
110
|
-
return `'${slug}' mobil katalogda yok — component_list ile mobil uygulamanın bileşenlerini görün`;
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* `component_get` mobil uygulamada (G4): kayıt mobil kataloğun satırıdır, davranış notları web
|
|
114
|
-
* runtime'ından (`knowledge`), bileşene özgü farklar mobil sözlükten (`mobileKnowledge`) gelir; bu
|
|
115
|
-
* liste BÜTÜN mobil bileşenlere geçen notlardır — kural kodlu üç not + `MOBILE_COMPONENT_NOTES`.
|
|
116
|
-
*/
|
|
117
|
-
export function mobileComponentNotes(slug) {
|
|
118
|
-
const wave = slug === undefined ? undefined : MOBILE_COMPONENT_WAVES[slug];
|
|
119
|
-
return [
|
|
120
|
-
"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).",
|
|
121
|
-
"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.",
|
|
122
|
-
"Basılabilir bileşen role=\"button\" taşır; page_screenshot click onu kabuğun içinde bulur.",
|
|
123
|
-
...MOBILE_COMPONENT_NOTES,
|
|
124
|
-
...(wave === undefined ? [] : [`Mobil bileşen dalgası: ${wave} (plans/mobil-uygulama-expo §11).`])
|
|
125
|
-
];
|
|
126
|
-
}
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
export const MAX_MOBILE_TABS = 5;
|
|
2
|
-
const STYLES = new Set(['default', 'floating', 'custom']);
|
|
3
|
-
function record(value) {
|
|
4
|
-
return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
5
|
-
}
|
|
6
|
-
function text(value) {
|
|
7
|
-
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
|
|
8
|
-
}
|
|
9
|
-
/** Öğenin sayfa id'si: `{ page }` ya da düz metin (`items: ['id']`). */
|
|
10
|
-
function entryPage(entry) {
|
|
11
|
-
const item = record(entry);
|
|
12
|
-
return item ? text(item.page) : text(entry);
|
|
13
|
-
}
|
|
14
|
-
/** `Application.properties.mobile.tabs` — ham değer, doğrulanmamış. */
|
|
15
|
-
export function readMobileTabs(properties) {
|
|
16
|
-
return record(record(properties)?.mobile)?.tabs;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Ham sekme ayarını çizilebilir biçime getirir; çizilecek çubuk yoksa `null` (yığın).
|
|
20
|
-
*
|
|
21
|
-
* - Nesne değilse, `enabled !== true` ise ya da geçerli öğe kalmadıysa → `null`.
|
|
22
|
-
* - `style` bilinmiyorsa `default`; `showLabels` boolean değilse `true`.
|
|
23
|
-
* - Öğe `{ page }` ya da düz sayfa id'sidir; sayfası yoksa düşer; `pages` verildiyse orada olmayan
|
|
24
|
-
* sayfa düşer; tekrar eden sayfa (ilki kalır) atılır; `MAX_MOBILE_TABS`'tan sonrası kırpılır.
|
|
25
|
-
* `label`/`icon` boş metinse yazılmaz.
|
|
26
|
-
* - Görünüm alanı yoktur: çubuk temadan çizilir; eski kayıtların `appearance`'ı okunmaz.
|
|
27
|
-
* - `customWidgetId` yalnız `custom` türde ve metinse yazılır.
|
|
28
|
-
*/
|
|
29
|
-
export function normalizeMobileTabs(tabs, pages) {
|
|
30
|
-
const source = record(tabs);
|
|
31
|
-
if (!source || source.enabled !== true)
|
|
32
|
-
return null;
|
|
33
|
-
const known = pages ? new Set(pages.map(page => page.id)) : undefined;
|
|
34
|
-
const seen = new Set();
|
|
35
|
-
const items = [];
|
|
36
|
-
for (const entry of Array.isArray(source.items) ? source.items : []) {
|
|
37
|
-
const item = record(entry);
|
|
38
|
-
const page = entryPage(entry);
|
|
39
|
-
if (!page || seen.has(page) || (known && !known.has(page)))
|
|
40
|
-
continue;
|
|
41
|
-
seen.add(page);
|
|
42
|
-
const label = text(item?.label);
|
|
43
|
-
const icon = text(item?.icon);
|
|
44
|
-
items.push({ page, ...(label ? { label } : {}), ...(icon ? { icon } : {}) });
|
|
45
|
-
if (items.length === MAX_MOBILE_TABS)
|
|
46
|
-
break;
|
|
47
|
-
}
|
|
48
|
-
if (items.length === 0)
|
|
49
|
-
return null;
|
|
50
|
-
const style = (typeof source.style === 'string' && STYLES.has(source.style) ? source.style : 'default');
|
|
51
|
-
const customWidgetId = style === 'custom' ? text(source.customWidgetId) : undefined;
|
|
52
|
-
return {
|
|
53
|
-
enabled: true,
|
|
54
|
-
style,
|
|
55
|
-
items,
|
|
56
|
-
...(customWidgetId ? { customWidgetId } : {}),
|
|
57
|
-
showLabels: typeof source.showLabels === 'boolean' ? source.showLabels : true
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* MOB-007 denetimi: ham ayarın normalizasyonda neyi kaybedeceğini ve tutarsızlıklarını söyler.
|
|
62
|
-
* Yazım yine platforma gider (D3); bulgular yalnız uyarıdır.
|
|
63
|
-
*/
|
|
64
|
-
export function mobileTabsProblems(tabs, context = {}) {
|
|
65
|
-
if (tabs === undefined || tabs === null)
|
|
66
|
-
return [];
|
|
67
|
-
const source = record(tabs);
|
|
68
|
-
if (!source)
|
|
69
|
-
return [`properties.mobile.tabs nesne olmalı (gelen: ${Array.isArray(tabs) ? 'dizi' : typeof tabs}) — sekme çubuğu çizilmez, uygulama yığınla açılır`];
|
|
70
|
-
const problems = [];
|
|
71
|
-
const rawItems = Array.isArray(source.items) ? source.items : undefined;
|
|
72
|
-
if (source.enabled !== undefined && typeof source.enabled !== 'boolean')
|
|
73
|
-
problems.push(`enabled boolean olmalı (gelen: ${JSON.stringify(source.enabled)}) — true olmayan her değer sekmeleri kapatır`);
|
|
74
|
-
if (source.style !== undefined && !(typeof source.style === 'string' && STYLES.has(source.style)))
|
|
75
|
-
problems.push(`style '${String(source.style)}' tanınmıyor (default | floating | custom) — 'default' çizilir`);
|
|
76
|
-
if (source.items !== undefined && !rawItems)
|
|
77
|
-
problems.push('items dizi olmalı — hiçbir sekme çizilmez');
|
|
78
|
-
const items = rawItems ?? [];
|
|
79
|
-
const pageIds = items.map(entryPage);
|
|
80
|
-
if (source.enabled === true && items.length === 0)
|
|
81
|
-
problems.push("enabled:true ama items boş — çubuk çizilmez; en az bir sayfa ekleyin ya da enabled:false yazın");
|
|
82
|
-
if (items.length > MAX_MOBILE_TABS)
|
|
83
|
-
problems.push(`${items.length} sekme var, en çok ${MAX_MOBILE_TABS} çizilir — fazlası kırpılır (${pageIds.slice(MAX_MOBILE_TABS).filter(Boolean).join(', ')})`);
|
|
84
|
-
const missingPage = pageIds.filter(id => !id).length;
|
|
85
|
-
if (missingPage > 0)
|
|
86
|
-
problems.push(`${missingPage} öğede page (sayfa id'si) yok — o öğeler düşer`);
|
|
87
|
-
const duplicates = pageIds.filter((id, index) => id && pageIds.indexOf(id) !== index);
|
|
88
|
-
if (duplicates.length > 0)
|
|
89
|
-
problems.push(`aynı sayfa birden çok sekmede: ${[...new Set(duplicates)].join(', ')} — tekrarlar atılır`);
|
|
90
|
-
if (context.pages) {
|
|
91
|
-
const known = new Set(context.pages.map(page => page.id));
|
|
92
|
-
const unknown = pageIds.filter((id) => Boolean(id) && !known.has(id));
|
|
93
|
-
if (unknown.length > 0)
|
|
94
|
-
problems.push(`var olmayan sayfa: ${[...new Set(unknown)].join(', ')} — page_list ile id'leri doğrulayın; bu öğeler düşer (item.page slug değil sayfa id'sidir)`);
|
|
95
|
-
}
|
|
96
|
-
const normalized = normalizeMobileTabs(tabs, context.pages ?? null);
|
|
97
|
-
if (normalized && context.homepageId && normalized.items[0]?.page !== context.homepageId) {
|
|
98
|
-
problems.push(`açılış sayfası (${context.homepageId}) ilk sekme değil — uygulama açılış sayfasıyla başlar ama çubukta başka sekme seçili görünür; açılış sayfasını items'ın başına alın`);
|
|
99
|
-
}
|
|
100
|
-
if (normalized?.style === 'custom') {
|
|
101
|
-
const widgetId = normalized.customWidgetId;
|
|
102
|
-
if (!widgetId)
|
|
103
|
-
problems.push("style:'custom' ama customWidgetId yok — özel sekme çubuğu widget'ı seçilmeli (component_list kind:'widget')");
|
|
104
|
-
else if (context.widgetIds && !context.widgetIds.has(widgetId))
|
|
105
|
-
problems.push(`customWidgetId '${widgetId}' bu mobil uygulamanın widget'ı değil — çubuk yerine uyarı çizilir`);
|
|
106
|
-
}
|
|
107
|
-
return problems;
|
|
108
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Motorun kendi register ettiği adımlar (`@ng-apps/flow` `flow-engine.ts` initialize) — iki
|
|
3
|
-
* platformda da koşar. `end` ve `query` hiçbir barındırıcıda yoktur (normalizasyon `end`'i atar).
|
|
4
|
-
*/
|
|
5
|
-
export const ENGINE_STEP_TYPES = ['start', 'setter', 'code', 'condition', 'microservice', 'validation', 'flow-trigger', 'function'];
|
|
6
|
-
/**
|
|
7
|
-
* Eski adım tipi → koşan karşılığı. `navigate` ile `redirect`'in argümanı aynıdır; web `navigate`'i
|
|
8
|
-
* register etmez, yeni mobil runtime onu `redirect` takma adı olarak kaydeder — iki platformda da
|
|
9
|
-
* `redirect` kesin üstündür.
|
|
10
|
-
*/
|
|
11
|
-
export const STEP_ALIASES = Object.freeze({ navigate: 'redirect' });
|
|
12
|
-
/** Tipli stil grupları iki platformda da çizilir (web CSS'e, mobil RN stiline derler). */
|
|
13
|
-
export const TYPED_STYLE_GROUPS = ['surfaceStyle', 'typographyStyle', 'sectionStyle', 'headerStyle', 'valueStyle'];
|
|
14
|
-
/** Bulguyu mevcut `layoutWarnings[]` satır biçimine çevirir (`PAGE-010 (uyarı) — …` kalıbı). */
|
|
15
|
-
export function findingLine(finding) {
|
|
16
|
-
const level = finding.severity === 'info' ? 'bilgi' : 'uyarı';
|
|
17
|
-
const target = finding.target ? ` [${finding.target}]` : '';
|
|
18
|
-
const guide = finding.guide ? ` (${finding.guide})` : '';
|
|
19
|
-
return `${finding.code} (${level})${target} — ${finding.message}${guide}`;
|
|
20
|
-
}
|