@appaflytech/zeyrek-ai-mcp 0.0.24 → 0.0.26

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 (81) hide show
  1. package/.env.example +0 -15
  2. package/dist/config.js +0 -11
  3. package/dist/index.js +0 -2
  4. package/dist/knowledge/componentCatalog.js +25 -13
  5. package/dist/knowledge/componentEvents.js +6 -13
  6. package/dist/knowledge/normalize.js +17 -68
  7. package/dist/knowledge/rules.js +49 -137
  8. package/dist/knowledge/styleResolve.js +4 -50
  9. package/dist/knowledge/themeResolve.js +3 -3
  10. package/dist/observability/trace.js +1 -1
  11. package/dist/platform/diagnostics.js +4 -10
  12. package/dist/platform/domSummary.js +1 -1
  13. package/dist/platform/http.js +7 -23
  14. package/dist/platform/responses.js +2 -6
  15. package/dist/platform/{capture/capture.js → screenshot.js} +514 -52
  16. package/dist/platform/secrets.js +0 -9
  17. package/dist/platform/strictArguments.js +12 -68
  18. package/dist/platform/tokenVault.js +4 -187
  19. package/dist/resources/examples/workflow-http-step.json +2 -2
  20. package/dist/resources/guides/component.md +1 -6
  21. package/dist/resources/guides/expressions.md +2 -2
  22. package/dist/resources/guides/functions.md +1 -1
  23. package/dist/resources/guides/layout.md +7 -11
  24. package/dist/resources/guides/microfunction.md +28 -0
  25. package/dist/resources/guides/microservice.md +4 -10
  26. package/dist/resources/guides/middleware.md +1 -5
  27. package/dist/resources/guides/page.md +55 -215
  28. package/dist/resources/guides/theme.md +1 -4
  29. package/dist/resources/guides/translation.md +1 -1
  30. package/dist/resources/guides/workflow.md +9 -12
  31. package/dist/resources/schema-fingerprint.json +2 -2
  32. package/dist/resources/schemas/CreateWorkflowCommand.json +3 -0
  33. package/dist/resources/schemas/PreviewWorkflowCommand.json +3 -0
  34. package/dist/resources/schemas/StepDto.json +12 -9
  35. package/dist/resources/schemas/UpdateWorkflowCommand.json +3 -0
  36. package/dist/server.js +1 -3
  37. package/dist/tools/applications.js +3 -60
  38. package/dist/tools/components.js +10 -50
  39. package/dist/tools/datasources.js +1 -1
  40. package/dist/tools/designSchemaPatch.js +6 -16
  41. package/dist/tools/externalComponents.js +4 -25
  42. package/dist/tools/flows.js +6 -79
  43. package/dist/tools/layouts.js +12 -44
  44. package/dist/tools/microservices.js +10 -26
  45. package/dist/tools/pages.js +67 -303
  46. package/dist/tools/platformInfo.js +2 -6
  47. package/dist/tools/rows.js +1 -7
  48. package/dist/tools/schemaOps.js +1 -1
  49. package/dist/tools/screenWrite.js +5 -6
  50. package/dist/tools/screens.js +3 -90
  51. package/dist/tools/workflows.js +1 -1
  52. package/package.json +1 -1
  53. package/dist/knowledge/mobileComponentCatalog.js +0 -801
  54. package/dist/knowledge/mobileComponentEvents.js +0 -225
  55. package/dist/knowledge/platform/lint.js +0 -312
  56. package/dist/knowledge/platform/mobile.js +0 -78
  57. package/dist/knowledge/platform/mobileRules.js +0 -182
  58. package/dist/knowledge/platform/mobileTabs.js +0 -108
  59. package/dist/knowledge/platform/profile.js +0 -20
  60. package/dist/knowledge/platform/resolve.js +0 -122
  61. package/dist/knowledge/platform/web.js +0 -38
  62. package/dist/knowledge/styleResolveNative.js +0 -510
  63. package/dist/platform/argumentAliases.js +0 -128
  64. package/dist/platform/bodyFile.js +0 -103
  65. package/dist/platform/capture/actions.js +0 -333
  66. package/dist/platform/capture/browser.js +0 -1
  67. package/dist/platform/capture/console-log.js +0 -80
  68. package/dist/platform/capture/index.js +0 -11
  69. package/dist/platform/capture/readiness.js +0 -103
  70. package/dist/platform/capture/summary.js +0 -77
  71. package/dist/platform/capture/target.js +0 -5
  72. package/dist/platform/capture/targets/studio-mobile.js +0 -15
  73. package/dist/platform/capture/targets/studio-web.js +0 -5
  74. package/dist/platform/schemaReadMemory.js +0 -60
  75. package/dist/resources/examples/mobile-app-shell-drawer.json +0 -209
  76. package/dist/resources/examples/mobile-app-shell-stack.json +0 -123
  77. package/dist/resources/examples/mobile-app-shell-tab.json +0 -174
  78. package/dist/resources/examples/mobile-list-detail-form.json +0 -656
  79. package/dist/resources/guides/mobile.md +0 -419
  80. package/dist/tools/mobile.js +0 -203
  81. package/dist/tools/profileWrite.js +0 -45
