@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,215 +0,0 @@
1
- # Mobil uygulama rehberi
2
-
3
- Mobil uygulama (`application_create` → `type: 'mobile'`) web uygulamasıyla **aynı şemayı**, aynı
4
- micro-op sözlüğünü ve aynı akış motorunu kullanır; farkı çizerdedir. Sayfalar
5
- `@ng-apps/ui-native` ile React Native olarak çizilir: Studio'da (kanvas, `/preview`, `/dev|/view`)
6
- react-native-web üzerinde, cihazda Expo uygulamasında. Gateway uygulamanın tipini kendisi okur
7
- (platform profili) ve mobilde çizilmeyecek her yazımı `MOB-*` satırıyla söyler — yazım yine
8
- platforma gider, veri düşürülmez.
9
-
10
- **Sözlük.** Bu rehberde "mobil" yalnız **mobil uygulama** demektir. Web uygulamasının telefondaki
11
- dar ekran kırılımı (`hideOn: 'mobile'`, `breakpoint: mobile`) ayrı bir kavramdır ve
12
- `platform_guide('page')` / `platform_guide('layout')`'ta anlatılır; alan adları değişmedi, yalnız
13
- anlamları ayrı.
14
-
15
- ## Kurulum sırası
16
-
17
- 1. **Uygulama ve kabuk.** Tek çağrıda: `mobile_app_create` (uygulama + sayfalar + kabuk layout'u +
18
- sekme ayarı + ana sayfa bağı, `dryRun` ile önce planı görün). Tekil araçlarla aynı sıra:
19
- `application_create({ type: 'mobile' })` → `page_create` × N → `layout_create` (kabuk) →
20
- `page_update({ layoutId })` × N → `application_update({ homepageId, properties.mobile.tabs })`.
21
- Sekme ayarı sayfa **id**'si ister; bu yüzden sayfalardan SONRA yazılır.
22
- 2. **Veri.** Entity, microservice ve workflow web'dekiyle aynıdır (`platform_guide('microservice')`).
23
- Sayfa akışı `microservice` adımıyla aynı uçları çağırır.
24
- 3. **Ekranlar.** `page_schema_patch` ile bileşen ekle, akış bağla (`flow_create` → `set_event`).
25
- 4. **Doğrulama.** `page_screenshot` (mobilde varsayılan `device: 'phone'`, 390x844 @2) ve
26
- `page_runtime_get({ resolveStyles: true })` (düğümün React Native stili). Ayrıntı §Doğrulama.
27
-
28
- `page_create` mobil uygulamada kökü kendisi kurar: `page_container`, `layout` prop'u YOK,
29
- `direction: 'column'`, `blocks: []`, `appearance.heightType: 'fill'` — kök telefon ekranını doldurur.
30
-
31
- ## Bileşenler
32
-
33
- Mobil uygulamanın bileşen kataloğu ayrıdır (backend satırları `(slug, type: 'mobile')`).
34
- `component_list` ve `component_get` mobil uygulamada **kendiliğinden** mobil kataloğu okur.
35
- `component_get` yanıtı `platform: 'mobile'` ile üç katman taşır: `knowledge` (web kaydı — mobil
36
- şema web sınıfını genişlettiği için prop'ların anlamı oradadır), `mobileKnowledge` (bu bileşenin
37
- mobil farkları: düşen prop, değişen etkileşim, mobile özgü prop notları) ve `mobileNotes` (bütün
38
- mobil bileşenlere geçen notlar). Web'de karşılığı olmayan bileşende (`app-bar`, `safe-area`,
39
- `scroll-view`, `choice-chips`, `tooltip`) bilgi yalnız `mobileKnowledge`'tadır. Olay yükleri
40
- (`events[].payload`) önce mobile özgü sözleşmeden okunur (`app-bar.clickAction`, `repeater.swipe`,
41
- `choice-chips.change`…), yoksa web'inkinden.
42
-
43
- - Katalogda olmayan tip `MOB-003` alır ve backend `unknown-type` (422) ile reddeder. Mobil pakette
44
- olan ama bu uygulamanın kataloğuna yazılmamış tip için gateway katalog senkronunu gösterir; web'de
45
- olup mobilde henüz olmayan için ne zaman geleceğini söyler (Dalga 2, Faz 3).
46
- - **Katalog A** (FlutterFlow paritesi, 2026-09-24) on bileşen ekledi: `badge`, `avatar`, `progress`,
47
- `radio`, `checkboxgroup`, `choice-chips`, `collapsible-container`, `slider`, `tabs`, `tooltip`;
48
- `button` mobilde `secondary`/`ghost`/`link` varyantlarını ve `icon` boyunu alır. Tam liste
49
- `component_list`'tir.
50
- - **Katalog B** (FlutterFlow paritesi) önce dört prop'la başladı; hepsi YALNIZ mobil şemadadır:
51
- `scroll-view.stickyHeader` (ilk çocuk dikey kaydırmada üstte kalır), `numberinput.variant: stepper`
52
- (± düğmeleri, iç roller `__decrement`/`__increment`), `modal.presentation: center | sheet | fullscreen`
53
- (alt sayfa şeritten aşağı sürükleyince `change { isOpen: false }` ile kapanır; en çok ekranın %90'ı,
54
- uzun içerik için içine `scroll-view`). En-boy oranı yeni
55
- prop DEĞİLDİR: boyut panelinin `appearance.aspectRatio`'su mobilde kaplara da uygulanır
56
- (§Yerleşim).
57
- - Web'e özgü bileşenlerin mobil karşılığı:
58
-
59
- | Web | Mobilde |
60
- |---|---|
61
- | `navigations`, `sidebar`, `header`, `layout-shell` | `app-bar` + Ayarlar › Sekmeler + `drawer` widget'ı (`MOB-004`) |
62
- | `popover` | `modal` widget'ı |
63
- | `pagination` | `repeater` + "daha fazla yükle" düğmesi |
64
- | `breadcrumb` | yığın gezinmesi (app-bar geri düğmesi) |
65
- | `iframe` | `button` + `redirect` (`targetBlank`) ile tarayıcıda aç |
66
- | `theme-toggle` | cihaz teması |
67
-
68
- **Harici bileşen mobilde yoktur** (`MOB-006`): protokol v1 tarayıcı `.mjs` paketidir;
69
- `external_component_*` araçları mobil uygulamada platforma gitmez.
70
-
71
- ## Yerleşim — yalnız flex
72
-
73
- Mobil çizer 12 sütunlu ızgarayı **okumaz**: sıra dizi indeksidir, yerleşim flex'tir.
74
-
75
- - `blocks[]`, `set_block`, `layout: 'grid' | 'cssgrid'` yazılır ama çizilmez (`MOB-001`).
76
- `add_component` / `move_component`'ın `index`'i dizideki sıradır; gateway konum çevirisi yapmaz.
77
- - Kaplar flex kabıdır: `direction`, `gap`, `horizontalAlign`, `verticalAlign`, `wrap`, `size`.
78
- Mobil `container` şemasında `layout` prop'u yoktur.
79
- - Ölçü `appearance` ile: `widthType: 'fill' | 'auto' | 'fixed'` (+ `widthString`),
80
- `heightType: 'fill' | 'auto' | 'fixed'` (+ `heightString`). `fill` kalan alanı alır.
81
- - En-boy oranı `appearance.aspectRatio` (sayı; 16:9 için `1.78`): boy genişlikten türer. Koşullar:
82
- `heightType: 'auto'` ve genişlik belli — `widthType: 'fill'` ya da `'fixed'` (içerik genişliğinde
83
- web ile cihaz oranı ters yönden çözer, uygulanmaz). Web'den farkı: mobilde **akış kapları** da oran
84
- alır (`container`, `form`, `safe-area`, `scroll-view`) ve akışları oranlı boyu doldurur; öteki
85
- bırakılabilir tipler, widget örneği, sayfa kökü ve `modal`/`drawer` almaz; `image` oranı kendi
86
- çizimine yazar.
87
- - Hiza (`verticalAlign` `top | middle | bottom`, `horizontalAlign` `left | center | right`) web ile
88
- aynıdır. Dikey hizanın boşluğu kabın boyundan gelir: `heightType` `fill`, `fixed` ya da oranlı kapta
89
- akış kabın boyunu doldurur; boyu içerikten gelen sütun kapta dikey hiza etkisizdir (satır kapta farklı
90
- boydaki kardeşleri yine hizalar). `stretch` de web gibi: ana eksende çocukları yayar, çapraz eksende gerer.
91
- - Birim: `px` ve `%`. `em`/`rem` tema puntosuyla çarpılır; `calc()` ve `vw/vh` React Native'de yoktur
92
- (`MOB-002`).
93
-
94
- ## Görünüm — tipli gruplar
95
-
96
- React Native'de CSS yoktur; tipli stil grupları **derlenir** (web'in `composeTypedStyle` ikizi,
97
- aynı katman sırası: tema taban → gruplar → serbest `style`).
98
-
99
- - `surfaceStyle` → zemin, köşe, kenarlık, gölge (tek katman), opaklık, taşma.
100
- - `typographyStyle` → renk, aile (tek ad), punto, kalınlık, hizalama, satır yüksekliği.
101
- - Tema token'ları (`var(--ng-*)`) sayılara çözülür; proje teması değişince mobil de değişir.
102
-
103
- Çizilmeyen alanlar (`MOB-002`) ve yerine ne:
104
-
105
- | Alan | Yerine |
106
- |---|---|
107
- | `className` | `surfaceStyle` / `typographyStyle` |
108
- | serbest `style` nesnesi (alt seçici `&:hover`, `&__body`) | tipli gruplar; düz anahtarlar (ör. `padding`) çevrilir, alt seçici düşer |
109
- | `hoverStyle` | yok — dokunmatikte üzerine gelme durumu yoktur |
110
- | `tooltip`, `tooltipPosition` | düğümü `tooltip` bileşenine sarın (dokunuşla açılır, yanı `side`) ya da açıklamayı metin olarak koyun |
111
- | `zIndex` | katman için `modal` ya da `drawer` widget'ı |
112
-
113
- Düğümün gerçek stilini görmek: `page_runtime_get({ applicationSlug, pageSlug, resolveStyles: true })`
114
- → her düğümde `nativeStyle: { shell, text, warnings }` (`shell` kutunun, `text` içine akan metnin
115
- stili; RN'de tipografi miras alınmaz). Yanıtta `styleResolver: 'native'`. Temanın
116
- `componentStyles`/preset katmanı mobil çizerde henüz uygulanmıyor ve rapora da girmez.
117
-
118
- ## Görünüm — shadcn/ui
119
-
120
- Bileşenler shadcn/ui görünümünde çizilir (react-native-reusables ilkelleri, NativeWind). Renk ve
121
- köşe yine **proje temasından** gelir: `--ng-*` değişkenleri shadcn token'larına köprüyle çevrilir
122
- (`--primary` ← `accent.primary`, `--destructive` ← hata rengi, `--border`, `--input`, `--ring`…).
123
- `theme_update` web'i ve mobili birlikte değiştirir; koyu kip aynı köprünün ikinci çözümüdür. Ölçü
124
- (alan yüksekliği, punto, iç boşluk) temanın `--ng-form-*` değişkenlerinden gelir. Yazı **ailesi**
125
- köprüde yoktur: temanın ailesi alanlarda, başlıklarda ve sekme çubuğunda uygulanır; `text` ve düğme
126
- etiketi ailesini `typographyStyle`'dan alır, alan etiketleri platform yazı tipiyle çizilir.
127
-
128
- - `button`: `kind` rengi, `variant` kullanımını seçer — `solid` dolu (`kind: error` → shadcn
129
- `destructive`), `outline` çerçeveli, `text`/`ghost` zeminsiz; mobilde ayrıca `secondary` (nötr
130
- dolgu) ve `link`. `info`/`success`/`warning` → temanın `--status-*` renkleri. Eşlemenin tamamı ve
131
- bilinen sınırlar: `component_get('button')` → `mobileKnowledge.propNotes`.
132
- - `switch` platformun kendi anahtarı DEĞİLDİR: üç barındırıcıda (kanvas, önizleme, cihaz) aynı
133
- shadcn anahtarı çizilir; kanvasta basılamadığı için soluk görünür.
134
- - Web sayfası kendi bileşen ailesiyle çizilir; iki platformun görünüm farkı bilinçlidir, ortak olan
135
- temadır. Yakalama seçicileri (`role="button"`, `__tabs__item-<slug>`, `submit` köprüsü) görünümden
136
- bağımsızdır (§Doğrulama).
137
-
138
- ## Gezinme
139
-
140
- - **Yığın varsayılandır; ayar yoktur.** `redirect` adımı hedef sayfayı yığına iter, geri hareketi
141
- (cihaz geri tuşu, app-bar geri düğmesi, tarayıcı geri) önceki ekrana döner. Göreli yol uygulama
142
- köküne eklenir; tam adres (`https://…`) dışarıda açılır.
143
- - **Sekmeler uygulama ayarıdır, layout'ta değil.** `application_update({ properties: { mobile: { tabs } } })`:
144
-
145
- ```json
146
- {
147
- "mobile": {
148
- "tabs": {
149
- "enabled": true,
150
- "style": "default",
151
- "items": [
152
- { "page": "<page_list id>", "label": "Görevler", "icon": "list" },
153
- { "page": "<page_list id>", "label": "Profil", "icon": "user" }
154
- ],
155
- "showLabels": true
156
- }
157
- }
158
- }
159
- ```
160
-
161
- `items` 1–5 sayfa **id**'si (slug değil), tekrar yok; `style: 'floating'` köşe `pill` + gölge `md`
162
- çizer. Çubuğun rengi ayarda YOKTUR: zemin `surface`, seçili `accent-primary`, seçilmemiş
163
- `text-secondary` — değiştirmek için `theme_update`; eski `appearance` alanı okunmaz; `style: 'custom'` + `customWidgetId` çubuğu kendi widget'ınla çizer. Tutarsız ayar ve
164
- layout/sayfada eski `tab-bar` / `drawer-menu` düğümü `MOB-007` alır. Sekme dışı sayfalar yığına açılır.
165
- - **Çekmece bir bileşendir.** `drawer` widget'ı + `app-bar`'ın `menuTarget` prop'u çekmeceyi açar.
166
- - Kabuk örnekleri: `ngapps://examples/mobile-app-shell-stack` (yığın), `ngapps://examples/mobile-app-shell-tab`
167
- (sekmeler), `ngapps://examples/mobile-app-shell-drawer` (çekmece); üç sayfalık liste → detay → form akışı:
168
- `ngapps://examples/mobile-list-detail-form`.
169
-
170
- ## Akış adımları
171
-
172
- Motorun kendi adımları (`setter`, `condition`, `microservice`, `code`, `function`, `flow-trigger`,
173
- `validation`) iki platformda aynıdır. Barındırıcıya dokunan adımlar:
174
-
175
- | Adım | Mobilde |
176
- |---|---|
177
- | `redirect` | yığına iter (göreli yol) ya da dışarıda açar (tam adres) |
178
- | `navigate` | eski mobil şemaların adı — `redirect`'in takma adıdır; gateway `redirect`'e çevirir |
179
- | `alert` | tipli uyarı (`success`/`error`/`info`/`warning`) toast'tur ve beklemez; düz uyarı onay bekler |
180
- | `confirm` | `yes` / `no` dalları |
181
- | `download` | adres cihazın açma sayfasında açılır, gövde paylaşım sayfasına gider (`MOB-005`, bilgi) |
182
- | `storage` | cihaz deposu |
183
- | `setcookie` | cihaz deposuna yazar (`MOB-005`, bilgi); paletten yalnız web'de sunulur |
184
-
185
- Sayfa ve layout `mount` akışları ilk çizimden sonra, `destroy` akışları sayfa kalkarken koşar.
186
- `Functions.<ad>()` uygulama ve sayfa fonksiyonlarını görür (`platform_guide('functions')`).
187
-
188
- ## Doğrulama
189
-
190
- - `page_screenshot({ applicationSlug, pageSlug })` mobil uygulamada `device: 'phone'` (390x844, kare
191
- 780x1688 px) çeker; `'tablet'` 820x1180 @2 ya da `{ width, height, scale? }`. `viewport` verilirse
192
- cihaz yok sayılır (`deviceNote`). Yanıt `applicationType: 'mobile'` ve `platformNote` taşır: kare
193
- react-native-web çizimidir, cihazdakiyle yapısal olarak (düğüm, sıra, ölçü, renk) aynıdır.
194
- - Eylemler aynı sözlük: `click` basılabilir öğeyi (`role="button"`) bulur; `submit` runtime
195
- köprüsüyle gider (`submitVia: 'bridge'`); sekme öğesi `__tabs__item-<sayfa slug'ı>`, çubuk `__tabs`
196
- özet satırıdır. `select` açılır listeyi açar ve seçeneği değerle ya da görünen metinle seçer
197
- (liste katmanda açılır, `optionsSeen` kaç seçenek görüldüğünü söyler); `tab` bir `tabs`
198
- bileşeninin sekmesini anahtarıyla (`<elementId>__tab-<anahtar>`) bulur.
199
- - `flowRuns: 0` + `requests: 0` bir tıklamada akışın koşmadığını söyler: olay bağını
200
- `page_runtime_get`, akışı `flow_get` ile doğrulayın.
201
- - Kuru doğrulama: `page_schema_validate` mobil kataloğa karşı doğrular (`MOB-003`).
202
-
203
- ## Cihazda önizleme
204
-
205
- Expo uygulaması (`apps/mobile-runtime`) aynı sayfayı native çizer. Derin bağlantı
206
- `zeyrek://open/{proje}/{uygulama}/{sayfa}?env=<ortam>` **token taşımaz**: cihazdaki uygulama zaten giriş
207
- yapmış olmalıdır. Canlı önizleme sayfa şeması değişikliğini (`/hubs/page-schema`) dinler; yazdığın
208
- yama cihazda yenilenir.
209
-
210
- ## Bilinen farklar
211
-
212
- - Piksel paritesi yoktur: yazı tipi ölçüsü, gölge ve güvenli alan cihaza göre değişir. Parite yapısaldır.
213
- - Bileşen kataloğu dalga dalga gelir; `component_list` ne döndürüyorsa odur.
214
- - Temanın bileşen stilleri (`componentStyles`, preset'ler) mobil çizerde henüz uygulanmıyor.
215
- - Dar ekran kırılımı (`hideOn: 'mobile'`) mobil uygulamada anlamsızdır: ekran zaten tek boydur.
@@ -1,203 +0,0 @@
1
- import { z } from 'zod';
2
- import { environmentParam, guid, projectParam, writes } from './common.js';
3
- import { fetchHost, pageUpdateBody, unwrap } from './screens.js';
4
- import { fetchCatalogSlugs } from './components.js';
5
- import { mobileTabsWarnings } from './applications.js';
6
- import { PlatformApiError } from '../platform/errors.js';
7
- import { handle } from '../platform/responses.js';
8
- import { ensurePageContainer } from '../knowledge/normalize.js';
9
- import { mobileProfile } from '../knowledge/platform/mobile.js';
10
- import { MAX_MOBILE_TABS } from '../knowledge/platform/mobileTabs.js';
11
- import { forgetApplication } from '../knowledge/platform/resolve.js';
12
- import { readKnowledgeFile } from '../resources/knowledge.js';
13
- /** Ana sayfa: açıkça verilen, yoksa ilk sekme, yoksa ilk sayfa. */
14
- function homepageSlug(input) {
15
- return input.homepage ?? input.tabs?.[0]?.pageSlug ?? input.pages[0].pageSlug;
16
- }
17
- export function mobileAppInputErrors(input) {
18
- const errors = [];
19
- const slugs = input.pages.map(page => page.pageSlug);
20
- const repeated = slugs.filter((slug, index) => slugs.indexOf(slug) !== index);
21
- if (input.pages.length === 0)
22
- errors.push('pages en az bir sayfa ister');
23
- if (repeated.length > 0)
24
- errors.push(`pages içinde tekrar eden slug var: ${[...new Set(repeated)].join(', ')}`);
25
- if (input.tabs !== undefined) {
26
- const tabSlugs = input.tabs.map(tab => tab.pageSlug);
27
- if (input.tabs.length === 0)
28
- errors.push("tabs verildiyse en az bir öğe taşır; sekme istemiyorsan alanı hiç gönderme (yığın)");
29
- if (input.tabs.length > MAX_MOBILE_TABS)
30
- errors.push(`tabs en fazla ${MAX_MOBILE_TABS} öğe taşır (${input.tabs.length} verildi) — fazlası yığına açılan sayfa olarak kalmalı`);
31
- for (const slug of tabSlugs.filter(slug => !slugs.includes(slug)))
32
- errors.push(`tabs '${slug}' pages içinde yok — sekme yalnız kurulan bir sayfayı gösterebilir`);
33
- if (tabSlugs.some((slug, index) => tabSlugs.indexOf(slug) !== index))
34
- errors.push('tabs içinde tekrar eden sayfa var');
35
- }
36
- if (input.homepage !== undefined && !slugs.includes(input.homepage))
37
- errors.push(`homepage '${input.homepage}' pages içinde yok`);
38
- return errors;
39
- }
40
- export function buildMobileAppPlan(input) {
41
- const home = homepageSlug(input);
42
- const others = input.pages.filter(page => page.pageSlug !== home);
43
- return [
44
- { step: 'application_create', tool: 'application_create', summary: "type:'mobile' uygulama; backend ana sayfayı kendisi açar" },
45
- { step: 'layout_create', tool: 'layout_create', summary: `kabuk: app-bar + slot${input.drawer ? ' + drawer widget (app-bar.menuTarget)' : ''}` },
46
- ...others.map((page, index) => ({ step: `page_create#${index + 1}`, tool: 'page_create', summary: `'${page.pageSlug}' (${page.pageName}), layoutId ile` })),
47
- { step: 'page_update#homepage', tool: 'page_update', summary: `backend'in ana sayfası → '${home}' (ad, slug, layoutId)` },
48
- ...(input.tabs ? [{ step: 'application_update', tool: 'application_update', summary: `properties.mobile.tabs: ${input.tabs.map(tab => tab.pageSlug).join(' · ')}` }] : [])
49
- ];
50
- }
51
- /** Kabuk ağacı örnek dosyasından okunur — örnek ile aracın kurduğu layout tek kaynaktır. */
52
- function shellViews(input) {
53
- const example = JSON.parse(readKnowledgeFile('examples', input.drawer ? 'mobile-app-shell-drawer.json' : 'mobile-app-shell-stack.json'));
54
- const views = structuredClone(example.layoutCreate.views);
55
- const appBar = views[0]?.children?.find(node => node.type === 'app-bar');
56
- if (appBar && input.title)
57
- appBar.props.title = { type: 'static', ref: null, data: input.title };
58
- return views;
59
- }
60
- const SHELL_TYPES = ['app-bar', 'slot'];
61
- export function registerMobileTools(server, platform) {
62
- const config = platform.config;
63
- server.registerTool('mobile_app_create', {
64
- title: 'Mobil uygulamayı kabuğuyla kur',
65
- description: "Mobil uygulamayı (type:'mobile'), kabuk layout'unu (app-bar + slot; drawer:true ise çekmece widget'ı), sayfalarını ve isteğe bağlı sekme ayarını (properties.mobile.tabs) TEK çağrıda kurar. Sıra: application_create → layout_create → page_create (sayfalar layoutId ile doğar) → backend'in açtığı ana sayfa seçilen ana sayfaya dönüştürülür (yetim sayfa kalmaz) → application_update (yalnız tabs verildiyse). tabs yoksa gezinme yığındır. dryRun:true planı döner, hiçbir şey yazmaz. Platformda işlem yoktur: hata olursa yanıt completed (kurulanlar), failed (düşen adım) ve remaining (kalanlar) taşır — kalanları tekil araçlarla tamamlayın. Kabuk bileşenleri (app-bar, slot) mobil katalogda yoksa layout atlanır ve uyarı döner. Rehber: platform_guide('mobile').",
66
- inputSchema: {
67
- project: projectParam,
68
- environment: environmentParam,
69
- projectId: guid,
70
- slug: z.string().min(1),
71
- name: z.string().min(1),
72
- color: z.string().optional(),
73
- pages: z.array(z.object({ pageSlug: z.string().min(1), pageName: z.string().min(1) })).min(1),
74
- tabs: z.array(z.object({ pageSlug: z.string().min(1), label: z.string().optional(), icon: z.string().optional() })).optional()
75
- .describe(`1-${MAX_MOBILE_TABS} sekme; her pageSlug pages içinden. Verilmezse sekme çubuğu yok (yığın).`),
76
- drawer: z.boolean().optional().describe("true: layout'a drawer widget'ı + app-bar.menuTarget (çekmece)"),
77
- title: z.string().optional().describe('app-bar başlığı; boşsa her sayfanın adı yazılır'),
78
- homepage: z.string().optional().describe('Ana sayfanın pageSlug\'ı; varsayılan ilk sekme, sekme yoksa ilk sayfa'),
79
- dryRun: z.boolean().optional()
80
- },
81
- annotations: writes
82
- }, handle(config, async ({ project, environment, projectId, slug, name, color, pages, tabs, drawer, title, homepage, dryRun }) => {
83
- const input = { pages, ...(tabs ? { tabs } : {}), ...(drawer ? { drawer } : {}), ...(homepage ? { homepage } : {}), ...(title ? { title } : {}) };
84
- const errors = mobileAppInputErrors(input);
85
- if (errors.length > 0)
86
- throw new PlatformApiError(`mobile_app_create: ${errors.join(' · ')}. Hiçbir şey yazılmadı.`, 400);
87
- const plan = buildMobileAppPlan(input);
88
- if (dryRun)
89
- return { dryRun: true, plan };
90
- const completed = [];
91
- const skipped = new Set();
92
- const warnings = [];
93
- const home = homepageSlug(input);
94
- const pageIds = new Map();
95
- let applicationId;
96
- let homepageId;
97
- let layoutId;
98
- let current = 'application_create';
99
- try {
100
- const created = unwrap(await platform.org('POST', `/applications/${projectId}`, {
101
- environment,
102
- body: { slug, name, type: 'mobile', ...(color ? { color } : {}) }
103
- }));
104
- applicationId = String(created.id ?? '');
105
- homepageId = typeof created.homepageId === 'string'
106
- ? created.homepageId
107
- : String(created.homepage?.id ?? '');
108
- forgetApplication(platform);
109
- if (!applicationId || !homepageId)
110
- throw new PlatformApiError('application_create yanıtı uygulama ya da ana sayfa kimliği taşımıyor — application_get ile kontrol edin.');
111
- completed.push({ step: 'application_create', id: applicationId });
112
- current = 'layout_create';
113
- const catalog = await fetchCatalogSlugs(platform, project, environment, applicationId);
114
- const missing = SHELL_TYPES.filter(type => !catalog.has(type));
115
- if (missing.length > 0) {
116
- skipped.add('layout_create');
117
- warnings.push(`MOB-003 (uyarı) — kabuk bileşenleri mobil katalogda yok (${missing.join(', ')}): layout KURULMADI, sayfalar layout'suz açıldı. Katalog yazıldıktan sonra kabuğu ngapps://examples/mobile-app-shell-${input.drawer ? 'drawer' : input.tabs ? 'tab' : 'stack'} ile layout_create + page_update(layoutId) olarak kurun.`);
118
- }
119
- else {
120
- const layout = unwrap(await platform.tenant('POST', project, `/layouts/${applicationId}`, {
121
- environment,
122
- body: { name: `${name} kabuğu`, views: shellViews(input), events: [], parameters: null }
123
- }));
124
- layoutId = String(layout.id ?? '');
125
- completed.push({ step: 'layout_create', id: layoutId });
126
- }
127
- const others = pages.filter(page => page.pageSlug !== home);
128
- for (const [index, page] of others.entries()) {
129
- current = `page_create#${index + 1}`;
130
- const saved = unwrap(await platform.tenant('POST', project, `/pages/${applicationId}`, {
131
- environment,
132
- body: {
133
- name: page.pageName,
134
- slug: page.pageSlug,
135
- browserTitle: '',
136
- browserTitleKey: '',
137
- layoutId: layoutId ?? null,
138
- roleIds: [],
139
- middlewareIds: [],
140
- events: [],
141
- schema: ensurePageContainer([], mobileProfile.layout.rootContainer).schema
142
- }
143
- }));
144
- pageIds.set(page.pageSlug, String(saved.id ?? ''));
145
- completed.push({ step: current, id: String(saved.id ?? '') });
146
- }
147
- current = 'page_update#homepage';
148
- const homePage = pages.find(page => page.pageSlug === home);
149
- const existing = await fetchHost(platform, 'page', project, environment, applicationId, homepageId);
150
- await platform.tenant('PUT', project, `/pages/${applicationId}/${homepageId}`, {
151
- environment,
152
- body: pageUpdateBody(existing, { name: homePage.pageName, slug: homePage.pageSlug, layoutId: layoutId ?? null })
153
- });
154
- pageIds.set(home, homepageId);
155
- completed.push({ step: current, id: homepageId });
156
- if (tabs) {
157
- current = 'application_update';
158
- const properties = {
159
- mobile: {
160
- tabs: {
161
- enabled: true,
162
- style: 'default',
163
- showLabels: true,
164
- items: tabs.map(tab => ({ page: pageIds.get(tab.pageSlug), ...(tab.label ? { label: tab.label } : {}), ...(tab.icon ? { icon: tab.icon } : {}) }))
165
- }
166
- }
167
- };
168
- warnings.push(...await mobileTabsWarnings(platform, applicationId, platform.resolveProject(project), homepageId, properties));
169
- await platform.org('PUT', `/applications/${applicationId}`, {
170
- environment,
171
- body: { slug, name, homepageId, ...(color ? { color } : {}), properties }
172
- });
173
- forgetApplication(platform, applicationId);
174
- completed.push({ step: current });
175
- }
176
- }
177
- catch (error) {
178
- if (completed.length === 0)
179
- throw error;
180
- const done = new Set(completed.map(entry => entry.step));
181
- return {
182
- applicationId,
183
- ...(layoutId ? { layoutId } : {}),
184
- completed,
185
- failed: { step: current, error: error instanceof Error ? error.message : String(error) },
186
- remaining: plan.map(step => step.step).filter(step => !done.has(step) && !skipped.has(step) && step !== current),
187
- ...(warnings.length > 0 ? { warnings } : {}),
188
- note: 'Kurulum yarıda kaldı: completed kurulanları, remaining eksik adımları söyler. Kalanları tekil araçlarla tamamlayın (page_create, page_update, application_update) — mobile_app_create aynı slug ile tekrar çağrılırsa uygulamayı ikinci kez açmaya çalışır.'
189
- };
190
- }
191
- return {
192
- applicationId,
193
- homepageId,
194
- ...(layoutId ? { layoutId } : {}),
195
- applicationType: 'mobile',
196
- pages: pages.map(page => ({ slug: page.pageSlug, name: page.pageName, id: pageIds.get(page.pageSlug), ...(page.pageSlug === home ? { homepage: true } : {}) })),
197
- ...(tabs ? { tabs: tabs.map(tab => tab.pageSlug) } : {}),
198
- completed,
199
- ...(warnings.length > 0 ? { warnings } : {}),
200
- next: "Ekranları page_schema_patch ile doldurun; doğrulama page_screenshot (mobilde varsayılan device: 'phone'). platform_guide('mobile')."
201
- };
202
- }));
203
- }
@@ -1,45 +0,0 @@
1
- import { findingLines, profileFindings } from '../knowledge/platform/lint.js';
2
- import { resolveProfile } from '../knowledge/platform/resolve.js';
3
- import { fetchCatalogSlugs } from './components.js';
4
- import { flowPath } from './screens.js';
5
- /**
6
- * Sayfa/layout/widget yazma yollarının profil kapısı: mobil profilde `MOB-*` bulgularını
7
- * `layoutWarnings[]` satırı olarak döner. Web profilinde HİÇBİR istek atmaz ve boş dizi döner —
8
- * web yanıtı profil öncesiyle aynı kalır (G1 golden).
9
- */
10
- export async function screenProfileWarnings(platform, resolved, project, environment, applicationId, input) {
11
- if (resolved.profile.platform === 'web')
12
- return [];
13
- const catalog = await fetchCatalogSlugs(platform, project, environment, applicationId).catch(() => undefined);
14
- return findingLines(profileFindings(resolved.profile, { ...input, ...(catalog ? { catalog } : {}) }));
15
- }
16
- /**
17
- * `set_event` ile bağlanan akışların adımları (MOB-005). Akış gövdeleri şemada değil ayrı uçtadır;
18
- * okunamazsa bulgu üretilmez, yazım yine geçer.
19
- */
20
- export async function boundFlowSteps(platform, resolved, project, environment, host, applicationId, hostId, ops) {
21
- if (resolved.profile.platform === 'web')
22
- return [];
23
- const flowIds = new Set(ops.filter(entry => entry.op === 'set_event').map(entry => String(entry.eventValue ?? '')).filter(id => id.length > 0));
24
- if (flowIds.size === 0)
25
- return [];
26
- try {
27
- const hosted = await platform.tenant('GET', project, flowPath(host, applicationId, hostId), { environment });
28
- return (hosted?.data ?? [])
29
- .filter(flow => flowIds.has(String(flow.id ?? '')))
30
- .flatMap(flow => [...(Array.isArray(flow.steps) ? flow.steps : []), ...(Array.isArray(flow.errorSteps) ? flow.errorSteps : [])]);
31
- }
32
- catch {
33
- return [];
34
- }
35
- }
36
- /**
37
- * Akış yazma yollarının profil kapısı (MOB-005): mobil uygulamada `download`/`setcookie` adımının
38
- * davranış farkını `platformWarnings`'e yazar. Web profilinde istek atmaz, boş döner.
39
- */
40
- export async function flowProfileWarnings(platform, applicationId, steps) {
41
- const resolved = await resolveProfile(platform, applicationId);
42
- if (resolved.profile.platform === 'web')
43
- return [];
44
- return findingLines(profileFindings(resolved.profile, { kind: 'page', steps }));
45
- }