@appaflytech/zeyrek-ai-mcp 0.0.20 → 0.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +14 -13
  2. package/dist/config.js +0 -2
  3. package/dist/knowledge/componentCatalog.js +940 -215
  4. package/dist/knowledge/componentEvents.js +368 -75
  5. package/dist/knowledge/normalize.js +66 -63
  6. package/dist/knowledge/rules.js +29 -6
  7. package/dist/knowledge/styleResolve.js +1 -1
  8. package/dist/knowledge/themeResolve.js +3 -3
  9. package/dist/platform/diagnostics.js +85 -0
  10. package/dist/platform/domSummary.js +1 -1
  11. package/dist/platform/ifExists.js +129 -0
  12. package/dist/platform/responses.js +2 -1
  13. package/dist/platform/{capture/capture.js → screenshot.js} +509 -23
  14. package/dist/platform/strictArguments.js +22 -2
  15. package/dist/platform/writeDiff.js +58 -31
  16. package/dist/platform/writeView.js +17 -0
  17. package/dist/resources/guides/component.md +9 -6
  18. package/dist/resources/guides/functions.md +32 -3
  19. package/dist/resources/guides/getting-started.md +35 -0
  20. package/dist/resources/guides/layout.md +7 -11
  21. package/dist/resources/guides/page.md +49 -71
  22. package/dist/resources/guides/translation.md +5 -6
  23. package/dist/resources/schema-fingerprint.json +2 -2
  24. package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +18 -0
  25. package/dist/server.js +0 -2
  26. package/dist/tools/applications.js +3 -60
  27. package/dist/tools/components.js +10 -50
  28. package/dist/tools/designSchemaPatch.js +13 -18
  29. package/dist/tools/externalComponents.js +4 -25
  30. package/dist/tools/flows.js +38 -51
  31. package/dist/tools/functions.js +20 -10
  32. package/dist/tools/layouts.js +9 -41
  33. package/dist/tools/microservices.js +39 -11
  34. package/dist/tools/pages.js +95 -127
  35. package/dist/tools/platformInfo.js +1 -2
  36. package/dist/tools/schemaOps.js +2 -2
  37. package/dist/tools/screenWrite.js +2 -2
  38. package/dist/tools/screens.js +3 -9
  39. package/dist/tools/workflows.js +39 -12
  40. package/package.json +7 -2
  41. package/dist/knowledge/mobileComponentCatalog.js +0 -331
  42. package/dist/knowledge/mobileComponentEvents.js +0 -50
  43. package/dist/knowledge/platform/lint.js +0 -205
  44. package/dist/knowledge/platform/mobile.js +0 -78
  45. package/dist/knowledge/platform/mobileRules.js +0 -121
  46. package/dist/knowledge/platform/mobileTabs.js +0 -108
  47. package/dist/knowledge/platform/profile.js +0 -20
  48. package/dist/knowledge/platform/resolve.js +0 -122
  49. package/dist/knowledge/platform/web.js +0 -38
  50. package/dist/knowledge/styleResolveNative.js +0 -410
  51. package/dist/platform/capture/actions.js +0 -333
  52. package/dist/platform/capture/browser.js +0 -1
  53. package/dist/platform/capture/index.js +0 -10
  54. package/dist/platform/capture/readiness.js +0 -103
  55. package/dist/platform/capture/summary.js +0 -77
  56. package/dist/platform/capture/target.js +0 -5
  57. package/dist/platform/capture/targets/studio-mobile.js +0 -15
  58. package/dist/platform/capture/targets/studio-web.js +0 -5
  59. package/dist/resources/examples/mobile-app-shell-drawer.json +0 -209
  60. package/dist/resources/examples/mobile-app-shell-stack.json +0 -123
  61. package/dist/resources/examples/mobile-app-shell-tab.json +0 -174
  62. package/dist/resources/examples/mobile-list-detail-form.json +0 -656
  63. package/dist/resources/guides/mobile.md +0 -215
  64. package/dist/tools/mobile.js +0 -203
  65. package/dist/tools/profileWrite.js +0 -45