@@ -1,419 +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`, `markdown`, `calendar`, `stack`, `list-item`, `fab`, `timer`,
40
- `audio`, `lottie`, `webview`, `map`, `grouped-list`, `floating-container`) bilgi yalnız `mobileKnowledge`'tadır. Olay yükleri
41
- (`events[].payload`) önce mobile özgü sözleşmeden okunur (`app-bar.clickAction`, `repeater.swipe`,
42
- `choice-chips.change`…), yoksa web'inkinden.
43
-
44
- - Katalogda olmayan tip `MOB-003` alır ve backend `unknown-type` (422) ile reddeder. Mobil pakette
45
- olan ama bu uygulamanın kataloğuna yazılmamış tip için gateway katalog senkronunu gösterir; web'de
46
- olup mobilde henüz olmayan için ne zaman geleceğini söyler (Dalga 2, Faz 3).
47
- - **Katalog A** (FlutterFlow paritesi, 2026-09-24) on bileşen ekledi: `badge`, `avatar`, `progress`,
48
- `radio`, `checkboxgroup`, `choice-chips`, `collapsible-container`, `slider`, `tabs`, `tooltip`;
49
- `button` mobilde `secondary`/`ghost`/`link`/`tonal` varyantlarını ve `icon` boyunu alır. Tam liste
50
- `component_list`'tir.
51
- - **Katalog B** (FlutterFlow paritesi) önce dört prop'la başladı; hepsi YALNIZ mobil şemadadır:
52
- `scroll-view.stickyHeader` (ilk çocuk dikey kaydırmada üstte kalır), `numberinput.variant: stepper`
53
- (± düğmeleri, iç roller `__decrement`/`__increment`), `modal.presentation: center | sheet | fullscreen`
54
- (alt sayfa şeritten aşağı sürükleyince `change { isOpen: false }` ile kapanır; en çok ekranın %90'ı,
55
- uzun içerik için içine `scroll-view`). En-boy oranı yeni
56
- prop DEĞİLDİR: boyut panelinin `appearance.aspectRatio`'su mobilde kaplara da uygulanır
57
- (§Yerleşim).
58
- İkinci adımda dört bileşen: `markdown` (Markdown çizer, HTML çalıştırmaz; bağlantı `linkClick { href }`)
59
- ve `calendar` (satır içi takvim, tek gün ya da aralık; prop adları ve `change` biçimi web
60
- `datepicker`ınki) mobile özgüdür; `otpinput` ve `qrcode` web ikizleridir, kuralları web modülünden.
61
- Üçüncü adımda dört bileşen daha: `stack` (katmanlar: ilk çocuk taban, üsttekiler yığının 9 noktalı
62
- `alignment`ıyla hizalanır — çocukta hiza alanı YOKTUR), `list-item` (satır: baş · başlık · alt başlık ·
63
- son; `click`), `fab` (yüzen düğme: sayfanın neresinde olursa olsun ekranın alt köşesinde durur, içerikle
64
- kaymaz; `click`, iç rol `__press`) mobile özgüdür; `carousel` web ikizidir (`activeIndex`,
65
- `slideChange`) ve mobilde bırakılabilir — çocukları sayfalardır.
66
- Dördüncü adım Katalog B'yi kapattı: `timer` (mobile özgü; başlat/duraklat `isRunning`, sıfırlama `value`
67
- yazmak; `tick { value }` her saniye, `finish`), `rating`, `multi-select` (değer `{ label, value }`
68
- dizisi) ve `toast` web ikizleridir. `toast`ın bildirimi sayfadan bağımsız yaşar (`redirect`ten sonra da
69
- görünür), açık modal ya da çekmecenin de üstünde çizilir; akışın tipli `alert` uyarıları cihazda da
70
- toast'tur.
71
- - **Katalog C** (cihaz modüllü bileşenler) medyayla başladı: `video` web ikizidir (cihazda `expo-video`,
72
- kanvas ve önizlemede HTML5 `<video>`; `click` yalnız `controls: false` iken), `audio` (oynat/duraklat,
73
- ilerleme izi; iç roller `__toggle`, `__track`) ve `lottie` (Lottie JSON ya da `.lottie`) mobile özgüdür.
74
- İkisinde çalma `isPlaying` prop'udur: akış `true`/`false` yazar, bileşen `play`/`pause`/`ended` ya da
75
- `finish` olayında `{ isPlaying }` yayar ve prop'u gerçeğe döndürür.
76
- İkinci adım: `signature` web ikizidir (her vuruşta `change { value }` PNG data URL; vuruş sürerken sayfa
77
- kaymaz; iOS 26'da itilmiş sayfada sağa vuruş sayfayı kapatır — platformun geri jesti) ve `webview` mobile özgüdür (web
78
- `iframe`inin karşılığı): `src` ya da `html`; yüklemeden sonra başka belgeye giden gezinme varsayılan olarak
79
- engellenir ve `navigate { url }` yayar, adres yalnız kullanıcının dokunduğu bağlantıda sistem tarayıcısında
80
- açılır (`allowNavigation` görünümde açar); sayfa akışa `window.ReactNativeWebView.postMessage(metin)` →
81
- `message { data }` ile yazar, yüklenemeyen sayfa `error` yayar.
82
- Üçüncü adım: `chart` web ikizidir — seçenek ve olay eşlemesi web'in modülünden gelir, iki platform aynı
83
- ECharts seçeneğini çizer (cihazda SVG); dışa aktarma, tip değiştirici, yenile ve kırıntı araç çubuğu
84
- düşer. `map` mobile özgüdür (cihazda Apple/Google Haritalar, kanvas ve önizlemede Leaflet +
85
- OpenStreetMap): görünüm `latitude`/`longitude`/`zoom` sayılarıdır ve akış yazınca harita oraya gider;
86
- kullanıcı haritayı hareket ettirince `regionChange { latitude, longitude, zoom }` düğüme geri yazılır,
87
- işaretçiye dokunmak `markerClick { markerId }` yayar.
88
- Dördüncü adım: `datepicker`, `timepicker` ve `datetimepicker` web ikizleridir — kutuya dokununca cihazda
89
- işletim sisteminin seçicisi (iOS alttan açılan sayfa, seçim Tamam'la yazılır; Android iletişim kutusu),
90
- önizlemede tarayıcının seçicisi açılır; değer ve `change` yükü web ile aynıdır. Sistem seçicisi tek tek
91
- saati kapatamadığı için `minTime`/`maxTime`/`disabledTimes` dışındaki seçim izinli en yakın saate yaslanır;
92
- `datetimepicker` tek kutudur (`timePlaceholder` yok). `page_screenshot` `set` kökte değil
93
- `<elementId>__input`ta, HTML biçimiyle çalışır (`2026-08-01`, `09:30`, `2026-08-01T09:30`). Tasarım takvim
94
- istiyorsa (Pazartesi başlangıcı, "Bugün / Dün" çipleri, `24.09.2026`): `datepicker` `presentation: calendar` +
95
- `quickPicks` + `displayFormat`; aralık için `daterangepicker` (web ikizi, `valueFrom`/`valueTo`, hep takvim
96
- sayfası). `fileInput` ve `filebutton` de web ikizidir:
97
- değer web gibi base64 veri URL'sidir — bileşen kendisi yüklemez, kayda Table microservice'in FILE
98
- parametresiyle yazılır (§Dosya ve fotoğraf); tür/boyut/adet kapısı ve uyarılar web kuralı.
99
- Seçiciyi mobile özgü `source` seçer — `files` (belge seçici), `library` (fotoğraf arşivi), `camera`,
100
- `ask` (her seferinde sorar; `filebutton`'ın varsayılanı); fotoğraflar `maxImageDimension` (varsayılan
101
- 1600 px uzun kenar) ile küçültülüp `quality` ile sıkıştırılır ve iOS'ta HEIC JPEG'e çevrilir.
102
- Önizlemede tarayıcının dosya penceresi açılır (kanvasta alan kilitli); kırpma yoktur.
103
- - **Liste ve sabit konum** (2026-09-26): `grouped-list` (başlıklı gruplu liste — §Güne göre görev
104
- listesi) ve `floating-container` (ekranın 9 noktasından birine sabit kap — §Ekrana sabit içerik)
105
- mobile özgüdür. `scroll-view`'de ilk çocuk dışındaki doğrudan çocuklar `states.isSticky: true` ile
106
- kaydırırken üstte kalır (Studio panelinde alanı yok; şemaya MCP yazar).
107
- - **Faz 10** (FlutterFlow D listesi): `datatable` web ikizidir — kolon biçimi, sıralama ve sayfa hesabı web'in
108
- modülünden, akış sözleşmesi aynı (sıralama/sayfa değişince `sortField`/`sortType`/`offset` düğüme yazılır ve
109
- `mount` yeniden koşar; satır eylemi `selectedRow`u yazıp eylemin akışını koşturur). Mobile özgü `variant`:
110
- `table` (varsayılan, yatay kaydırılan tablo; başlığa dokunuş `__sort-<kolon>`) ya da `cards` (satır başına
111
- kart). Eylemler `__action-<satır>-<sıra>`, `dropdown` kipinde `__more-<satır>` → alttan eylem sayfası;
112
- sayfa çubuğu `__page-prev`/`__page-next`. Filtre düzenleyicisi, özet satırı ve sabit eylem sütunu yok.
113
- - Web'e özgü bileşenlerin mobil karşılığı:
114
-
115
- | Web | Mobilde |
116
- |---|---|
117
- | `navigations`, `sidebar`, `header`, `layout-shell` | `app-bar` + Ayarlar › Sekmeler + `drawer` widget'ı (`MOB-004`) |
118
- | `popover` | `modal` widget'ı |
119
- | `pagination` | `repeater` + "daha fazla yükle" düğmesi |
120
- | `breadcrumb` | yığın gezinmesi (app-bar geri düğmesi) |
121
- | `iframe` | `webview` (`src` ya da `html`) |
122
- | `theme-toggle` | cihaz teması |
123
- | `fileUpload` | `fileInput` (alan) ya da `filebutton` (düğme) — sürükle-bırak yok |
124
-
125
- **Harici bileşen mobilde yoktur** (`MOB-006`): protokol v1 tarayıcı `.mjs` paketidir;
126
- `external_component_*` araçları mobil uygulamada platforma gitmez. **Geçici yol `html`** (2026-09-28):
127
- katalogda karşılığı olmayan küçük görünüm (özel rozet, SVG, biçimli metin) `html` ile konur — içerik
128
- kendi korumalı belgesinde çizilir (uygulamanın çerezine/jetonuna ulaşamaz), tema `:root` değişkeni
129
- olarak gelir (`hsl(var(--primary))`, `var(--ng-…)`), boy içeriğe göre ayarlanır, bağlantı sistem
130
- tarayıcısında açılır. Akışla iki yön: belgede `fireEvent(key, data)` → `actions` satırının akışı,
131
- `window.ngApps.emit(name, data)` → `message` olayı (`event.name`/`event.data`); akışın `message`
132
- prop'una yazdığı değer belgede `window.ngApps.on(name, fn)` ile alınır. Aynı köprü `webview`de de var.
133
- - **Etkileşim yüzeyleri** (2026-09-27): bileşik kart `container` + `click` ile kurulur (mobile-only olay;
134
- bağlıysa kap runtime'da basılır, içteki düğme kabın `click`ini tetiklemez) — `card` çocuk almaz.
135
- `button` ikonu (`icon` + `iconPosition: left|right|top`) ve rozeti (`badgeValue`, `'•'` nokta) çizer;
136
- `iconPosition: top` + `heightType: fixed` + `variant: tonal` dikey hızlı eylem karosudur. `size: icon`
137
- yalnız glif çizer: `ariaLabel` verin (MOB-013). `icon` `click`/`href` ile basılır, `isDisabled` soldurur.
138
- `modal`/`drawer` `actions` düğme satırı çizer (`action` olayı, `currentItem`; `type: submit` formu
139
- gönderir). Dış bağlantı (`href`) sistem tarayıcısında açılır; uygulama içi yol akışın `redirect`'idir.
140
-
141
- ## Dosya ve fotoğraf
142
-
143
- "Fotoğraf çek → kaydet → listede göster" üç parçadır; yükleme ucu yoktur, dosya kaydın yazıldığı
144
- istekle gider.
145
-
146
- 1. **Tablo.** Entity'ye `dbType: 'FILE'` + `commonType: 'file'` alanı (ör. `foto`; tekil dosya,
147
- `relationId` verilmez — `platform_guide('entity')`).
148
- 2. **Yazma ucu.** Table microservice `operation: 'create'` (ya da `upsert`); `fields`'ta alanı
149
- `{ fieldSlug: 'foto' }` ile listeleyin. Uç gövdede alanın slug'ıyla `type: 'file'` bir parametre
150
- bekler ve değeri `{ type, value, fileName }` biçiminde okur (`TableFileParameter`): `type: 'file'`
151
- base64'ü depoya yazar ve kayda dosya kimliğini koyar, `type: 'ref'` var olan dosyayı (`value` =
152
- dosya id'si) bağlar, `value: null` alanı boşaltır. Dosya alanının DEĞERİ bu biçimdedir — dönüştürmeyin.
153
- 3. **Form sayfası.** `fileInput` (`source: 'camera' | 'library' | 'ask'`) ya da `filebutton` + kaydet
154
- düğmesi. Düğmenin akışında `microservice` adımı, parametre alanın değerini olduğu gibi taşır:
155
-
156
- ```json
157
- { "name": "foto", "type": "file", "source": "body",
158
- "value": { "type": "property", "ref": { "element": "foto_in", "property": "value" } } }
159
- ```
160
-
161
- Base64 gövdeyle gider: fotoğraflar istemcide `maxImageDimension` (1600 px) ile küçültülür,
162
- `maxFileSizeMb` mobilde varsayılan 10'dur — ikisini de sıfırlamayın. Kamera izni reddedilirse alan
163
- Ayarlar'a yönlendiren bir uyarı gösterir.
164
- 4. **Liste.** `get` ucu FILE alanını `{ id, type: 'ref', value: <dosya id>, absolute_path: <url>,
165
- fileName }` olarak döndürür. `image.src`, `avatar.src`, `card.image`, `list-item.leadingImage`'a
166
- **nesnenin kendisini** verin: `{ "type": "code", "ref": "{{ item.foto }}" }` (`.absolute_path` da
167
- çalışır). `{{ item.foto.value }}` dosya **kimliğidir** (GUID), görsel çizilmez — `MOB-011` uyarır.
168
- Gateway alan tipini şemadan bilemediği için uyarı metinseldir: `.value` ile biten en az üç parçalı
169
- yol (`urlInput.value` gibi bileşen değeri ve `fileInput1.value.value` uyarı almaz).
170
-
171
- Workflow'un dosya yükleme adımı yalnız `{ id }` döndürür (URL yok); görsel adresi kaydı okuyan uçtan
172
- gelir. `page_screenshot` ile listede görseli doğrulayın.
173
-
174
- ## Güne göre görev listesi
175
-
176
- Başlıklar altında gruplanmış liste (güne, odaya, kategoriye göre) **tek `grouped-list`** ile kurulur.
177
- İç içe repeater `PAGE-013` ile reddedilir; `textarea` hilesi de gerekmez — liste düz satır dizisidir,
178
- bölümleri bileşen render anında çıkarır.
179
-
180
- 1. **Veri.** Düz satırlar (ör. `q_tasks` sorgusu: `title`, `dueDate`, `done`). Sıralama sorguda
181
- değil bileşende: `sectionOrder` `asc` (varsayılan) · `desc` · `asis`.
182
- 2. **Düğüm.** `rows: { type: 'code', ref: '{{ q_tasks.data }}' }`, `groupBy: 'dueDate'` (noktalı yol
183
- olabilir: `plant.room`). Tarih/saat değeri cihazın yerel gününe (`2026-09-26`) indirgenir; alanı
184
- boş satırlar en sonda `otherSectionTitle` (varsayılan `Other`) bölümündedir.
185
- 3. **Çocuklar konumsaldır:** çocuk 0 bölüm başlığı, çocuk 1 öğe şablonu; fazlası çizilmez.
186
-
187
- ```json
188
- { "elementId": "tasks", "name": "tasks", "type": "grouped-list",
189
- "props": { "rows": { "type": "code", "ref": "{{ q_tasks.data }}" },
190
- "groupBy": { "type": "static", "data": "dueDate" },
191
- "itemPressable": { "type": "static", "data": true } },
192
- "children": [
193
- { "elementId": "day_title", "name": "day_title", "type": "text",
194
- "props": { "content": { "type": "code", "ref": "{{ Section.title }} · {{ Section.count }}" } } },
195
- { "elementId": "task_row", "name": "task_row", "type": "container", "props": {},
196
- "children": [ { "elementId": "task_title", "name": "task_title", "type": "text",
197
- "props": { "content": { "type": "code", "ref": "{{ item.title }}" } } } ] } ] }
198
- ```
199
-
200
- Başlık `Section` okur (`{ key, title, count, items }`), satır kapsamı taşımaz; bölüm dışında
201
- `Section` boş nesnedir (`Row` gibi). Öğe repeater satırıyla aynıdır: `Row`, `RowIndex`,
202
- `{{ item.<alan> }}`; akışta tıklanan satır `tasks.selectedItem`. Şablon çocukları setter hedefi
203
- değildir — satır dışına (sayfadaki bir metne, modala) yazın.
204
- 4. **Olaylar.** `itemPress { index, row }` (yalnız `itemPressable`; öğe şablonu `list-item`/`button`
205
- ise dokunuş onun `click`'ine gider), `swipe { key, index, row }` (`swipeActions`),
206
- `change { selectedIndex }`, `sectionPress` (yük `Section`). `index` `rows` içindeki DÜZ indekstir.
207
- 5. **Yapışkan gün başlığı.** `stickySectionHeaders: true` + `appearance.heightType: 'fixed'` +
208
- `heightString`. Sayfanın kendisi kaydırıcıdır; `auto` ya da `fill` liste içerik kadar uzar ve başlık
209
- yapışmaz. Başlık kabına kendi zeminini verin.
210
-
211
- ## Ekrana sabit içerik — FAB, alt eylem çubuğu, üst şerit
212
-
213
- Mobil şemada `style.position`/`zIndex` yoktur (`MOB-002`); sayfa kayarken yerinde duran içerik iki
214
- bileşenle kurulur. İkisi de sayfanın neresine konursa konsun runtime'da ekran katmanına taşınır —
215
- düğümü sayfa kök kabının SONUNA koyun (akıştaki yeri boş kalır).
216
-
217
- - **Sağ alt tek düğme:** `fab` (`position: bottom-right | bottom-center | bottom-left`, `icon`,
218
- `click`). Sekme çubuğu varsa onun üstünde, kenardan 16 dp.
219
- - **Alt eylem çubuğu / birden çok düğme / üst ya da orta çapa:** `floating-container`.
220
- `anchor` 9 nokta (`top-left` … `bottom-right`, varsayılan `bottom-right`), `offsetX`/`offsetY` (dp,
221
- varsayılan 16, çapalı kenardan içeri), `fullWidth` (alt/üst orta satırı kaplar; `offsetX` yok
222
- sayılır), `respectSafeArea` (varsayılan açık). Alt eylem çubuğu:
223
-
224
- ```json
225
- { "elementId": "action_bar", "name": "action_bar", "type": "floating-container",
226
- "props": { "anchor": { "type": "static", "data": "bottom-center" },
227
- "fullWidth": { "type": "static", "data": true } },
228
- "children": [ { "elementId": "bar", "name": "bar", "type": "container",
229
- "props": { "direction": { "type": "static", "data": "row" } }, "children": [] } ] }
230
- ```
231
-
232
- Kabın kendi yüzeyi yoktur: zemin, köşe, gölge ve iç boşluk içerideki kaba (`surfaceStyle`) verilir.
233
- Dip sekme çubuğunun tepesidir — `aboveTabBar` diye bir prop YOKTUR. App-bar sayfayla kayar; üst
234
- çapalı kap app-bar'ın üstüne biner, sabit app-bar'ın altına `offsetY` ile inilir (ör. 72).
235
- - **Kaydırırken üstte kalan başlık:** `scroll-view` içinde ilk çocuk `stickyHeader: true`, diğer
236
- doğrudan çocuklar düğüm durumu `states: { isSticky: true }` ile (yalnız dikey; kaydırıcıya `fixed`
237
- boy verin). Sayfayı örten katman için `modal` / `drawer` widget'ı.
238
-
239
- `page_screenshot` (viewport karesi, `layoutReport: true`) sabit içeriğin çapalı noktada, sekme çubuğunun
240
- üstünde durduğunu gösterir; uzun sayfada içerik kabın altından akar.
241
-
242
- ## Yerleşim — yalnız flex
243
-
244
- Mobil çizer 12 sütunlu ızgarayı **okumaz**: sıra dizi indeksidir, yerleşim flex'tir.
245
-
246
- - `blocks[]`, `set_block`, `layout: 'grid' | 'cssgrid'` yazılır ama çizilmez (`MOB-001`).
247
- `add_component` / `move_component`'ın `index`'i dizideki sıradır; gateway konum çevirisi yapmaz.
248
- - Kaplar flex kabıdır: `direction`, `gap`, `horizontalAlign`, `verticalAlign`, `wrap`, `size`.
249
- Mobil `container` şemasında `layout` prop'u yoktur.
250
- - Ölçü `appearance` ile: `widthType: 'fill' | 'auto' | 'fixed'` (+ `widthString`),
251
- `heightType: 'fill' | 'auto' | 'fixed'` (+ `heightString`). `fill` kalan alanı alır.
252
- - En-boy oranı `appearance.aspectRatio` (sayı; 16:9 için `1.78`): boy genişlikten türer. Koşullar:
253
- `heightType: 'auto'` ve genişlik belli — `widthType: 'fill'` ya da `'fixed'` (içerik genişliğinde
254
- web ile cihaz oranı ters yönden çözer, uygulanmaz). Web'den farkı: mobilde **akış kapları** da oran
255
- alır (`container`, `form`, `safe-area`, `scroll-view`) ve akışları oranlı boyu doldurur; öteki
256
- bırakılabilir tipler, widget örneği, sayfa kökü ve `modal`/`drawer` almaz; `image` oranı kendi
257
- çizimine yazar.
258
- - `image` boyu: `heightType: 'fixed'` + `heightString` (`px`/`%`) ya da `'fill'` → görsel kabuğu doldurur
259
- (`%` ve `fill`de 96 dp taban boyun altına inmez: atası içerik boyundaki kapta yüzde çözülmez); yoksa `aspectRatio` + `heightType: 'auto'` + belli genişlik → boy orandan; ikisi de yoksa 96 dp taban boy
260
- ve MOB-012 bilgi satırı. Küçük resim için `widthType: 'fixed'` + `widthString` + oran `1` kullanın.
261
- - Hiza (`verticalAlign` `top | middle | bottom`, `horizontalAlign` `left | center | right`) web ile
262
- aynıdır. Dikey hizanın boşluğu kabın boyundan gelir: `heightType` `fill`, `fixed` ya da oranlı kapta
263
- akış kabın boyunu doldurur; boyu içerikten gelen sütun kapta dikey hiza etkisizdir (satır kapta farklı
264
- boydaki kardeşleri yine hizalar). `stretch` de web gibi: ana eksende çocukları yayar, çapraz eksende gerer.
265
- - Birim: `px` ve `%`. `em`/`rem` tema puntosuyla çarpılır; `calc()` ve `vw/vh` React Native'de yoktur
266
- (`MOB-002`).
267
-
268
- ## Görünüm — tipli gruplar
269
-
270
- React Native'de CSS yoktur; tipli stil grupları **derlenir** (web'in `composeTypedStyle` ikizi,
271
- aynı katman sırası: tema taban → gruplar → serbest `style`).
272
-
273
- - `surfaceStyle` → zemin, köşe, kenarlık, gölge (tek katman), opaklık, taşma; döndürme `rotate`
274
- (derece), ölçek `scale` (yüzde, 100 aynı boy), kaydırma `translateX`/`translateY` (px). Sıra web
275
- ile aynı (kaydır → döndür → ölçekle, köken merkez), düğüm çocuklarıyla döner. Değer birimsiz sayıdır
276
- (`'15deg'` yazılmaz); aralık dışı (`rotate` ±360, `scale` 10–500, kaydırma ±1000) yazılmaz ve
277
- `nativeStyle.warnings`'e düşer. Katman ve kabukta (`modal`, `drawer`, `app-bar`) bu dört alan yoktur.
278
- - `typographyStyle` → renk, aile (tek ad), punto, kalınlık, hizalama, satır yüksekliği.
279
- - Tema token'ları (`var(--ng-*)`) sayılara çözülür; proje teması değişince mobil de değişir.
280
-
281
- Çizilmeyen alanlar (`MOB-002`) ve yerine ne:
282
-
283
- | Alan | Yerine |
284
- |---|---|
285
- | `className` | `surfaceStyle` / `typographyStyle` |
286
- | serbest `style` nesnesi (alt seçici `&:hover`, `&__body`) | tipli gruplar; düz anahtarlar (ör. `padding`) çevrilir, alt seçici düşer. İstisna `html`: metin (ya da koda bağlı) `style` kendi belgesinin CSS'idir ve çizilir; statik nesne `style` orada da düşer |
287
- | `hoverStyle` | yok — dokunmatikte üzerine gelme durumu yoktur |
288
- | `tooltip`, `tooltipPosition` | düğümü `tooltip` bileşenine sarın (dokunuşla açılır, yanı `side`) ya da açıklamayı metin olarak koyun |
289
- | `zIndex`, `style.position` | görsel üstüne bindirme (rozet, hap, tarih etiketi, açıklama bandı, arka plan görseli): `stack` — ilk çocuk taban, ötekiler üst katman; her köşe için `fill`×`fill` bir `container` katmanı kendi hizasıyla; görselin altına binen kart: kartın üst boşluğu negatif (`marginValues.top: -28`); ekrana sabit: `floating-container` / `fab`; yapışkan başlık: `scroll-view` çocuğunda `states.isSticky`; örten katman: `modal` / `drawer` (§Ekrana sabit içerik) |
290
- | `surfaceStyle`'ın tanımadığı alt anahtar (ör. CSS `transform` metni, `padding`) | grubun tipli alanları; döndürme/ölçek/kaydırma `rotate`, `scale`, `translateX`/`translateY` |
291
-
292
- Düğümün gerçek stilini görmek: `page_runtime_get({ applicationSlug, pageSlug, resolveStyles: true })`
293
- → her düğümde `nativeStyle: { shell, text, warnings }` (`shell` kutunun, `text` içine akan metnin
294
- stili; RN'de tipografi miras alınmaz). Yanıtta `styleResolver: 'native'`. Temanın
295
- `componentStyles`/preset katmanı mobil çizerde henüz uygulanmıyor ve rapora da girmez.
296
-
297
- ## Görünüm — shadcn/ui
298
-
299
- Bileşenler shadcn/ui görünümünde çizilir (react-native-reusables ilkelleri, NativeWind). Renk ve
300
- köşe yine **proje temasından** gelir: `--ng-*` değişkenleri shadcn token'larına köprüyle çevrilir
301
- (`--primary` ← `accent.primary`, `--destructive` ← hata rengi, `--border`, `--input`, `--ring`…).
302
- `theme_update` web'i ve mobili birlikte değiştirir; koyu kip aynı köprünün ikinci çözümüdür. Ölçü
303
- (alan yüksekliği, punto, iç boşluk) temanın `--ng-form-*` değişkenlerinden gelir. Yazı **ailesi**
304
- köprüde yoktur: temanın ailesi alanlarda, başlıklarda ve sekme çubuğunda uygulanır; `text` ve düğme
305
- etiketi ailesini `typographyStyle`'dan alır, alan etiketleri platform yazı tipiyle çizilir.
306
-
307
- - `button`: `kind` rengi, `variant` kullanımını seçer — `solid` dolu (`kind: error` → shadcn
308
- `destructive`), `outline` çerçeveli, `text`/`ghost` zeminsiz; mobilde ayrıca `secondary` (nötr
309
- dolgu) ve `link`. `info`/`success`/`warning` → temanın `--status-*` renkleri. Eşlemenin tamamı ve
310
- bilinen sınırlar: `component_get('button')` → `mobileKnowledge.propNotes`.
311
- - `switch` platformun kendi anahtarı DEĞİLDİR: üç barındırıcıda (kanvas, önizleme, cihaz) aynı
312
- shadcn anahtarı çizilir; kanvasta basılamadığı için soluk görünür.
313
- - Web sayfası kendi bileşen ailesiyle çizilir; iki platformun görünüm farkı bilinçlidir, ortak olan
314
- temadır. Yakalama seçicileri (`role="button"`, `__tabs__item-<slug>`, `submit` köprüsü) görünümden
315
- bağımsızdır (§Doğrulama).
316
-
317
- ## Gezinme
318
-
319
- - **Yığın varsayılandır; ayar yoktur.** `redirect` adımı hedef sayfayı yığına iter, geri hareketi
320
- (cihaz geri tuşu, app-bar geri düğmesi, tarayıcı geri) önceki ekrana döner. Göreli yol uygulama
321
- köküne eklenir; tam adres (`https://…`) dışarıda açılır.
322
- - **Sekmeler uygulama ayarıdır, layout'ta değil.** `application_update({ properties: { mobile: { tabs } } })`:
323
-
324
- ```json
325
- {
326
- "mobile": {
327
- "tabs": {
328
- "enabled": true,
329
- "style": "default",
330
- "items": [
331
- { "page": "<page_list id>", "label": "Görevler", "icon": "list" },
332
- { "page": "<page_list id>", "label": "Profil", "icon": "user" }
333
- ],
334
- "showLabels": true
335
- }
336
- }
337
- }
338
- ```
339
-
340
- `items` 1–5 sayfa **id**'si (slug değil), tekrar yok; `style: 'floating'` köşe `pill` + gölge `md`
341
- çizer. Çubuğun rengi ayarda YOKTUR: zemin `surface`, seçili `accent-primary`, seçilmemiş
342
- `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
343
- layout/sayfada eski `tab-bar` / `drawer-menu` düğümü `MOB-007` alır. Sekme dışı sayfalar yığına açılır.
344
- - **Çekmece bir bileşendir.** `drawer` widget'ı + `app-bar`'ın `menuTarget` prop'u çekmeceyi açar.
345
- - Kabuk örnekleri: `ngapps://examples/mobile-app-shell-stack` (yığın), `ngapps://examples/mobile-app-shell-tab`
346
- (sekmeler), `ngapps://examples/mobile-app-shell-drawer` (çekmece); üç sayfalık liste → detay → form akışı:
347
- `ngapps://examples/mobile-list-detail-form`.
348
-
349
- ## Çok dil
350
-
351
- Mobil uygulama web ile **aynı sözlüğü ve aynı motoru** kullanır (plans/mobil-coklu-dil, 2026-09-25):
352
- `project_update(languages, defaultLanguage)` → prop'u bağla (`set_prop_type(target, prop, 'translation', ref)`)
353
- → `translation_update`. Kurulum ve fallback zinciri `platform_guide('translation')`.
354
-
355
- - **Dil değiştirici:** `language-selector` mobil katalogdadır (`component_get` → `mobileKnowledge`):
356
- `dropdown`un listesi (az dilde alanın altında, çok dilde alt sayfada), tetikleyici `role="combobox"`,
357
- seçenekler `displayMode`'a göre dilin yerel adıyla (`tr` → Türkçe), koduyla (`TR`) ya da ikisiyle. Seçim `machine.setLanguage` ile bütün prop'ları yerinde çözer ve cihazda uygulama
358
- çapında yayılır: öteki sekmelerin ekranları ve sekme etiketleri de değişir, makine yeniden kurulmaz.
359
- - **Cihazda boot sırası:** uygulama içi seçim → cihazda kayıtlı seçim (`zeyrek:<app>:lang`) → cihaz dili
360
- → projenin varsayılanı. `?lang` CİHAZDA YOKTUR; `/preview`'da vardır, bu yüzden `page_screenshot(language:)`
361
- mobil sayfada da çalışır — kanıt için sayfaya seçici koymak gerekmez.
362
- - **Sekme etiketi:** `properties.mobile.tabs[].label` `t:<anahtar>` ile çevrilir (doğrulama mesajıyla aynı
363
- önek); `mobile_app_create` `tabs[].label` de kabul eder. Sayfa adı fallback'i düz kalır.
364
- - **Çerçeve:** cihazda `alert`/`confirm` düğmeleri (`Tamam`/`Evet`/`Hayır` ↔ `OK`/`Yes`/`No`) uygulama dilini
365
- izler; mesaj gövdesi akışın kendi metnidir.
366
- - **Doğrulama:** `page_screenshot({ language: 'en' })` ikinci dilin karesi;
367
- `page_screenshot({ actions: [{ select: '<seçici elementId>', value: 'en' }] })` seçiciyi sürer
368
- (`optionsSeen` proje dili sayısıdır); seçici sonrası sekme çubuğunun `t:` etiketleri de aynı karede
369
- çevrilir (ölçüldü 2026-09-25: `Ana/Liste`).
370
- - **Bilinen fark:** mobilde istemci tarafı şema doğrulaması yoktur, `t:` doğrulama mesajı akışta
371
- (`Loc()` / `t()`) çözülür.
372
-
373
- ## Akış adımları
374
-
375
- Motorun kendi adımları (`setter`, `condition`, `microservice`, `code`, `function`, `flow-trigger`,
376
- `validation`) iki platformda aynıdır. Barındırıcıya dokunan adımlar:
377
-
378
- | Adım | Mobilde |
379
- |---|---|
380
- | `redirect` | yığına iter (göreli yol) ya da dışarıda açar (tam adres) |
381
- | `navigate` | eski mobil şemaların adı — `redirect`'in takma adıdır; gateway `redirect`'e çevirir |
382
- | `alert` | tipli uyarı (`success`/`error`/`info`/`warning`) toast'tur ve beklemez; düz uyarı onay bekler. Eylem düğmeli bildirim ("Silindi · Geri al") `alert`le kurulmaz: sayfaya bir `toast` bileşeni koyun (`actions: [{ text, flow }]`, gerekirse `keepOpen`), akış setter'la `isOpen: true` yazar; basış eylemin kendi akışını koşar (`actionClick { text, index }`) |
383
- | `confirm` | `yes` / `no` dalları |
384
- | `download` | adres cihazın açma sayfasında açılır, gövde paylaşım sayfasına gider (`MOB-005`, bilgi) |
385
- | `storage` | cihaz deposu |
386
- | `setcookie` | cihaz deposuna yazar (`MOB-005`, bilgi); paletten yalnız web'de sunulur |
387
- | `share` | RN paylaşım sayfası (metin + bağlantı; Android'de bağlantı metne eklenir, `title` iOS'ta e-posta konusu, Android'de seçici başlığı); akış `always` ile sürer — iOS'ta sayfa kapanınca, Android'de seçici açılır açılmaz (RN kapanışı bildirmez). Argümanlar `platform_guide('page')` §`share` adımı |
388
-
389
- Sayfa ve layout `mount` akışları ilk çizimden sonra, `destroy` akışları sayfa kalkarken koşar.
390
- `Functions.<ad>()` uygulama ve sayfa fonksiyonlarını görür (`platform_guide('functions')`).
391
-
392
- ## Doğrulama
393
-
394
- - `page_screenshot({ applicationSlug, pageSlug })` mobil uygulamada `device: 'phone'` (390x844, kare
395
- 780x1688 px) çeker; `'tablet'` 820x1180 @2 ya da `{ width, height, scale? }`. `viewport` verilirse
396
- cihaz yok sayılır (`deviceNote`). Yanıt `applicationType: 'mobile'` ve `platformNote` taşır: kare
397
- react-native-web çizimidir, cihazdakiyle yapısal olarak (düğüm, sıra, ölçü, renk) aynıdır.
398
- - Eylemler aynı sözlük: `click` basılabilir öğeyi (`role="button"`) bulur; `submit` runtime
399
- köprüsüyle gider (`submitVia: 'bridge'`); sekme öğesi `__tabs__item-<sayfa slug'ı>`, çubuk `__tabs`
400
- ö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
401
- (liste katmanda açılır, `optionsSeen` kaç seçenek görüldüğünü söyler); `tab` bir `tabs`
402
- bileşeninin sekmesini anahtarıyla (`<elementId>__tab-<anahtar>`) bulur.
403
- - `flowRuns: 0` + `requests: 0` bir tıklamada akışın koşmadığını söyler: olay bağını
404
- `page_runtime_get`, akışı `flow_get` ile doğrulayın.
405
- - Kuru doğrulama: `page_schema_validate` mobil kataloğa karşı doğrular (`MOB-003`).
406
-
407
- ## Cihazda önizleme
408
-
409
- Expo uygulaması (`apps/mobile-runtime`) aynı sayfayı native çizer. Derin bağlantı
410
- `zeyrek://open/{proje}/{uygulama}/{sayfa}?env=<ortam>` **token taşımaz**: cihazdaki uygulama zaten giriş
411
- yapmış olmalıdır. Canlı önizleme sayfa şeması değişikliğini (`/hubs/page-schema`) dinler; yazdığın
412
- yama cihazda yenilenir.
413
-
414
- ## Bilinen farklar
415
-
416
- - Piksel paritesi yoktur: yazı tipi ölçüsü, gölge ve güvenli alan cihaza göre değişir. Parite yapısaldır.
417
- - Bileşen kataloğu dalga dalga gelir; `component_list` ne döndürüyorsa odur.
418
- - Temanın bileşen stilleri (`componentStyles`, preset'ler) mobil çizerde henüz uygulanmıyor.
419
- - 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). label 't:<anahtar>' ile yazılırsa çeviri sözlüğünden çözülür (platform_guide('mobile') §Çok dil).`),
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
- }