@appaflytech/zeyrek-ai-mcp 0.0.18 → 0.0.20
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/README.md +13 -14
- package/dist/config.js +2 -0
- package/dist/knowledge/componentCatalog.js +204 -836
- package/dist/knowledge/componentEvents.js +75 -368
- package/dist/knowledge/mobileComponentCatalog.js +331 -0
- package/dist/knowledge/mobileComponentEvents.js +50 -0
- package/dist/knowledge/normalize.js +63 -66
- package/dist/knowledge/platform/lint.js +205 -0
- package/dist/knowledge/platform/mobile.js +78 -0
- package/dist/knowledge/platform/mobileRules.js +121 -0
- package/dist/knowledge/platform/mobileTabs.js +108 -0
- package/dist/knowledge/platform/profile.js +20 -0
- package/dist/knowledge/platform/resolve.js +122 -0
- package/dist/knowledge/platform/web.js +38 -0
- package/dist/knowledge/rules.js +6 -29
- package/dist/knowledge/styleResolve.js +1 -1
- package/dist/knowledge/styleResolveNative.js +410 -0
- package/dist/knowledge/themeResolve.js +3 -3
- package/dist/platform/capture/actions.js +333 -0
- package/dist/platform/capture/browser.js +1 -0
- package/dist/platform/{screenshot.js → capture/capture.js} +23 -509
- package/dist/platform/capture/index.js +10 -0
- package/dist/platform/capture/readiness.js +103 -0
- package/dist/platform/capture/summary.js +77 -0
- package/dist/platform/capture/target.js +5 -0
- package/dist/platform/capture/targets/studio-mobile.js +15 -0
- package/dist/platform/capture/targets/studio-web.js +5 -0
- package/dist/platform/domSummary.js +1 -1
- package/dist/platform/responses.js +1 -2
- package/dist/platform/strictArguments.js +2 -22
- package/dist/platform/writeDiff.js +31 -58
- package/dist/resources/examples/mobile-app-shell-drawer.json +209 -0
- package/dist/resources/examples/mobile-app-shell-stack.json +123 -0
- package/dist/resources/examples/mobile-app-shell-tab.json +174 -0
- package/dist/resources/examples/mobile-list-detail-form.json +656 -0
- package/dist/resources/guides/component.md +6 -9
- package/dist/resources/guides/functions.md +3 -32
- package/dist/resources/guides/getting-started.md +0 -35
- package/dist/resources/guides/layout.md +11 -7
- package/dist/resources/guides/mobile.md +215 -0
- package/dist/resources/guides/page.md +71 -49
- package/dist/resources/guides/translation.md +6 -5
- package/dist/resources/schema-fingerprint.json +2 -2
- package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +0 -18
- package/dist/server.js +2 -0
- package/dist/tools/applications.js +60 -3
- package/dist/tools/components.js +50 -10
- package/dist/tools/designSchemaPatch.js +18 -13
- package/dist/tools/externalComponents.js +25 -4
- package/dist/tools/flows.js +51 -38
- package/dist/tools/functions.js +10 -20
- package/dist/tools/layouts.js +41 -9
- package/dist/tools/microservices.js +11 -39
- package/dist/tools/mobile.js +203 -0
- package/dist/tools/pages.js +127 -95
- package/dist/tools/platformInfo.js +2 -1
- package/dist/tools/profileWrite.js +45 -0
- package/dist/tools/schemaOps.js +2 -2
- package/dist/tools/screenWrite.js +2 -2
- package/dist/tools/screens.js +9 -3
- package/dist/tools/workflows.js +12 -39
- package/package.json +2 -7
- package/dist/platform/diagnostics.js +0 -85
- package/dist/platform/ifExists.js +0 -129
- package/dist/platform/writeView.js +0 -17
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bütün mobil komponentler için geçerli kurallar. Kayıt başına tekrarlanmaz;
|
|
3
|
+
* okuyucu (`component_get`) bu notları `mobileNotes`'a ekler
|
|
4
|
+
* (`platform/mobileRules.ts` `mobileComponentNotes`). Yerleşim (MOB-001) ve stil
|
|
5
|
+
* (MOB-002) notları kural koduyla birlikte orada durur, burada tekrarlanmaz.
|
|
6
|
+
*/
|
|
7
|
+
export const MOBILE_COMPONENT_NOTES = [
|
|
8
|
+
"Tasarım kipinde (Studio kanvası) alanlar yazılamaz ve düğmeler basılamaz: dokunuş kanvasın seçim jestidir.",
|
|
9
|
+
"Olay yükleri web ile AYNI sözleşmedir (`componentEvents.ts`): alanlar `change`/`changed`/`enter`/`focus`/`blur`, seçim bileşenleri `change` + `checked`/`unchecked`, katmanlar kapanışta `change { isOpen: false }` ve `open`/`close` yayar; `type: submit` düğme `click` yaymaz, üstteki `form`un `submit`ini tetikler (`formId` varsa o formun). Web'deki istemci tarafı şema doğrulaması (`validations` → zod) mobilde YOKTUR: doğrulamayı akışta yapın.",
|
|
10
|
+
"Bileşenler shadcn/ui görünümünde çizilir (react-native-reusables ilkelleri, NativeWind); renk ve köşe yine PROJE TEMASINDAN gelir: `--ng-*` değişkenleri shadcn token'larına (`--primary`, `--secondary`, `--muted`, `--destructive`, `--border`, `--input`, `--ring`…) ve köşe ölçeğine köprüyle çevrilir (`shadcn-vars.ts`), koyu kip aynı köprünün ikinci çözümüdür. Tema değişince web ve mobil birlikte hareket eder; web sayfası kendi bileşen ailesiyle çizildiği için iki platformun görünüm farkı bilinçlidir. Ölçü (alan yüksekliği, punto, iç boşluk) temanın `--ng-form-*` değişkenlerinden gelir. Yazı AİLESİ köprüde yoktur: temanın ailesi alanlarda (metin, sayı, parola, açılır liste), başlıklarda (`app-bar`, `form`, `modal`/`drawer`, boş durum) ve sekme çubuğunda uygulanır; `text` ve düğme etiketi ata kabın `typographyStyle` ailesini, o da yoksa platform yazı tipini kullanır; alan, anahtar ve onay kutusu etiketleri HER ZAMAN platform yazı tipiyle çizilir.",
|
|
11
|
+
"Boyut panelinin en-boy oranı (`appearance.aspectRatio`, SAYI — 16:9 için `1.78`) boyu genişlikten türetir ve düğüm KABUĞUNA yazılır (zemin, kenar ve iç boşluk dahil görünen kutu tam oranda). Üç koşul: `heightType: 'auto'` (web ile aynı; `fixed`/`fill` ya da yazılmamış boy oranı ezer), genişlik BELLİ — `widthType: 'fill'` ya da `'fixed'` + `widthString` (içerik genişliğinde react-native-web boyu genişlikten, cihazın Yoga'sı genişliği boydan türetir; iki kutu ayrışmasın diye oran o durumda uygulanmaz), düğüm widget örneği değil. Web'den farkı AKIŞ KAPLARI: `container`, `form`, `safe-area`, `scroll-view` mobilde oran alır ve akışları oranlı boyu doldurur (`verticalAlign` `middle`/`bottom`/`stretch` web gibi çalışır). Öteki bırakılabilir tipler (`collapsible-container`, `repeater`, `tabs`, `tooltip`), sayfa kökü ve katmanlar (`modal`, `drawer`) oran almaz; `image` oranı kendi çizimine yazar."
|
|
12
|
+
];
|
|
13
|
+
/**
|
|
14
|
+
* Web sözlüğünün `STATE_FLAG_NOTES`'undan mobilde FARKLI olan bayraklar. `component_get` mobil
|
|
15
|
+
* uygulamada bunları web notlarının üstüne yazar; kaydı olmayan bayrak web notunu korur.
|
|
16
|
+
* Kaynak: `packages/ng-apps-ui-native/src/renderer/renderer.functions.ts` `aspectRatioStyle`.
|
|
17
|
+
*/
|
|
18
|
+
export const MOBILE_STATE_FLAG_NOTES = {
|
|
19
|
+
isDroppable: "Düğüm çocuk kabul eder ve iç dolgusunu padding kutusundan alır. Web'den farklı olarak mobilde AKIŞ KAPLARI (`container`, `form`, `safe-area`, `scroll-view`) appearance.aspectRatio'yu uygular — boy `auto` ve genişlik `fill`/`fixed` iken; kabın akışı oranlı boyu doldurur. Öteki bırakılabilir tipler (katlanır kap, liste, sekmeler, ipucu, katmanlar) oranı yok sayar."
|
|
20
|
+
};
|
|
21
|
+
export const mobileComponentKnowledge = {
|
|
22
|
+
container: {
|
|
23
|
+
summary: 'Flex akışı; mobil sayfanın tek yerleşim kabı.',
|
|
24
|
+
differences: [
|
|
25
|
+
"`layout` düşer (flex-only), `hoverStyle` düşer (dokunmatikte hover durumu yoktur), `style` düşer.",
|
|
26
|
+
'Yüzey (zemin, köşe, kenarlık, gölge) düğüm kabuğunda derlenir; kabın kendisi yalnız yön, hizalama ve aralık (`gap`) taşır.',
|
|
27
|
+
"Hiza adları web ile aynı eşlenir: `verticalAlign: 'middle'` ve `horizontalAlign: 'center'` ortalar. Dikey hizanın kullanacağı boşluk kabın boyundan gelir — `heightType` `fill`, `fixed` ya da en-boy oranlı kapta akış kabuğun boyunu doldurur; boyu içerikten gelen SÜTUN kapta dikey hiza etkisizdir (satır kapta farklı boydaki kardeşleri yine hizalar). `stretch` de web gibi: ana eksende (sütunda `verticalAlign`, satırda `horizontalAlign`) çocukları eksen boyunca yayar, çapraz eksende gerer.",
|
|
28
|
+
'Kutu modeli web ile AYNI kural ve varsayılanlarla çizilir (`appearance.marginType/paddingType`, `normal` = 4 / 10 dp, `custom` kenar kenar): tipi yazılmamış düğüm `normal` dış boşluk, bırakılabilir kap `normal` iç boşluk alır — kenara yaslanacak kapta `none` yazın. `wrap: true` iken satırlar çapraz hizayı izler (web `flex-layout` ile aynı: `stretch` kabı doldurur, `top`/`left` üstten dizer).'
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
'widget-area': {
|
|
32
|
+
summary: 'Konteyner widget`ının kökü; yerleşim sözleşmesi `container` ile aynıdır.',
|
|
33
|
+
differences: [
|
|
34
|
+
'`layout` düşer (flex-only), `style` düşer.',
|
|
35
|
+
'Özel sekme çubuğu (Ayarlar › Sekmeler › Özel widget, K14) bu kökü taşıyan mobil widget`tır: runtime onu `Widget.tabs = { items: [{ page, slug, label, icon, active }], activePage }` parametresiyle çizer, kök kimliği `__tabs`.'
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
'safe-area': {
|
|
39
|
+
summary: 'Güvenli alan kabı: içeriği durum çubuğu, çentik ve ev göstergesinden uzak tutar (D15).',
|
|
40
|
+
differences: [
|
|
41
|
+
'Web`de karşılığı YOKTUR — mobile özgü şema; yerleşim alanları mobil `container` ile aynıdır, varsayılan yön sütun.',
|
|
42
|
+
'Pay barındırıcının cihaz bilgisinden gelir (`host.device.insets`): cihazda gerçek güvenli alan, Studio kanvasında seçili cihaz ön ayarı, önizlemede çerçevenin cihazı. Tarayıcıda tam sayfa `/dev`de pay yoktur.',
|
|
43
|
+
'`edges`: all (dört kenar) · top · bottom · vertical. Tasarım kanvası ve önizleme payı KENDİLİĞİNDEN BIRAKMAZ; mobil sayfanın kökü genellikle `safe-area` (ya da içinde `app-bar`) olmalıdır.',
|
|
44
|
+
'Varsayılan sütun + `stretch` ve kenara yaslı: boşluk tipi yazılmamışsa dış/iç boşluk `none` sayılır (`safe-area`, `scroll-view`, `app-bar` — açıkça seçilen değer uygulanır).'
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
'scroll-view': {
|
|
48
|
+
summary: 'Kaydırılabilir kap; uzun içeriği kaydıran açık bir karardır.',
|
|
49
|
+
differences: [
|
|
50
|
+
'Web`de karşılığı YOKTUR — mobil ekran kendiliğinden kaymaz; `heightType: fill` ile ekranın kalanını kaplatın.',
|
|
51
|
+
'`horizontal` yatay kaydırır; yerleşim alanları içerik kabına uygulanır. Sona varınca `scrollEnd` yayılır.',
|
|
52
|
+
'`refreshable` YALNIZ dikey kaydırmada ve yalnız runtime kipinde çekerek yenileyi açar (`refresh` olayı); tasarım kanvasında jest çizilmez. Barındırıcı (Expo) kendi çekerek yenilesini bu durumda KAPATIR, yoksa iki gösterge üst üste biner.',
|
|
53
|
+
'`stickyHeader` (FlutterFlow StickyHeader) İLK çocuğu kaydırırken üstte tutar — tasarım kanvasında da; yalnız dikey kaydırmada (RN `stickyHeaderIndices`), `horizontal` açıkken yok sayılır ve panel alanı gizler.'
|
|
54
|
+
],
|
|
55
|
+
propNotes: {
|
|
56
|
+
refreshable: 'Çekerek yenile; akış bir söz döndürürse gösterge onu bekler, döndürmezse 600 ms döner.',
|
|
57
|
+
stickyHeader: 'İlk çocuk yapışır: başlık olacak kabı ilk sıraya koyun ve ona kendi zeminini verin — yapışan kap saydamsa altından akan içerik görünür.'
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
text: {
|
|
61
|
+
summary: 'Metin; punto temanın tip ölçeğinden gelir.',
|
|
62
|
+
differences: [
|
|
63
|
+
'`wordBreak` düşer: React Native metni `numberOfLines`/`ellipsizeMode` ile sınırlar, CSS kırma kuralının karşılığı yoktur.',
|
|
64
|
+
"Seviyenin mobil karşılığı varsa (`--ng-font-size-<seviye>-mobile`) o kullanılır; metin stili ATA KAPTAN MİRAS ALINMAZ (RN'de miras yoktur), kabuk tipografiyi bağlama yazar."
|
|
65
|
+
]
|
|
66
|
+
},
|
|
67
|
+
button: {
|
|
68
|
+
summary: "Tıklama kaynağı; shadcn `Button` ilkeli. `kind` semantik rengi, `variant` o rengin kullanımını seçer; mobil kırılım shadcn'in `secondary`/`ghost`/`link` varyantlarını ve `icon` boyunu ekler (web şeması değişmez).",
|
|
69
|
+
differences: [
|
|
70
|
+
'`isNewTabLink` düşer: mobilde sekme yoktur, bağlantı sistem tarayıcısına ya da derin bağlantıya gider (F5/F6).',
|
|
71
|
+
'`isLoading` açıkken metnin yanında `ActivityIndicator` döner.',
|
|
72
|
+
"`icon`, `iconPosition`, `badgeValue` ve `badgeColor` şemada durur ama mobil çizer bugün ÇİZMEZ: düğmede yalnız etiket ve yükleniyor göstergesi vardır.",
|
|
73
|
+
"Etiketin rengi düğmenin kendi kararıdır (`kind`/`variant` kontrastı): ata kabın `typographyStyle.color`ı etikete İNMEZ, punto da temanın form ölçüsünden gelir; aile ve kalınlık gibi öteki tipografi alanları iner."
|
|
74
|
+
],
|
|
75
|
+
propNotes: {
|
|
76
|
+
variant: "shadcn varyantına çevrilir: `solid` → `default` (`kind: error` iken `destructive`) · `outline` → `outline` (`--background` zemin, `--border` kenar) · `text` (web'in adı) ve `ghost` → `ghost` (zeminsiz) · `secondary` → `secondary` (nötr `--secondary` dolgu) · `link` → `link` (zeminsiz, etiket `--primary`). `secondary`, `ghost` ve `link` YALNIZ mobil şemada seçenektir.",
|
|
77
|
+
kind: "Rengin nerede göründüğü varyanta bağlıdır. `solid`: zemin `primary` → `--primary`, `error` → `--destructive`, `info`/`success`/`warning` → `--status-*`. `outline`: `primary` NÖTR kalır (`--border` kenar, ön plan rengi etiket), `error` ve durum renkleri kenarı ve etiketi boyar. `text`: `primary` nötr ön plan etiketi, `error` ve durum renkleri etiketi boyar (`ghost` `primary`/`error`'da aynı). `secondary`: nötr dolgu, `error` etiketi `--destructive` yapar. `link`: etiket `--primary`, `error` iken `--destructive`. BİLİNEN HATA: `info`/`success`/`warning` `ghost`, `secondary` ya da `link` ile verilirse zemin de etiket de aynı durum rengine boyanır ve etiket okunmaz — bu üç varyantta `kind`'ı `primary` ya da `error` bırakın.",
|
|
78
|
+
size: "`sm` → shadcn `sm`, `md` → `default`, `lg` → `lg`, boş (`''`) temanın varsayılanı (`--ng-form-default-size`); bu kademelerde yükseklik, punto ve yatay iç boşluk sınıftan değil temanın `--ng-form-height-*`/`--ng-form-font-size-*`/`--ng-form-padding-x-*` değişkenlerinden gelir. `icon` (yalnız mobil) için temada `--ng-form-*-icon` değişkeni YOKTUR: satır içi yükseklik tanımsız kalır, iç boşluk `spacing.md`'ye, punto taban puntoya düşer ve düğme ikon da çizmez — bu kademe bugün kullanışlı değildir."
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
form: {
|
|
82
|
+
summary: 'Alanları taşıyan kap; yerleşim sözleşmesi `container` ile aynıdır.',
|
|
83
|
+
differences: [
|
|
84
|
+
'`layout` düşer (flex-only).',
|
|
85
|
+
'Doğrulama ve `submit` akışı F5 runtime fazında bağlanır; tasarım kipinde form yalnız alanları dizer.'
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
'form-validation-errors': {
|
|
89
|
+
summary: 'Formun topladığı hataların listesi; renk temanın hata renginden gelir.',
|
|
90
|
+
differences: [
|
|
91
|
+
'Satır metni `label` + `message` birleşiminden çıkar; düz metin dizisi de kabul edilir.',
|
|
92
|
+
'Başlık `hideFormValidationMessage` ile gizlenir; liste ve başlık ikisi de boşsa komponent HİÇ çizilmez (web kutuyu boş çizer).'
|
|
93
|
+
]
|
|
94
|
+
},
|
|
95
|
+
textinput: {
|
|
96
|
+
summary: 'Tek satırlık metin alanı; etiket/açıklama/hata kabuğu bütün alanlarda ortaktır.',
|
|
97
|
+
differences: [
|
|
98
|
+
'`inputMask` düşer: web maskesi DOM olayına bağlıdır, mobil karşılığı (klavye tipi + biçimleyici) Faz 3.',
|
|
99
|
+
'`clearable` düşer: temizleme düğmesi Dalga 1b`in `icon` komponentini bekliyor.',
|
|
100
|
+
'Sayaç kuralı web ile aynı: yalnız `showCounter` açık VE `maxChars` > 0 iken çizilir.'
|
|
101
|
+
]
|
|
102
|
+
},
|
|
103
|
+
textarea: {
|
|
104
|
+
summary: 'Çok satırlı metin alanı.',
|
|
105
|
+
differences: [
|
|
106
|
+
'Yükseklik satır sayısından hesaplanır (`lines`, `minLines`, `maxLines`): React Native `TextInput` satır sayısını kendi ölçmez, yükseklik açıkça verilmelidir.'
|
|
107
|
+
]
|
|
108
|
+
},
|
|
109
|
+
numberinput: {
|
|
110
|
+
summary: 'Sayısal klavye açan alan; değer `Intl.NumberFormat` ile biçimlenir.',
|
|
111
|
+
differences: [
|
|
112
|
+
'Klavye `allowDemicalPoints` açıkken `decimal-pad`, kapalıyken `number-pad`. Yazılan değer ayrıştırılabildiği anda `minValue`/`maxValue` aralığına kırpılarak yazılır.',
|
|
113
|
+
'Web alanı artı/eksi düğmelerini HEP çizer; mobilde düz alan varsayılandır. Düğmeler `variant: stepper` ile gelir — YALNIZ mobil şemada (web şeması değişmez; FlutterFlow Count Controller): alanın iki yanında `-`/`+` (shadcn `outline` `icon`), alana elle yazmak açık kalır; web gibi salt okunur ve görüntüleme kipinde çizilmez, yüklenirken pasiftir. İç roller `<elementId>__decrement` / `<elementId>__increment` — `page_screenshot` `click` onlarla basar.'
|
|
114
|
+
],
|
|
115
|
+
propNotes: {
|
|
116
|
+
variant: "`input` (varsayılan) düz alan; `stepper` ± düğmeli. Basış web `stepBy` gibi değeri `step` kadar değiştirir ve sırayla `change` ile `changed` yayar (`{ value }`): boş değer 0'dan başlar, `step` yoksa ya da pozitif değilse 1; sonuç ve adım alanın GÖSTERDİĞİ basamağa oturur (tam sayı alanında adım en az 1, ondalıkta `decimalPlaces`), yani saklanan değer ekrandakiyle aynıdır; `minValue`/`maxValue`'ya kırpılır. Web kuralıyla `+` yalnız değer üst sınırın altındayken, `-` alt sınırın üstündeyken basılır (aralık dışındaki değerde ters yöne götüren düğme pasif); tasarım kipinde basılamaz."
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
password: {
|
|
120
|
+
summary: 'Gizli metin alanı; göster/gizle düğmesi alanın içindedir.',
|
|
121
|
+
differences: [
|
|
122
|
+
"`autofill` düşer: tarayıcının `autocomplete` sözlüğüdür, mobil karşılığı `textContentType`/`autoComplete` (Faz 3).",
|
|
123
|
+
'Göster/gizle düğmesi şimdilik METİNDİR (`Show`/`Hide`); `icon` Dalga 1b`de inince ikona döner. Kimliği `<elementId>__reveal` — `page_screenshot` `click` onunla basar.'
|
|
124
|
+
]
|
|
125
|
+
},
|
|
126
|
+
checkbox: {
|
|
127
|
+
summary: 'Onay kutusu; shadcn `Checkbox` ilkeli (`@rn-primitives/checkbox`), işaret Material Symbols glifidir. Kutu ölçüsü temanın form puntosundan çıkar.',
|
|
128
|
+
differences: [
|
|
129
|
+
'`isIndeterminate` `value`yu bastırır ve tire (`remove` glifi) çizer; belirsiz kutuya dokunmak onu seçili yapar.',
|
|
130
|
+
'Etikete dokunmak da kutuyu çevirir (basış satırın `Pressable`ında toplanır).',
|
|
131
|
+
'`labelPosition` yalnız `left`/`right`; `left` seçilince etiket ile kutu satırın iki ucuna yaslanır.'
|
|
132
|
+
]
|
|
133
|
+
},
|
|
134
|
+
switch: {
|
|
135
|
+
summary: 'Açma/kapama anahtarı; shadcn `Switch` ilkeli (`@rn-primitives/switch`) — üç barındırıcıda AYNI görünüm.',
|
|
136
|
+
differences: [
|
|
137
|
+
'Platformun kendi anahtarı (iOS/Android `Switch`) KULLANILMAZ: açıkken zemin `--primary`, kapalıyken `--input`, baş `--background`; kanvas, önizleme ve cihaz aynı bileşeni çizer.',
|
|
138
|
+
'`isReadonly` ve `isDisabled` anahtarı kilitler ve soldurur; `isDisabled` etiketi de soldurur. Tasarım kanvasında anahtar basılamaz olduğu için HER anahtar soluk çizilir — önizlemede ve cihazda değil.'
|
|
139
|
+
]
|
|
140
|
+
},
|
|
141
|
+
dropdown: {
|
|
142
|
+
summary: 'Seçim listesi; liste tam ekran `Modal` olarak açılır.',
|
|
143
|
+
differences: [
|
|
144
|
+
'Açılır liste kutunun altına DEĞİL, ekranın altından açılan tam ekran modala çizilir: küçük ekranda kutunun altına açılan liste klavyeyle çakışıyor.',
|
|
145
|
+
'Tetikleyici shadcn `Input` görünümündedir (`--input` kenarı; yükseklik, köşe ve punto öteki alanlarla aynı `--ng-form-*` ölçüsü); liste `--popover` yüzeyinde, seçili satırda `check` glifi. Tetikleyici `role="combobox"` taşır (iç rol `<elementId>__trigger`, temizleme düğmesi `<elementId>__clear`), seçilebilir satırlar `role="option"` + `data-value`; liste katmanda açıldığı için `page_screenshot` `select` seçeneği belgede arar ve değerle ya da görünen metinle seçer.',
|
|
146
|
+
'Seçenekler normalize edilir: düz metin dizisi, nesne dizisi ve `optionType: "grouped"` çocukları tek biçime iner; grup başlığı seçilemez, etiketi olmayan seçenek değerini gösterir.'
|
|
147
|
+
]
|
|
148
|
+
},
|
|
149
|
+
divider: {
|
|
150
|
+
summary: 'Çizgi; etiket verilirse çizgi etiketin iki yanına bölünür.',
|
|
151
|
+
differences: [
|
|
152
|
+
"Kesikli/noktalı desen `borderStyle` ile çizilir (arka planla çizilen düz çizgide desen görünmez); kalınlık kenarlığa yazılır.",
|
|
153
|
+
'Dikey kipte çizgi kabın yüksekliğini alır (`alignSelf: stretch`).'
|
|
154
|
+
]
|
|
155
|
+
},
|
|
156
|
+
spacer: {
|
|
157
|
+
summary: 'Boşluk; `flex` kipinde kalan alanı yer, `fixed` kipinde `size` kadar.',
|
|
158
|
+
differences: [
|
|
159
|
+
'Tasarım kipinde `showInEditor` açıkken kesikli çerçeveyle görünür olur; runtime`da iz bırakmaz (web ile aynı sözleşme).'
|
|
160
|
+
]
|
|
161
|
+
},
|
|
162
|
+
image: {
|
|
163
|
+
summary: 'Görsel; kaynak düz URL ya da platformun dosya nesnesi olabilir.',
|
|
164
|
+
differences: [
|
|
165
|
+
'RN `Image` kullanılır (expo-image değil): paket üç barındırıcıda da aynı kodla çalışır.',
|
|
166
|
+
'Kaynak çözülemezse (boş prop, tanınmayan nesne) YER TUTUCU çizilir — boş kutu tasarımcıya "kaynak boş" demenin tek yolu.',
|
|
167
|
+
'Boyut panelinin en-boy oranı (`appearance.aspectRatio`) görselin KENDİSİNE yazılır (web`de de medya oranı kendi uygular): genişlik satırı kaplar, boy oranla gelir. Koşullar kaplarla aynı: boy `auto` ve genişlik `fill`/`fixed`; değilse (ya da oran yoksa) görsel 96 dp taban boyla çizilir.'
|
|
168
|
+
]
|
|
169
|
+
},
|
|
170
|
+
icon: {
|
|
171
|
+
summary: 'Tek glif; Material Symbols LİGATÜRÜ olarak çizilir.',
|
|
172
|
+
differences: [
|
|
173
|
+
'Glif bir metindir: ad (`home`) yazı tipi tarafından glife çevrilir, ayrı svg seti taşınmaz. Studio sayfası yazı tipini yüklü tutar; Expo uygulaması `expo-font` ile yükler (F6).',
|
|
174
|
+
'`isSpinning` düşer: dönen ikon `Animated` döngüsü ister, v1`de animasyon yok.',
|
|
175
|
+
'`isNewTabLink` düşer (button ile aynı gerekçe).'
|
|
176
|
+
]
|
|
177
|
+
},
|
|
178
|
+
card: {
|
|
179
|
+
summary: 'Görsel + başlık + açıklama + düğme.',
|
|
180
|
+
differences: [
|
|
181
|
+
'`imageFit` RN `resizeMode`a çevrilir: cover→cover, contain→contain, fill→stretch, none→center.',
|
|
182
|
+
'Kartın TAMAMI dokunulabilir ve tek `click` yayar (web ile aynı: düğmenin kendi olayı yoktur); liste satırına dokunmak repeater şablonundaki kartın `click`idir. Kart çocuk ALMAZ — satırın alanlarını `title`/`description` kod bağıyla (`{ type: \'code\', ref: \'{{ item.alan }}\' }`) verin.'
|
|
183
|
+
]
|
|
184
|
+
},
|
|
185
|
+
emptystate: {
|
|
186
|
+
summary: 'Liste boşken çizilen ikon + başlık + açıklama + eylemler.',
|
|
187
|
+
differences: [
|
|
188
|
+
'İkon `icon` komponentiyle aynı ligatür yolunu kullanır.',
|
|
189
|
+
'Metni olmayan eylem kaydı çizilmez; eylem kimlikleri `__action-<sıra>`.'
|
|
190
|
+
]
|
|
191
|
+
},
|
|
192
|
+
slot: {
|
|
193
|
+
summary: 'Layout`un sayfa yuvası; runtime`da sayfanın şeması buraya enjekte edilir.',
|
|
194
|
+
differences: [
|
|
195
|
+
'Tasarım kipinde YER TUTUCU çizilir (kesikli kutu + ad): boş `View` yuvayı görünmez yapıyor, layout`ta yeri kestirilemiyordu.'
|
|
196
|
+
]
|
|
197
|
+
},
|
|
198
|
+
repeater: {
|
|
199
|
+
summary: 'Şablon düğümlerini satır başına tekrarlar; satır kimlikleri `__row-<i>`.',
|
|
200
|
+
differences: [
|
|
201
|
+
'`FlatList` v1`de KULLANILMAZ: sanallaştırma yalnız gerçek veriyle anlamlı, tasarım kipinde ölçüm haritasını bozar (kaplama görünmeyen satırın kutusunu bulamaz).',
|
|
202
|
+
'Tasarım kipinde şablon BİR kez çizilir. Runtime`da her satır web ile AYNI makine parametresiyle (`index`, `row`, `repeaterId`) çizilir: bağlı prop`lar ve akışlar `Row` / `RowIndex` okur; satır yoksa hiçbir şey çizilmez (web ile aynı).',
|
|
203
|
+
'Satırdaki bir düğümün olayı ÖNCE tekrarlayıcıya `change { selectedIndex }` yazar (aynı satırsa yazmaz), sonra kendi akışını koşar (web sırası): akışta tıklanan satır `<tekrarlayıcı>.selectedItem`dır. `item`/`Row`/`RowIndex` yalnız prop BAĞLARINDA satırdır; akış ifadesinde tanımsız döner (ölçüldü 2026-09-19).',
|
|
204
|
+
'`columnCount` > 1 iken satırlar yüzde genişlikle sarılır (RN`de CSS grid yok).',
|
|
205
|
+
'`swipeActions` ve `itemPressable` MOBİLE ÖZGÜDÜR (webde yok). Jest RN `Animated` + `PanResponder` ile kurulur — paket v1`de `reanimated`/`gesture-handler` yok — ve yalnız YATAY harekette yakalanır, böylece dikey kaydırma kaba kalır. Tasarım kipinde satırlar şablon olarak çizildiği için jest hiç bağlanmaz.',
|
|
206
|
+
'`swipe` ve `itemPress` yükleri satırı `row` alanında TAŞIR; akış ifadesindeki `Row`/`RowIndex` bağı ayrı bir konudur (ARC-080).'
|
|
207
|
+
],
|
|
208
|
+
propNotes: {
|
|
209
|
+
itemPressable: 'Satıra dokunulabilir yapar; dokunuş önce `change { selectedIndex }`, sonra `itemPress` yayar.',
|
|
210
|
+
swipeActions: 'Sola kaydırınca açılan eylemler: `key` (zorunlu), `label`, `tone` (default · danger), `icon`. Her eylem 72 pt genişliktedir.'
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
modal: {
|
|
214
|
+
summary: 'Widget kökü; sayfa akışının dışında, üstte açılan katman.',
|
|
215
|
+
differences: [
|
|
216
|
+
'RN `Modal` kullanılır; genişlik web kademelerinden ekran ORANINA çevrilir (small 0.75 · medium 0.85 · large 0.95).',
|
|
217
|
+
'Görünüm temanın shadcn token`larından: perde `--overlay` yarı saydam, gövde `--background` zemin + `--border` kenar + büyük köşe + gölge. Açılış, kapanış ve olaylar değişmedi.',
|
|
218
|
+
'Gövdenin kapatma (×) düğmesinin iç rol kimliği `<elementId>__overlay` (adı tarihsel; `isDismissDisabled` açıkken çizilmez) — `page_screenshot` `click` onunla kapatır.',
|
|
219
|
+
'Tasarım kipinde HER ZAMAN açık ve YERİNDE (sayfa akışında) çizilir: kapalı bir widget`ın içi düzenlenemez; RN `Modal` react-native-web`de sayfanın tamamını kaplayan bir portal açtığı için kanvasta kullanılmaz.',
|
|
220
|
+
'`presentation` YALNIZ mobil şemadadır (web şeması değişmez; FlutterFlow BottomSheet): `center` ortadaki kutu, `sheet` alt sayfa, `fullscreen` tam ekran. Tasarım kipinde üçü de yerinde, kendi görünümüyle çizilir.'
|
|
221
|
+
],
|
|
222
|
+
propNotes: {
|
|
223
|
+
presentation: "`center` (varsayılan; kayıtlı modallar böyle kalır) web ile aynı kutu, genişlik `size`dan. `sheet`: ekranın altına yapışık, tam genişlik, üst köşeleri yuvarlak ve tutamaçlı; cihazda alttan kayarak açılır. Tutamaç + başlık şeridinden aşağı sürükleyince — boyunun üçte biri ya da hızlı savurma — `change { isOpen: false }` ile kapanır; `isDismissDisabled` açıkken yerine döner. İçerik kendi kaydırmasını korur (sürükleme yalnız şeritte); alt sayfa en çok ekranın %90'ıdır, uzun içerik kesilir — kaydırmak için içine `scroll-view` koyun. Kapanış isteği tutmazsa (bir bağ `isOpen`'ı geri yazarsa) sayfa 300 ms sonra yerine döner. `fullscreen`: perdesiz tam ekran, çentik ve ev göstergesi payı dolguda; cihazda kayarak açılır. `size` yalnız `center`da anlamlıdır, panel diğer ikisinde gizler."
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
drawer: {
|
|
227
|
+
summary: 'Mobil ÇEKMECE (D14): expo-router Drawer navigator`ı yerine widget.',
|
|
228
|
+
differences: [
|
|
229
|
+
'RN `Modal` + kenara yaslı panel; kenardan kaydırma ve yumuşak geçiş Faz 3 (reanimated/gesture-handler v1`de paket bağımlılığı değil).',
|
|
230
|
+
'Görünüm temanın shadcn token`larından: perde `--overlay` yarı saydam, panel `--background` zemin + iç kenarda `--border` çizgisi + gölge.',
|
|
231
|
+
'`size` kademeleri ekran oranına iner (default 0.72 … full 1); `position` yalnız left/right.',
|
|
232
|
+
'Kaplamaya dokunuş kapatır (`__overlay` iç rolü), `isDismissDisabled` açıkken dokunuş yutulur.',
|
|
233
|
+
'`app-bar.menuTarget` bu widget`ı açar (`host.shell.openMenu` → makinede `isOpen: true`); kapanış `change { isOpen: false }` yazar, geçişte `open`/`close` yayılır.',
|
|
234
|
+
'Tasarım kipinde yerinde çizilir (modal ile aynı gerekçe).'
|
|
235
|
+
]
|
|
236
|
+
},
|
|
237
|
+
'app-bar': {
|
|
238
|
+
summary: 'Ekranın üst çubuğu: başlık, sol uç (geri ya da çekmece menüsü) ve sağ eylemler.',
|
|
239
|
+
differences: [
|
|
240
|
+
'Web`de karşılığı YOKTUR — mobile özgü şema (web `header` sayfa akışının parçasıdır, bu ekranın kabuğudur).',
|
|
241
|
+
'`leading: auto` runtime`da `host.shell.canGoBack`tan çözülür: geri gidilebiliyorsa geri, gidilemiyorsa `menuTarget` varsa menü, yoksa boş. Tasarım kipinde çubuk boş kalmasın diye menü/geri çizilir.',
|
|
242
|
+
'İç roller `__back`, `__menu`, `__action-<key>` (K2 kalıbı); `page_screenshot` eylemleri bunları kullanır.',
|
|
243
|
+
'Üst güvenli alan payı çubuğun işi DEĞİLDİR: düz kanvas kararından (D15) sonra payı kullanıcı `SafeAreaView` ile kurar.'
|
|
244
|
+
]
|
|
245
|
+
},
|
|
246
|
+
badge: {
|
|
247
|
+
summary: 'Sayaç ya da durum rozeti; taşma (99+) ve nokta kipi var.',
|
|
248
|
+
differences: [
|
|
249
|
+
'`isPulsing` DÜŞÜRÜLDÜ: nabız animasyonu web CSS keyframe`idir, mobilde karşılığı yoktur.',
|
|
250
|
+
'İkon İSTEĞE BAĞLIDIR ve verilmediğinde ÇİZİLMEZ — ortak ikon çözücü boş prop`ta `star`a düştüğü için bu slug prop`un gerçekten verilip verilmediğine kendisi bakar.'
|
|
251
|
+
],
|
|
252
|
+
propNotes: {
|
|
253
|
+
color: 'Renk SINIFTAN gelir (`bg-status-*`); `custom` seçilirse `customColor` ham renktir.',
|
|
254
|
+
maxCount: 'Aşıldığında metin `<maxCount>+` olur (99 → `99+`). Web ikiziyle aynı beş altın vaka `badge-logic.spec.ts`te kilitli.'
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
avatar: {
|
|
258
|
+
summary: 'Kullanıcı avatarı: görsel, baş harf yedeği ve `+N` grup kipi.',
|
|
259
|
+
differences: [
|
|
260
|
+
'Grup kipinde üst üste binme `spacing` ile negatif kenar boşluğundan çizilir; web`in `z-index` katmanı yerine çizim SIRASI belirler.'
|
|
261
|
+
]
|
|
262
|
+
},
|
|
263
|
+
progress: {
|
|
264
|
+
summary: 'Çubuk ilerleme göstergesi; eşiklere göre renk değiştirir.',
|
|
265
|
+
differences: [
|
|
266
|
+
'`variant` (dairesel), `isStriped` ve `isIndeterminate` DÜŞÜRÜLDÜ: üçü de CSS animasyonu ya da `conic-gradient` ister.',
|
|
267
|
+
'Kutu satırın tamamını kaplar (`width: 12`).'
|
|
268
|
+
]
|
|
269
|
+
},
|
|
270
|
+
radio: {
|
|
271
|
+
summary: 'Tek seçimli seçenek listesi.',
|
|
272
|
+
differences: [
|
|
273
|
+
'`autofocus` DÜŞÜRÜLDÜ: mobilde odak kavramı yoktur.',
|
|
274
|
+
'Seçenekler HER ZAMAN alt alta çizilir — dokunma hedefi korunur.',
|
|
275
|
+
'Seçeneğin iç rol kimliği `<elementId>__option-<değer>` (runtime\'da `data-element-id`) — `page_screenshot` `click` seçeneğe onunla basar.'
|
|
276
|
+
]
|
|
277
|
+
},
|
|
278
|
+
checkboxgroup: {
|
|
279
|
+
summary: 'Seçili değerleri bir liste oluşturan onay kutusu grubu.',
|
|
280
|
+
differences: [
|
|
281
|
+
'`autofocus` ve `columnCount` DÜŞÜRÜLDÜ: çok sütun telefon genişliğinde dokunma hedefini bozar; yerine `direction: horizontal` sarmalı kullanılır.',
|
|
282
|
+
'Kutu satırın tamamını kaplar (`width: 12`).',
|
|
283
|
+
'İç roller `<elementId>__option-<değer>` ve (`showSelectAll` açıkken) `<elementId>__select-all` — `page_screenshot` `click` onlarla basar.'
|
|
284
|
+
]
|
|
285
|
+
},
|
|
286
|
+
'choice-chips': {
|
|
287
|
+
summary: 'Dokunma için boyutlandırılmış, satır sarmalı seçilebilir çip dizisi. Web`de karşılığı YOKTUR.',
|
|
288
|
+
differences: [
|
|
289
|
+
'Mobile özgü şema: web`de bu işi `checkboxgroup`/`radio` görür; dokunmatikte çip dizisi daha az yer kaplar.',
|
|
290
|
+
'`multiple` açıkken `value` virgülle ayrılmış listedir, kapalıyken tek değer.',
|
|
291
|
+
'İkon İSTEĞE BAĞLIDIR ve verilmediğinde çizilmez (`badge` ile aynı gerekçe).',
|
|
292
|
+
'Çipin iç rol kimliği `<elementId>__chip-<değer>` — `page_screenshot` `click` çipe onunla basar.'
|
|
293
|
+
]
|
|
294
|
+
},
|
|
295
|
+
'collapsible-container': {
|
|
296
|
+
summary: 'Başlığa dokununca açılıp kapanan kap; çocuklar gövdeye iner.',
|
|
297
|
+
differences: [
|
|
298
|
+
'Bırakılabilir bir KAPTIR (`isDroppable`), gövdesi çocuklardır.',
|
|
299
|
+
'Açılma animasyonu YOKTUR: yükseklik anında değişir (düzen animasyonu bu dalgada kullanılmadı).',
|
|
300
|
+
'Başlığa basmak web ile AYNI olayı yayar (`expanded`/`collapsed { isOpen }`); makine `isOpen`\'ı yazar ve gövde açılıp kapanır. Tetikleyicinin iç rol kimliği `<elementId>__trigger` — `page_screenshot` `click` onunla basar.'
|
|
301
|
+
]
|
|
302
|
+
},
|
|
303
|
+
slider: {
|
|
304
|
+
summary: 'En küçük ve en büyük değer arasında sayısal kaydırıcı.',
|
|
305
|
+
differences: [
|
|
306
|
+
'`marks`, `showTicks` ve `autofocus` DÜŞÜRÜLDÜ.',
|
|
307
|
+
'Jest `PanResponder` ile çözülür ve YALNIZ YATAY sürüklemeyi yakalar: dokunuşu başlangıçta yakalasaydı sayfa kaydırması değeri değiştirirdi (ölçüldü — kaydırma 40`ı 100 yapıyordu).',
|
|
308
|
+
'Sürüklerken `change`, parmak kalkınca `changed` yayılır; akış genelde `changed`e bağlanır.'
|
|
309
|
+
]
|
|
310
|
+
},
|
|
311
|
+
tabs: {
|
|
312
|
+
summary: 'Başlıkların altında içerik gösteren sekme kabı.',
|
|
313
|
+
differences: [
|
|
314
|
+
'İçerik modeli web ile AYNI: her sekmenin gövdesi SIRADAKİ çocuk düğümdür — etkin sekmenin (`selected` anahtarı) `tabs` dizisindeki sırası hangi çocuğun çizileceğini seçer; gizli/pasif sekme sırayı kaydırmaz.',
|
|
315
|
+
'Basılan sekme web ile AYNI olayı yayar (`tab { selected }`); makine `selected`\'ı yazar ve gövde değişir. Sekmenin iç rol kimliği `<elementId>__tab-<anahtar>` (runtime\'da `data-element-id`) — `page_screenshot` `tab` onu anahtarla bulur.',
|
|
316
|
+
'Sekme başlıkları taşarsa yatay kaydırılır; telefonda sarmalanmaz.'
|
|
317
|
+
]
|
|
318
|
+
},
|
|
319
|
+
tooltip: {
|
|
320
|
+
summary: 'DOKUNUŞLA açılan ipucu balonu; çocuklar tetikleyicidir. Web`de karşılığı YOKTUR.',
|
|
321
|
+
differences: [
|
|
322
|
+
'Web`de ipucu hover ile açılır; dokunmatikte hover yoktur (MOB-002), bu yüzden jest DOKUNUŞTUR — ikinci dokunuş kapatır, dışarı dokunmak da kapatır.',
|
|
323
|
+
'Bırakılabilir bir KAPTIR: tetikleyici çocuklarıdır, yani her şeye (düğme, ikon, kart) ipucu takılabilir.',
|
|
324
|
+
'TASARIM KİPİNDE BALON ÇİZİLMEZ: kanvasta açık balon ölçümü ve seçim kutusunu kaydırırdı. Tasarımcı metni panelden düzenler.',
|
|
325
|
+
'Balon sayfa ağacının DIŞINA, barındırıcının katmanına çizilir (`modal`/`drawer` ile aynı çerçeve) — kaydırma kabı ya da kırpan kap onu kesmesin diye.'
|
|
326
|
+
],
|
|
327
|
+
propNotes: {
|
|
328
|
+
side: 'Balonun tetikleyiciye göre yanı. Seçilen yan ekrana sığmazsa balon ÇEVRİLMEZ, yalnız ekran içine kaydırılır.'
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
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
|
+
'app-bar': {
|
|
23
|
+
clickBack: NO_DATA,
|
|
24
|
+
clickMenu: NO_DATA,
|
|
25
|
+
clickAction: { payload: { key: 'string (eylemin key alanı; yoksa dizideki sırası)' } }
|
|
26
|
+
},
|
|
27
|
+
'safe-area': {
|
|
28
|
+
mount: NO_DATA
|
|
29
|
+
},
|
|
30
|
+
'scroll-view': {
|
|
31
|
+
mount: NO_DATA,
|
|
32
|
+
scrollEnd: NO_DATA,
|
|
33
|
+
refresh: NO_DATA
|
|
34
|
+
},
|
|
35
|
+
repeater: {
|
|
36
|
+
swipe: {
|
|
37
|
+
payload: {
|
|
38
|
+
key: 'string (kaydırma eyleminin key alanı)',
|
|
39
|
+
index: 'number (satırın sırası, 0 tabanlı)',
|
|
40
|
+
row: 'object (satırın verisi)'
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
itemPress: {
|
|
44
|
+
payload: {
|
|
45
|
+
index: 'number (satırın sırası, 0 tabanlı)',
|
|
46
|
+
row: 'object (satırın verisi)'
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
@@ -2,6 +2,7 @@ import { logStep } from '../observability/trace.js';
|
|
|
2
2
|
import { PlatformApiError } from '../platform/errors.js';
|
|
3
3
|
import { ensureWorkflowErrorPath } from './errorPath.js';
|
|
4
4
|
import { TYPED_STYLE_COMPONENTS } from './styleResolve.js';
|
|
5
|
+
import { STEP_ALIASES } from './platform/profile.js';
|
|
5
6
|
const SORT_DEFAULT = { name: 'sortField', type: 'sortType', source: 'query-string' };
|
|
6
7
|
const LIMIT_DEFAULT = { name: 'limit', offset: 'offset', source: 'query-string' };
|
|
7
8
|
function asArray(value) {
|
|
@@ -269,12 +270,11 @@ export function normalizeWorkflowDefinition(definition) {
|
|
|
269
270
|
*/
|
|
270
271
|
/**
|
|
271
272
|
* Adım tipi sözlüğü backend'in, KOŞAN tipler barındırıcının: motor yedi tip register eder, web
|
|
272
|
-
* `Runtime.svelte` altı, mobil `
|
|
273
|
+
* `Runtime.svelte` altı, mobil `runtime-tasks.ts` yedi tip ekler ve aynı sayfa kaydı ikisinde de
|
|
273
274
|
* koşar. Register edilmemiş bir tipe gelen akış sessizce ölür (`flow-engine.ts` `process()`
|
|
274
275
|
* callback bulamazsa next/completed/stopped hiç çağrılmaz), üstelik `run()` promise'i
|
|
275
276
|
* çözülmediği için `__ngAppsFlowActivity.running` sıfıra inmez. İki hâl tamamen türetilebilir
|
|
276
|
-
* olduğu için lint değil NORMALİZASYON
|
|
277
|
-
* uygulama tarafında kapanabilir, `FLOW-023` onları uyarır.
|
|
277
|
+
* olduğu için lint değil NORMALİZASYON; `FLOW-023` kalan hâlleri uyarır.
|
|
278
278
|
*/
|
|
279
279
|
function normalizeFlowHostGaps(steps, list) {
|
|
280
280
|
const fixes = [];
|
|
@@ -288,12 +288,15 @@ function normalizeFlowHostGaps(steps, list) {
|
|
|
288
288
|
.map(step => String(step.stepId ?? ''))
|
|
289
289
|
.filter(id => id.length > 0));
|
|
290
290
|
for (const step of list) {
|
|
291
|
-
// navigate ile redirect'in argümanı AYNI ({ path, pageId, query, targetBlank })
|
|
292
|
-
// yalnız mobil register eder; redirect ise iki barındırıcıda da koşar (mobilde
|
|
293
|
-
// uygulamasına eşlenir), yani redirect kesin üstün.
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
291
|
+
// navigate ile redirect'in argümanı AYNI ({ path, pageId, query, targetBlank, presentation })
|
|
292
|
+
// ama navigate'i yalnız mobil register eder; redirect ise iki barındırıcıda da koşar (mobilde
|
|
293
|
+
// aynı navigate uygulamasına eşlenir), yani redirect kesin üstün. `presentation`
|
|
294
|
+
// (push | modal | replace) yalnız mobil barındırıcıda okunur; web yok sayar.
|
|
295
|
+
const alias = STEP_ALIASES[String(step.type ?? '')];
|
|
296
|
+
if (alias) {
|
|
297
|
+
const original = String(step.type);
|
|
298
|
+
step.type = alias;
|
|
299
|
+
fixes.push(`'${label(step)}': type ${original} → ${alias} yazıldı — argüman şekli aynı ({ path, pageId, query, targetBlank, presentation }) ama ${original}'i web barındırıcısı register etmez, ${alias} ikisinde de koşar`);
|
|
297
300
|
}
|
|
298
301
|
if (endStepIds.size === 0)
|
|
299
302
|
continue;
|
|
@@ -650,6 +653,44 @@ export function styleKeyNote(fixed) {
|
|
|
650
653
|
+ 'değer kaydedilir ama hiç uygulanmaz ve tek işareti tarayıcı konsoludur (F-06). '
|
|
651
654
|
+ "Seçici anahtarlara ('&__body', ':hover', '--token') dokunulmadı."];
|
|
652
655
|
}
|
|
656
|
+
/**
|
|
657
|
+
* Kabın `states.isDroppable`ı KATALOĞUN kararıdır. Gateway düğümü kurarken bu alanı
|
|
658
|
+
* yazmıyordu (`DEFAULT_STATES` hepsini `false` bırakır) ve Studio kanvası düğümü kap saymıyordu:
|
|
659
|
+
* imleç `scroll-view`un İÇİNDEYKEN bırakma planı sayfa kabına düşüyor, çizgi kabın üst kenarına
|
|
660
|
+
* çiziliyordu (ölçüldü 2026-09-21, `test/mobile app` › `Satislar`). Aynı tipi paletten ekleyen
|
|
661
|
+
* Studio katalog değerini yazdığı için tek şemada iki türlü davranan kap oluyordu.
|
|
662
|
+
*
|
|
663
|
+
* Değer uygulamanın KENDİ sözlüğünden gelir (`listComponents`; mobil uygulamada `type=mobile`
|
|
664
|
+
* satırları), yani kural platformdan bağımsızdır ve kuruluşun kendi komponentlerini de kapsar.
|
|
665
|
+
* Yalnız `true` yazılır: katalog bir tipi kap saymıyorsa düğüme dokunulmaz.
|
|
666
|
+
*/
|
|
667
|
+
export function normalizeDroppableStates(nodes, droppable) {
|
|
668
|
+
const fixed = [];
|
|
669
|
+
if (droppable.size === 0)
|
|
670
|
+
return fixed;
|
|
671
|
+
for (const node of asObjects(nodes)) {
|
|
672
|
+
const type = typeof node.type === 'string' ? node.type : '';
|
|
673
|
+
if (droppable.get(type) === true) {
|
|
674
|
+
// `states` normalde `normalizeComponentDefaults`tan geçmiş olur; gelmediyse burada da
|
|
675
|
+
// eksiksiz kurulur — yarım `states` platformda NullReference üretir.
|
|
676
|
+
if (!node.states || typeof node.states !== 'object' || Array.isArray(node.states)) {
|
|
677
|
+
node.states = { ...DEFAULT_STATES };
|
|
678
|
+
}
|
|
679
|
+
const states = node.states;
|
|
680
|
+
if (states.isDroppable !== true) {
|
|
681
|
+
states.isDroppable = true;
|
|
682
|
+
fixed.push(`'${String(node.elementId ?? node.name ?? type)}' (${type})`);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
fixed.push(...normalizeDroppableStates(node.children, droppable));
|
|
686
|
+
}
|
|
687
|
+
return fixed;
|
|
688
|
+
}
|
|
689
|
+
export function droppableStatesNote(fixed) {
|
|
690
|
+
return fixed.length === 0
|
|
691
|
+
? []
|
|
692
|
+
: [`states.isDroppable katalogdan yazıldı (${fixed.join(' · ')}) — kataloğun kap saydığı düğüm Studio kanvasında da kap olmalı; alanı bilerek kapatmak için resize_component/set_states kullanın`];
|
|
693
|
+
}
|
|
653
694
|
export function normalizeComponentDefaults(nodes) {
|
|
654
695
|
const fixed = [];
|
|
655
696
|
for (const node of asObjects(nodes)) {
|
|
@@ -884,11 +925,11 @@ export function pageContainerNode() {
|
|
|
884
925
|
* çekilirlerse sayfa içeriğinin akışına girer ve katman olarak açılmazlar.
|
|
885
926
|
*/
|
|
886
927
|
const ROOT_LAYER_TYPES = new Set(['modal', 'drawer']);
|
|
887
|
-
export function ensurePageContainer(schema) {
|
|
928
|
+
export function ensurePageContainer(schema, rootContainer = pageContainerNode) {
|
|
888
929
|
const nodes = asObjects(schema);
|
|
889
930
|
if (nodes.some(node => node.elementId === PAGE_CONTAINER_ELEMENT_ID))
|
|
890
931
|
return { schema: nodes, fixes: [] };
|
|
891
|
-
const container =
|
|
932
|
+
const container = rootContainer();
|
|
892
933
|
const layers = nodes.filter(node => ROOT_LAYER_TYPES.has(String(node.type ?? '')));
|
|
893
934
|
container.children = nodes.filter(node => !ROOT_LAYER_TYPES.has(String(node.type ?? '')));
|
|
894
935
|
const moved = container.children.length;
|
|
@@ -2041,76 +2082,32 @@ export function orphanedFlowPlan(schema, pageEvents, ops) {
|
|
|
2041
2082
|
if (op.op !== 'set_event' && op.op !== 'remove_event')
|
|
2042
2083
|
continue;
|
|
2043
2084
|
const eventName = String(op.event ?? '');
|
|
2044
|
-
const eventValue = typeof op.eventValue === 'string' ? op.eventValue : undefined;
|
|
2045
|
-
const boundTo = (events) => asObjects(events)
|
|
2046
|
-
.filter(event => String(event.name ?? '') === eventName && typeof event.value === 'string' && event.value !== '');
|
|
2047
|
-
// Bir olaya birden fazla akış bağlı olabilir. Değersiz `remove_event` o durumda platformda
|
|
2048
|
-
// `ambiguous-event` ile düşer ve hiçbir bağ kalkmaz; değerli olan yalnız o akışı çözer.
|
|
2049
|
-
const removedBy = (named) => eventValue === undefined
|
|
2050
|
-
? (named.length === 1 ? named : [])
|
|
2051
|
-
: named.filter(event => event.value === eventValue);
|
|
2052
2085
|
// `remove_event target:'page'` düğümü değil barındırıcının düzey olaylarını hedefler
|
|
2053
2086
|
// (`PageSchemaMicroOpApplier.PageTarget`); düğüm aramasına düşseydi plan boş kalır ve yalnız
|
|
2054
2087
|
// oraya bağlı akış kapıdan geçip kalıcı silinirdi. Düzey olayı olmayan widget'ta applier da
|
|
2055
2088
|
// düğüm aramasına düşer.
|
|
2056
2089
|
if (op.op === 'remove_event' && target === 'page' && Array.isArray(pageEvents)) {
|
|
2057
2090
|
const hostEvents = asObjects(pageEvents);
|
|
2058
|
-
const removed =
|
|
2059
|
-
|
|
2091
|
+
const removed = hostEvents.find(event => String(event.name ?? '') === eventName);
|
|
2092
|
+
if (typeof removed?.value !== 'string' || removed.value === '')
|
|
2093
|
+
continue;
|
|
2094
|
+
const surviving = new Set(hostEvents.filter(event => event !== removed && typeof event.value === 'string').map(event => String(event.value)));
|
|
2060
2095
|
collectFlowRefsExcept(nodes, '', surviving);
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
plan.push({ target, op: 'remove_event', flowIds });
|
|
2096
|
+
if (!surviving.has(removed.value))
|
|
2097
|
+
plan.push({ target, op: 'remove_event', flowIds: [removed.value] });
|
|
2064
2098
|
continue;
|
|
2065
2099
|
}
|
|
2066
2100
|
const node = findNodeByElementId(nodes, target);
|
|
2067
|
-
const
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
if (losing.length === 0)
|
|
2101
|
+
const current = asObjects(node?.events).find(event => String(event.name ?? '') === eventName);
|
|
2102
|
+
const currentValue = typeof current?.value === 'string' ? current.value : '';
|
|
2103
|
+
if (currentValue === '' || currentValue === String(op.eventValue ?? ''))
|
|
2071
2104
|
continue;
|
|
2072
2105
|
const surviving = survivingRefs(target, eventName);
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
surviving.add(String(event.value));
|
|
2076
|
-
}
|
|
2077
|
-
const flowIds = [...new Set(losing.map(event => String(event.value)))].filter(flowId => !surviving.has(flowId));
|
|
2078
|
-
if (flowIds.length > 0)
|
|
2079
|
-
plan.push({ target, op: String(op.op), flowIds });
|
|
2106
|
+
if (!surviving.has(currentValue))
|
|
2107
|
+
plan.push({ target, op: String(op.op), flowIds: [currentValue] });
|
|
2080
2108
|
}
|
|
2081
2109
|
return plan;
|
|
2082
2110
|
}
|
|
2083
|
-
/**
|
|
2084
|
-
* PAGE-034 — dolu bir olaya farklı akışla `set_event` gelmesi. `set_event` adın bütün bağlarını
|
|
2085
|
-
* TEK kayda indirir, yani önceki akış bağını sessizce kaybeder; akış başka yerden de
|
|
2086
|
-
* referanslıysa `orphanedFlowPlan` da susar ve kayıp hiç görünmez (maddelerim 4: makale önerisi
|
|
2087
|
-
* akışı, akıllı öneri bağlanınca çalışmaz oldu). Olaya ikinci akış eklemek `add_event`'tir; bilerek
|
|
2088
|
-
* değiştirmek için op'a `replaceBoundFlows: true` verilir. Okunan ağaçta olmayan hedef denetlenmez.
|
|
2089
|
-
*/
|
|
2090
|
-
export function boundEventOps(schema, ops) {
|
|
2091
|
-
const nodes = asObjects(schema);
|
|
2092
|
-
return asObjects(ops)
|
|
2093
|
-
.filter(op => op.op === 'set_event' && op.replaceBoundFlows !== true)
|
|
2094
|
-
.flatMap(op => {
|
|
2095
|
-
const target = String(op.target ?? '');
|
|
2096
|
-
const event = String(op.event ?? '');
|
|
2097
|
-
const eventValue = typeof op.eventValue === 'string' ? op.eventValue : '';
|
|
2098
|
-
const bound = asObjects(findNodeByElementId(nodes, target)?.events)
|
|
2099
|
-
.filter(entry => String(entry.name ?? '') === event && typeof entry.value === 'string' && entry.value !== '')
|
|
2100
|
-
.map(entry => String(entry.value));
|
|
2101
|
-
const replaced = [...new Set(bound)].filter(flowId => flowId !== eventValue);
|
|
2102
|
-
if (replaced.length === 0)
|
|
2103
|
-
return [];
|
|
2104
|
-
return [`PAGE-034: '${target}' komponentinin '${event}' olayına zaten bağlı akış var (${replaced.join(', ')}). set_event olayın BÜTÜN bağlarını tek akışa indirir ve bu akışlar olay tetiklenince artık çalışmaz. `
|
|
2105
|
-
+ `Olaya bir akış daha eklemek için { op: 'add_event', target: '${target}', event: '${event}', eventValue: '${eventValue}' } kullanın — bağlı akışların hepsi paralel koşar. `
|
|
2106
|
-
+ `Mevcut bağı bilerek değiştirmek istiyorsanız aynı op'a replaceBoundFlows: true ekleyin.`];
|
|
2107
|
-
});
|
|
2108
|
-
}
|
|
2109
|
-
/** Gateway'e ait op alanları platforma gitmez. */
|
|
2110
|
-
export function stripGatewayEventFields(ops) {
|
|
2111
|
-
for (const op of ops)
|
|
2112
|
-
delete op.replaceBoundFlows;
|
|
2113
|
-
}
|
|
2114
2111
|
export function orphanedFlowMessage(plan) {
|
|
2115
2112
|
const lines = plan.map(entry => `'${entry.target}' (${entry.op}) → ${entry.flowIds.join(', ')}`);
|
|
2116
2113
|
return `İstek gönderilmedi: bu yama ${plan.reduce((total, entry) => total + entry.flowIds.length, 0)} akışı sayfada referanssız bırakıyor ve platform referanssız akışı KALICI siler — ${lines.join(' · ')}. `
|