@@ -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']),
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,121 +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`. Dalga 1 listesi artık yalnız `mobileNotes`'un bilgi
62
- * satırıdır — MOB-003'te yayındaki bileşeni sözlük tanır (`box-section` Dalga 1'de ama pakette yok).
63
- */
64
- export const MOBILE_COMPONENT_WAVES = Object.fromEntries([
65
- ...['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]),
66
- ...['box', 'datepicker', 'timepicker', 'otpinput', 'phoneinput', 'rating', 'statcard', 'toast', 'detailview', 'page-header', 'qrcode', 'language-selector', 'multi-select', 'autocomplete'].map(slug => [slug, 2]),
67
- ...['datatable', 'chart', 'kanban', 'timeline', 'fileUpload', 'filebutton', 'fileInput', 'signature', 'carousel', 'video', 'texteditor', 'html', 'stepper', 'tree-view'].map(slug => [slug, 3])
68
- ]);
69
- export const MOBILE_WEB_ONLY = {
70
- iframe: "dış içerik için button + redirect (targetBlank) ile tarayıcıda açın",
71
- sidebar: "'drawer' widget'ı + app-bar.menuTarget",
72
- navigations: 'Ayarlar › Sekmeler (application_update properties.mobile.tabs)',
73
- header: "'app-bar'",
74
- 'layout-shell': "tek slot'lu sıradan layout + 'app-bar'",
75
- 'layout-region': "tek slot'lu sıradan layout",
76
- 'auth-layout': "tek slot'lu sıradan layout",
77
- popover: "'modal' widget'ı",
78
- 'theme-toggle': 'cihaz teması otomatik (koyu/açık)',
79
- pagination: "'repeater' + daha fazla yükle düğmesi",
80
- breadcrumb: "yığın gezinmesi (app-bar geri düğmesi)"
81
- };
82
- const STYLE_REPLACEMENTS = {
83
- className: "yerine surfaceStyle (zemin, kenar, köşe, gölge) ve typographyStyle tipli gruplarını kullanın",
84
- tooltip: "dokunmatikte üzerine gelme yok — düğümü 'tooltip' bileşenine sarın (dokunuşla açılır) ya da açıklamayı metin olarak koyun",
85
- tooltipPosition: "dokunmatikte üzerine gelme yok — 'tooltip' bileşeninde balonun yanı `side` prop'udur",
86
- zIndex: "katman için 'modal' ya da 'drawer' widget'ı kullanın",
87
- style: "serbest CSS nesnesi React Native'de çizilmez — surfaceStyle ve typographyStyle tipli gruplarına taşıyın",
88
- hoverStyle: 'dokunmatikte üzerine gelme durumu yok'
89
- };
90
- export function styleReplacement(key) {
91
- return STYLE_REPLACEMENTS[key] ?? 'mobil çizer bu alanı okumaz';
92
- }
93
- export function missingComponentAdvice(slug) {
94
- // Yayındaki bileşen: katalog senkronu bu uygulamaya henüz yazmamış olabilir — "gelecek" demek yanlış olur.
95
- if (Object.hasOwn(mobileComponentKnowledge, slug))
96
- 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`;
97
- const wave = Object.hasOwn(MOBILE_COMPONENT_WAVES, slug) ? MOBILE_COMPONENT_WAVES[slug] : undefined;
98
- if (wave === 2)
99
- return `'${slug}' web'de var, mobilde Dalga 2 (F6 sonrası) — şimdilik Dalga 1 bileşenleriyle kurun (component_list)`;
100
- if (wave === 3)
101
- return `'${slug}' web'de var, mobilde Faz 3 (ayrı iş) — bu planda mobil karşılığı yok`;
102
- const alternative = Object.hasOwn(MOBILE_WEB_ONLY, slug) ? MOBILE_WEB_ONLY[slug] : undefined;
103
- if (alternative)
104
- return `'${slug}' web'e özgüdür, mobilde gelmeyecek — ${alternative}`;
105
- return `'${slug}' mobil katalogda yok — component_list ile mobil uygulamanın bileşenlerini görün`;
106
- }
107
- /**
108
- * `component_get` mobil uygulamada (G4): kayıt mobil kataloğun satırıdır, davranış notları web
109
- * runtime'ından (`knowledge`), bileşene özgü farklar mobil sözlükten (`mobileKnowledge`) gelir; bu
110
- * liste BÜTÜN mobil bileşenlere geçen notlardır — kural kodlu üç not + `MOBILE_COMPONENT_NOTES`.
111
- */
112
- export function mobileComponentNotes(slug) {
113
- const wave = slug === undefined ? undefined : MOBILE_COMPONENT_WAVES[slug];
114
- return [
115
- "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).",
116
- "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.",
117
- "Basılabilir bileşen role=\"button\" taşır; page_screenshot click onu kabuğun içinde bulur.",
118
- ...MOBILE_COMPONENT_NOTES,
119
- ...(wave === undefined ? [] : [`Mobil bileşen dalgası: ${wave} (plans/mobil-uygulama-expo §11).`])
120
- ];
121
- }
@@ -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
- }
@@ -1,122 +0,0 @@
1
- import { mobileProfile } from './mobile.js';
2
- import { webProfile } from './web.js';
3
- export const APPLICATION_TYPE_TTL_MS = 10 * 60_000;
4
- const caches = new WeakMap();
5
- function cacheOf(platform) {
6
- let cache = caches.get(platform);
7
- if (!cache) {
8
- cache = new Map();
9
- caches.set(platform, cache);
10
- }
11
- return cache;
12
- }
13
- /** Backend `ApplicationType` JSON'da açıklama metniyle ('web' | 'mobile') döner; eski sayısal biçim de okunur. */
14
- export function readApplicationPlatform(value) {
15
- if (value === 1 || (typeof value === 'string' && value.toLowerCase() === 'mobile'))
16
- return 'mobile';
17
- if (value === 0 || (typeof value === 'string' && value.toLowerCase() === 'web'))
18
- return 'web';
19
- return undefined;
20
- }
21
- export function profileFor(platform) {
22
- return platform === 'mobile' ? mobileProfile : webProfile;
23
- }
24
- function factsOf(record) {
25
- const platform = readApplicationPlatform(record.type);
26
- const id = typeof record.id === 'string' ? record.id : undefined;
27
- if (!platform || !id)
28
- return undefined;
29
- const homepage = record.homepage && typeof record.homepage === 'object' ? record.homepage.id : undefined;
30
- const homepageId = typeof record.homepageId === 'string' ? record.homepageId : typeof homepage === 'string' ? homepage : undefined;
31
- const properties = record.properties && typeof record.properties === 'object' && !Array.isArray(record.properties)
32
- ? record.properties
33
- : undefined;
34
- return {
35
- id,
36
- platform,
37
- ...(typeof record.slug === 'string' ? { slug: record.slug } : {}),
38
- ...(typeof record.name === 'string' ? { name: record.name } : {}),
39
- ...(typeof record.projectSlug === 'string' ? { projectSlug: record.projectSlug } : {}),
40
- ...(homepageId ? { homepageId } : {}),
41
- ...(properties ? { properties } : {})
42
- };
43
- }
44
- function fresh(entry) {
45
- return entry !== undefined && Date.now() - entry.at < APPLICATION_TYPE_TTL_MS;
46
- }
47
- export async function resolveApplication(platform, applicationId) {
48
- const cache = cacheOf(platform);
49
- const cached = cache.get(applicationId);
50
- if (fresh(cached))
51
- return cached.facts;
52
- try {
53
- const payload = await platform.org('GET', `/applications/${applicationId}`);
54
- const facts = factsOf(((payload?.data ?? payload) ?? {}));
55
- if (facts)
56
- cache.set(applicationId, { facts, at: Date.now() });
57
- return facts;
58
- }
59
- catch {
60
- return undefined;
61
- }
62
- }
63
- export async function resolveProfile(platform, applicationId) {
64
- const application = await resolveApplication(platform, applicationId);
65
- return application
66
- ? { profile: profileFor(application.platform), known: true, application }
67
- : { profile: webProfile, known: false };
68
- }
69
- const SLUG_LOOKUP_PAGE = 50;
70
- /**
71
- * `page_screenshot` ve `page_runtime_get` uygulamayı slug ile tanır. Org listesi `searchTerm` ile
72
- * daraltılır ve slug + proje slug'ı BİREBİR eşleşen kayıt alınır (arama ILIKE'tır, `shop` `shop-2`'yi
73
- * de getirir). B1'den beri liste tip filtresi verilmeyince iki tipi de döner. Aynı slug'ı çok proje
74
- * kullanıyorsa (`mobile`, `admin`) aranan kayıt ilk sayfanın dışında kalabilir: sayfa doluysa ve
75
- * eşleşme yoksa arama proje kimliğiyle tekrarlanır — (proje, slug) tekildir.
76
- */
77
- export async function resolveProfileBySlug(platform, projectSlug, applicationSlug) {
78
- const cache = cacheOf(platform);
79
- const key = `slug:${projectSlug}/${applicationSlug}`;
80
- const cached = cache.get(key);
81
- if (fresh(cached))
82
- return { profile: profileFor(cached.facts.platform), known: true, application: cached.facts };
83
- try {
84
- const search = async (query) => {
85
- const payload = await platform.org('GET', '/applications', { query: { searchTerm: applicationSlug, pageLength: SLUG_LOOKUP_PAGE, ...query } });
86
- return Array.isArray(payload?.data) ? payload.data : [];
87
- };
88
- const matchIn = (rows) => rows.find(row => row.slug === applicationSlug && (row.projectSlug === undefined || row.projectSlug === projectSlug));
89
- const rows = await search({});
90
- let match = matchIn(rows);
91
- if (!match && rows.length >= SLUG_LOOKUP_PAGE) {
92
- const project = await platform.org('GET', `/p/${projectSlug}`);
93
- const projectId = (project?.data ?? project)?.id;
94
- if (typeof projectId === 'string')
95
- match = matchIn(await search({ projectId }));
96
- }
97
- const facts = match ? factsOf(match) : undefined;
98
- if (!facts)
99
- return { profile: webProfile, known: false };
100
- cache.set(key, { facts, at: Date.now() });
101
- return { profile: profileFor(facts.platform), known: true, application: facts };
102
- }
103
- catch {
104
- return { profile: webProfile, known: false };
105
- }
106
- }
107
- /** Yazma araçları uygulamayı değiştirdiğinde (tip, slug, properties) kaydı düşürür. */
108
- export function forgetApplication(platform, applicationId) {
109
- const cache = cacheOf(platform);
110
- if (applicationId === undefined) {
111
- cache.clear();
112
- return;
113
- }
114
- for (const [key, entry] of cache) {
115
- if (key === applicationId || entry.facts.id === applicationId)
116
- cache.delete(key);
117
- }
118
- }
119
- /** Yanıta eklenen alan: tip biliniyorsa `applicationType`, bilinmiyorsa hiçbir şey. */
120
- export function applicationTypeField(resolved) {
121
- return resolved.known ? { applicationType: resolved.profile.platform } : {};
122
- }