@appaflytech/zeyrek-ai-mcp 0.0.13 → 0.0.15

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.
@@ -2041,32 +2041,76 @@ export function orphanedFlowPlan(schema, pageEvents, ops) {
2041
2041
  if (op.op !== 'set_event' && op.op !== 'remove_event')
2042
2042
  continue;
2043
2043
  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);
2044
2052
  // `remove_event target:'page'` düğümü değil barındırıcının düzey olaylarını hedefler
2045
2053
  // (`PageSchemaMicroOpApplier.PageTarget`); düğüm aramasına düşseydi plan boş kalır ve yalnız
2046
2054
  // oraya bağlı akış kapıdan geçip kalıcı silinirdi. Düzey olayı olmayan widget'ta applier da
2047
2055
  // düğüm aramasına düşer.
2048
2056
  if (op.op === 'remove_event' && target === 'page' && Array.isArray(pageEvents)) {
2049
2057
  const hostEvents = asObjects(pageEvents);
2050
- const removed = hostEvents.find(event => String(event.name ?? '') === eventName);
2051
- if (typeof removed?.value !== 'string' || removed.value === '')
2052
- continue;
2053
- const surviving = new Set(hostEvents.filter(event => event !== removed && typeof event.value === 'string').map(event => String(event.value)));
2058
+ const removed = removedBy(boundTo(hostEvents));
2059
+ const surviving = new Set(hostEvents.filter(event => !removed.includes(event) && typeof event.value === 'string').map(event => String(event.value)));
2054
2060
  collectFlowRefsExcept(nodes, '', surviving);
2055
- if (!surviving.has(removed.value))
2056
- plan.push({ target, op: 'remove_event', flowIds: [removed.value] });
2061
+ const flowIds = [...new Set(removed.map(event => String(event.value)))].filter(flowId => !surviving.has(flowId));
2062
+ if (flowIds.length > 0)
2063
+ plan.push({ target, op: 'remove_event', flowIds });
2057
2064
  continue;
2058
2065
  }
2059
2066
  const node = findNodeByElementId(nodes, target);
2060
- const current = asObjects(node?.events).find(event => String(event.name ?? '') === eventName);
2061
- const currentValue = typeof current?.value === 'string' ? current.value : '';
2062
- if (currentValue === '' || currentValue === String(op.eventValue ?? ''))
2067
+ const named = boundTo(node?.events);
2068
+ // `set_event` adın BÜTÜN bağlarını tek kayda indirir; yeni değerden farklı her akış bağını kaybeder.
2069
+ const losing = op.op === 'set_event' ? named.filter(event => event.value !== eventValue) : removedBy(named);
2070
+ if (losing.length === 0)
2063
2071
  continue;
2064
2072
  const surviving = survivingRefs(target, eventName);
2065
- if (!surviving.has(currentValue))
2066
- plan.push({ target, op: String(op.op), flowIds: [currentValue] });
2073
+ for (const event of named) {
2074
+ if (!losing.includes(event))
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 });
2067
2080
  }
2068
2081
  return plan;
2069
2082
  }
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
+ }
2070
2114
  export function orphanedFlowMessage(plan) {
2071
2115
  const lines = plan.map(entry => `'${entry.target}' (${entry.op}) → ${entry.flowIds.join(', ')}`);
2072
2116
  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(' · ')}. `
@@ -29,6 +29,14 @@ component_get → tek tipin TAM sözleşmesi, üç katman birleşik:
29
29
  - **Boyut:** `blocks[].size` katalogdaki `maxSize`'ı aşamaz; yükseklikte
30
30
  `appearance.heightType: 'auto'` tercih edilir. Katalog varsayılanı bazı tiplerde kendi
31
31
  sınırını aşar — körü körüne kopyalama, gateway kırpar ve bildirir.
32
+ - **Çocuk tarafı flex sözleşmesi — `mode: 'flex'` (2026-09-18):** flex kapta kalan alanı alan
33
+ öğe komponent değil onu taşıyan düğüm sarmalayıcısıdır ve sarmalayıcının `flex`i ortak
34
+ katmanda yazılır. `mode` prop'u `'flex'` olan bir çocuk (bugün yalnız `spacer`) kabın ANA
35
+ ekseninde kalanı ister: sütunda kalan yükseklik, satırda kalan genişlik, sıfır taban (wrap
36
+ açıkken satırdan düşmez). `appearance.widthType`/`heightType`'tan bağımsızdır ve ana
37
+ eksende onları yener; kabın ölçüsü o eksende belirli olmalıdır (auto/hug sütunda kalan
38
+ alan yoktur). Komponentin kendi `style`'ından istenemez — `style`'a `flex` yazma tablosuna
39
+ bkz. aşağıda.
32
40
 
33
41
  ### Arayüz metni ve biçimler uygulama dilini izler
34
42
 
@@ -93,8 +93,9 @@ mümkündür ama doğrulama duvarı ve canlı canvas yalnız `page_schema_patch`
93
93
  komponentin iç kimliğidir (aşağıda Kural 2, `FLOW-018`).
94
94
 
95
95
  - **`blocks[].size`'ı komponentin `component_get`'teki `minSize`..`maxSize` aralığında tut.** Dikkat:
96
- katalog varsayılanı aşabiliyor (`page-header` default `h:8`, `maxSize` `h:4`) — varsayılanı körü
97
- körüne kopyalama. Sınır grid kanvasının kuralıdır; API 2026-09-15'ten beri reddetmez, ama aşan
96
+ katalog varsayılanı aşabiliyor (`datepicker` default `h:8`, `maxSize` `h:5`; 18 şemada aynı
97
+ durum, liste zeyrek-frontend `docs/issues.md` ARC-013 — `page-header` 2026-09-18'de sınırı
98
+ 12'ye açarak çıktı) — varsayılanı körü körüne kopyalama. Sınır grid kanvasının kuralıdır; API 2026-09-15'ten beri reddetmez, ama aşan
98
99
  blok kanvasta boyutlandırılamaz (aşağıda Kural 7 "Boyut tuzağı").
99
100
  - **Kurulmuş bir düğümün boyutunu `resize_component` micro-op'u değiştirir.**
100
101
  `{ op: 'resize_component', target: '<elementId>', size: { w, h } }` ızgara boyutunu,
@@ -159,19 +160,32 @@ Sayfa açılışında veri çekmek için `mount` kullanılır ama tek seçenek o
159
160
 
160
161
  Yani "satırı seçince detay getir" (`select`), "kolondan süzünce sunucudan yeniden çek"
161
162
  (`filter`), "sayfa değişince yeni sayfayı çek" (`pagination`), "satıra çift tıklayınca formu aç"
162
- (`doubleClick`) akışları da aynı `set_event` mekanizmasıyla kurulur. Buna karşılık `page-header`
163
- ve `datatable` yalnız `mount` yayar (`datatable`'ınki bilinçli — Kural 1e).
163
+ (`doubleClick`) akışları da aynı `set_event` mekanizmasıyla kurulur. Buna karşılık `datatable`
164
+ yalnız `mount` yayar (bilinçli — Kural 1e); `page-header` 2026-09-18'den beri izdeki öğe
165
+ tıklamasını `clickItem` olarak yayar (`component_get` yükü verir).
164
166
 
165
167
  **Olay adları `on` ÖNEKİ ALMAZ** (`button` → `click`, `form` → `submit`), tek istisna katalogda
166
168
  `texteditor`'dur (`onMount`/`onChange`). **Komponentin desteklemediği bir olay adı verirsen bağ
167
169
  sessizce ölü kalır** — önce `component_get`. Gateway `set_event`'i katalogla karşılaştırır ve
168
170
  uymayan adı **PAGE-008** ile istek gitmeden reddeder.
169
171
 
172
+ **Bir olaya birden fazla akış bağlanabilir — ikinciyi `add_event` ile ekle.** `set_event` olayın
173
+ BÜTÜN bağlarını tek akışa indirir: başlık alanının `change` olayına makale önerisi bağlıyken akıllı
174
+ öneriyi `set_event` ile bağlamak makale önerisini devre dışı bırakır. Gateway bunu **PAGE-034** ile
175
+ istek gitmeden reddeder. Doğru op `{ "op": "add_event", "target": "<elementId>", "event": "change",
176
+ "eventValue": "<ikinci flow id>" }`'dir. Olay tetiklenince bağlı akışların hepsi **paralel ve
177
+ birbirinden bağımsız** koşar: biri hata verirse diğeri etkilenmez, **sıra garantisi yoktur**. Biri
178
+ diğerinin sonucunu bekliyorsa ya da ikisi aynı değişkene/prop'a yazıyorsa ayrı bağ değil,
179
+ `flow-trigger` adımıyla zincir kur (Kural 1c). Mevcut bağı bilerek değiştirmek için `set_event`
180
+ op'una `replaceBoundFlows: true` ekle. **Mobil runtime yalnız ilk akışı koşar** — mobil
181
+ uygulamada olay başına tek akış bağla.
182
+
170
183
  **Olay KALDIRMAK için `remove_event` kullan.** Platformun `set_event` micro-op'u olay silemez:
171
184
  `eventValue: null` verildiğinde kayıt `{ name: 'click' }` olarak (değersiz) kalır, diziden
172
185
  çıkmaz — şemada ölü bağ artığı bırakır. `{ "op": "remove_event", "target": "<elementId>",
173
186
  "event": "click" }` platformun op'udur: olayı diziden çıkarır (yanıtta `removedEvents`); olmayan
174
- bir olayı kaldırmaya çalışmak `missing-event` ile 422 döner. Sayfa düzeyi olaylar için
187
+ bir olayı kaldırmaya çalışmak `missing-event` ile 422 döner. Olaya birden fazla akış bağlıysa
188
+ kaldırılacak akışı `eventValue` ile ver; vermezsen `ambiguous-event` ile 422 döner. Sayfa düzeyi olaylar için
175
189
  `target: "page"`. Referanssız kalan akış silineceği için `allowFlowDeletion` kuralı (G-21) bu op'ta
176
190
  da geçerlidir.
177
191
 
@@ -185,12 +199,14 @@ olaylarında **boştur** (ölçüm 2026-08-25, kaynak okuması: `flow-machine.ts
185
199
  Pratik kural: **`payload` anahtarları aynı zamanda prop adlarıdır; akışta `self.<anahtar>` ile
186
200
  oku** (ya da `value { type:'property', ref:{ source:'component', element, property } }` ile).
187
201
  `event.data` yalnız `params` ile gelen olaylarda dolar — `html` komponentinin `fireEvent`
188
- aksiyonları (`platform_guide('html')`) ve **2026-08-27 kuşağı komponentler** (`eventcalendar` ·
189
- `importwizard` · `kanban` · `signature` · `stepper`): bu kuşak `params`'ı da gönderir, yani
190
- yükleri `event.data.<anahtar>` ile okunur; `kanban` ve `eventcalendar` yükü `self`'e HİÇ
191
- yazmaz (`component_get` çıktısındaki sözleşme notları ayrımı söyler). Sessiz hata örneği:
192
- sayfalama akışını `event.data.limit`'e bağlarsan sorgu `LIMIT null OFFSET null` ile koşar ve
193
- **tüm satırları** döndürür; hata yok, sayfalama yok.
202
+ aksiyonları (`platform_guide('html')`) ve **`params`'ı da gönderen komponentler**
203
+ (`eventcalendar` · `importwizard` · `kanban` · `signature` · `stepper` · `tabs` ·
204
+ `tree-view` · `pagination`): bunların yükü `event.data.<anahtar>` ile de okunur; `kanban` ve
205
+ `eventcalendar` yükü `self`'e HİÇ yazmaz (`component_get` çıktısındaki sözleşme notları ayrımı
206
+ söyler). Sessiz hata örneği: **tablonun kendi** sayfalama akışını (`table.pagination`)
207
+ `event.data.limit`'e bağlarsan sorgu `LIMIT null OFFSET null` ile koşar ve **tüm satırları**
208
+ döndürür; hata yok, sayfalama yok. (Bağımsız `pagination` komponenti bu tuzağa girmez, yükünü
209
+ `params` ile de gönderir.)
194
210
 
195
211
  | Komponent · olay | Yük (aynı zamanda prop adları) | Akışta nereden okunur |
196
212
  |---|---|---|
@@ -200,16 +216,18 @@ sayfalama akışını `event.data.limit`'e bağlarsan sorgu `LIMIT null OFFSET n
200
216
  | `table.select` · `doubleClick` | — | `self.selectedRow` |
201
217
  | `table.selectionChange` | — | `self.selectedRows` |
202
218
  | `datatable.mount` | — | `self.filters` · `self.sortField` · `self.sortType` · `self.offset` |
203
- | `pagination.change` | `{ offset, limit }` | `self.offset` · `self.limit` |
219
+ | `pagination.change` | `{ offset, limit }` | `self.offset` · `self.limit` ya da `event.data.offset` · `event.data.limit` |
204
220
  | `textinput/textarea/password.change` | `{ value }` | `self.value` |
205
221
  | `dropdown.change` · `multi-select.change` | `{ value }` | `self.value` |
206
222
  | `dropdown.search` · `multi-select.search` | `{ searchText }` | `self.searchText` |
207
- | `tabs.tab` | `{ selected }` | `self.selected` |
223
+ | `tabs.tab` | `{ selected }` | `self.selected` ya da `event.data.selected` |
208
224
  | `chart.dataPointClick` | `{ name, seriesName, value, dataIndex, row }` | `self.row` (kaynak satırın tamamı) |
209
- | `repeater.change` | `{ selectedIndex }` | `item` / `Row` satır bağlamı |
225
+ | `repeater.change` · `repeater.select` | `{ selectedIndex }` | `self.selectedIndex` · `self.selectedItem` (akış satır bağlamında koşmaz, `item`/`Row` boş) |
210
226
  | `html` aksiyonu (`fireEvent`) | ikinci argüman | **`event.data`** |
211
227
  | `kanban.cardMove` | `{ id, row, from, to }` | **`event.data`** (self'e yazılmaz) |
228
+ | `kanban.cardAdd` · `kanban.cardRemove` | `{ column }` · `{ id, row, column }` | **`event.data`** (self'e yazılmaz; kart eklenmez/gizlenmez, akış yazıp `rows`'u tazeler) |
212
229
  | `eventcalendar.eventMove` | `{ id, row, start, end, previousStart, previousEnd }` | **`event.data`** (self'e yazılmaz) |
230
+ | `eventcalendar.monthChange` · `eventcalendar.viewChange` · `eventcalendar.eventCreate` | `{ month, year, view, start, end }` · `{ view, start, end }` · `{ start, end, allDay }` | **`event.data`** (self'e yazılmaz; veri sorgusunu kesişimle bağla: başlangıç < `end`+1 gün ve bitiş >= `start`) |
213
231
  | `importwizard.complete` | `{ rows, errors, fileName, total }` | `self.rows`… ya da `event.data` |
214
232
  | `stepper.stepChange` | `{ currentStep, previous }` | `self.currentStep` ya da `event.data` |
215
233
  | `signature.change` | `{ value }` | `self.value` ya da `event.data.value` |
@@ -1813,7 +1831,7 @@ bağlanır, filtre `source:'expression'` + `value:'user.id'` olur.
1813
1831
  | Kimlik alanları | `email` · `password` |
1814
1832
  | Dosya | `fileUpload` (dropzone) · `fileInput` · `filebutton` · `file-viewer` |
1815
1833
  | Liste | `table` · `datatable` (Kural 1e) · `repeater` (kart listesi VE dinamik form — Kural 1g) · `tree-view` (hiyerarşi; `isSearchable` ile istemci içi arama) · `timeline` (tarihli olay akışı) |
1816
- | Pano / takvim | `kanban` (durum kolonlarında sürüklenebilir kartlar; taşıma `cardMove` ile `event.data`'dan okunur, kalıcılık AKIŞIN işidir) · `eventcalendar` (ay/hafta/gün; veri `rows`'a akışla yazılır, `monthChange` tazeleme kancasıdır, `month` 1-tabanlı) |
1834
+ | Pano / takvim | `kanban` (durum kolonlarında sürüklenebilir kartlar; taşıma `cardMove` ile `event.data`'dan okunur, kalıcılık AKIŞIN işidir) · `eventcalendar` (ay/hafta/gün/ajanda; veri `rows`'a akışla yazılır, `monthChange`/`viewChange` görünen `start`/`end` aralığıyla tazeleme kancasıdır) |
1817
1835
  | Kayıt detayı | `detailview` — tek kaydı etiket/değer ızgarası basar (alan başına format, iç içe alan yolu); OLAY YAYMAZ, tıklanabilirlik gerekiyorsa kapsayıcıya sar |
1818
1836
  | Adımlı akış | `stepper` — tabs gibi her adım kendi droppable görünümünü taşır; ileri gidiş `canProceed` kapısıyla (`{{ form1.isValid === true }}` biçimi), adım `currentStep` setter'ıyla sürülür |
1819
1837
  | İmza / CSV içe aktarma | `signature` (değer PNG data-URL ya da ham SVG; `change` setter'lı) · `importwizard` (3 adımlı sihirbaz; temiz+hatalı satırlar `complete` yükünde — satırları uca yazmak akışın işidir) |
@@ -1,4 +1,4 @@
1
- import { orphanedFlowWarnings, orphanedFlowPlan, orphanedFlowMessage, staleSetterWarnings, normalizeSchemaOps, validationNote, componentDefaultsNote, replacePropNote, flexSizeWarnings, nestedRepeaterNodes, nestedRepeaterMessage, stringStyleNodes, stringStyleWarning, inertFlexPropNodes, inertFlexPropWarning, verticalGapWarnings, withNormalization, FLEX_LAYOUT_PROP_NAMES } from '../knowledge/normalize.js';
1
+ import { orphanedFlowWarnings, orphanedFlowPlan, orphanedFlowMessage, boundEventOps, stripGatewayEventFields, staleSetterWarnings, normalizeSchemaOps, validationNote, componentDefaultsNote, replacePropNote, flexSizeWarnings, nestedRepeaterNodes, nestedRepeaterMessage, stringStyleNodes, stringStyleWarning, inertFlexPropNodes, inertFlexPropWarning, verticalGapWarnings, withNormalization, FLEX_LAYOUT_PROP_NAMES } from '../knowledge/normalize.js';
2
2
  import { opsParam, writeViewParam, outlineWriteResponse, unknownEventOps, collectTypes, simulateLayoutOps, removeNodeFromTree, planEventRemovals, planResizes, planPropTypes, assertOpBodies } from './schemaOps.js';
3
3
  import { guid, projectParam, environmentParam, updates, microOps } from './common.js';
4
4
  import { fetchHost, hostPath } from './screens.js';
@@ -68,7 +68,10 @@ export function registerDesignSchemaPatchTool(server, platform, host) {
68
68
  }
69
69
  const nodes = (record[spec.tree] ?? []);
70
70
  const events = host === 'layout' ? record.events : undefined;
71
- if (opRecords.some(entry => entry.op === 'set_event')) {
71
+ if (opRecords.some(entry => entry.op === 'set_event' || entry.op === 'add_event')) {
72
+ const bound = boundEventOps(nodes, opRecords);
73
+ if (bound.length > 0)
74
+ throw new PlatformApiError(`PAGE-034 — dolu olaya set_event (istek gönderilmedi): ${bound.join(' · ')}`, 400);
72
75
  const typesByElementId = new Map();
73
76
  collectTypes(nodes, typesByElementId);
74
77
  collectTypes(opRecords.map(entry => entry.component).filter(Boolean), typesByElementId);
@@ -77,6 +80,7 @@ export function registerDesignSchemaPatchTool(server, platform, host) {
77
80
  if (unknown.length > 0)
78
81
  throw new PlatformApiError(`PAGE-008 — geçersiz olay adı (istek gönderilmedi): ${unknown.join(' · ')}`, 400);
79
82
  }
83
+ stripGatewayEventFields(opRecords);
80
84
  const deletedFlows = orphanedFlowPlan(nodes, events, [...opRecords, ...removeEventOps]);
81
85
  if (deletedFlows.length > 0 && allowFlowDeletion !== true)
82
86
  throw new PlatformApiError(orphanedFlowMessage(deletedFlows), 400);
@@ -399,7 +399,7 @@ export function registerFlowTools(server, platform) {
399
399
  outcome: existing === undefined ? 'created' : 'updated',
400
400
  flow: { id: writtenFlowId, name, slug, steps: steps.length },
401
401
  ...(parameterWarnings.length > 0 ? { parameterWarnings } : {}),
402
- hint: "Akışı bir olaya bağlamak için: page_schema_patch (layout'ta layout_schema_patch, widget'ta component_schema_patch) ops:[{op:'set_event', target:<elementId>, event:'<component_get events listesindeki ad>', eventValue:<flow id>}]. Olay adları komponente özeldir ve 'on' ÖNEKİ ALMAZ (button → 'click', form → 'submit', table → 'mount'/'select'/'sort'…); uydurma ad 200 döner ama bağ sessizce ölü kalır — gateway PAGE-008 ile yakalar."
402
+ hint: "Akışı bir olaya bağlamak için: page_schema_patch (layout'ta layout_schema_patch, widget'ta component_schema_patch) ops:[{op:'set_event', target:<elementId>, event:'<component_get events listesindeki ad>', eventValue:<flow id>}]; olayda zaten akış bağlıysa ikinciyi add_event ile ekleyin (set_event bağlı akışı değiştirir, PAGE-034). Olay adları komponente özeldir ve 'on' ÖNEKİ ALMAZ (button → 'click', form → 'submit', table → 'mount'/'select'/'sort'…); uydurma ad 200 döner ama bağ sessizce ölü kalır — gateway PAGE-008 ile yakalar."
403
403
  }, normalized), drops), findings);
404
404
  }));
405
405
  server.registerTool('flow_update', {
@@ -1,5 +1,5 @@
1
1
  import { guid, projectParam, environmentParam, pagingParams, readOnly, writes, updates, destructive, microOps, isBranchName } from './common.js';
2
- import { orphanedFlowWarnings, orphanedFlowPlan, orphanedFlowMessage, staleSetterWarnings, lostSetterTargets, normalizeComponentDefaults, normalizeSchemaOps, schemaOpValidationErrors, codePropTemplateErrors, validationNote, componentDefaultsNote, replacePropNote, ensurePageContainer, retargetRootAdds, rootAddsNote, flexSizeWarnings, spacingLayerWarnings, reorderSchemaBlocks, reorderNote, nestedRepeaterNodes, nestedRepeaterMessage, stringStyleNodes, stringStyleWarning, inertFlexPropNodes, inertFlexPropWarning, FLEX_LAYOUT_PROP_NAMES, resetPageContainerPosition, pageContainerPositionWarning, verticalGapWarnings, flexIndexOrder, flexIndexOrderNote, withNormalization, parseVariableEntries, PAGE_CONTAINER_ELEMENT_ID } from '../knowledge/normalize.js';
2
+ import { orphanedFlowWarnings, orphanedFlowPlan, orphanedFlowMessage, boundEventOps, stripGatewayEventFields, staleSetterWarnings, lostSetterTargets, normalizeComponentDefaults, normalizeSchemaOps, schemaOpValidationErrors, codePropTemplateErrors, validationNote, componentDefaultsNote, replacePropNote, ensurePageContainer, retargetRootAdds, rootAddsNote, flexSizeWarnings, spacingLayerWarnings, reorderSchemaBlocks, reorderNote, nestedRepeaterNodes, nestedRepeaterMessage, stringStyleNodes, stringStyleWarning, inertFlexPropNodes, inertFlexPropWarning, FLEX_LAYOUT_PROP_NAMES, resetPageContainerPosition, pageContainerPositionWarning, verticalGapWarnings, flexIndexOrder, flexIndexOrderNote, withNormalization, parseVariableEntries, PAGE_CONTAINER_ELEMENT_ID } from '../knowledge/normalize.js';
3
3
  import { functionSignature } from '../knowledge/functionGuards.js';
4
4
  import { fetchHost, pageUpdateBody, schemaParam, eventsParam, unwrap, modeParam, summarize, findNode, flowPath } from './screens.js';
5
5
  import { fetchComponentEventNames } from './components.js';
@@ -67,7 +67,7 @@ async function fetchPage(platform, project, environment, applicationId, pageId)
67
67
  */
68
68
  export const SERVER_PAGE_OPS = [
69
69
  'replace_prop', 'set_binding', 'set_prop_type', 'add_component', 'remove_component', 'move_component', 'reorder', 'set_block',
70
- 'set_appearance', 'set_states', 'set_event', 'remove_event', 'set_validations', 'set_state_binding', 'remove_state_binding', 'batch'
70
+ 'set_appearance', 'set_states', 'set_event', 'add_event', 'remove_event', 'set_validations', 'set_state_binding', 'remove_state_binding', 'batch'
71
71
  ];
72
72
  export function registerPageTools(server, platform, injectedScreenshotter, injectedMemory) {
73
73
  const config = platform.config;
@@ -638,10 +638,13 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
638
638
  reject('PAGE-016', entry, schemaOpValidationErrors([entry]));
639
639
  for (const entry of opRecords)
640
640
  reject('PAGE-032', entry, codePropTemplateErrors([entry]));
641
- if (opRecords.some(entry => entry.op === 'set_event')) {
641
+ if (opRecords.some(entry => entry.op === 'set_event' || entry.op === 'add_event')) {
642
642
  const typesByElementId = new Map();
643
643
  try {
644
- collectTypes((await readPage()).schema, typesByElementId);
644
+ const page = await readPage();
645
+ collectTypes(page.schema, typesByElementId);
646
+ for (const entry of opRecords)
647
+ reject('PAGE-034', entry, boundEventOps(page.schema, [entry]));
645
648
  }
646
649
  catch {
647
650
  typesByElementId.clear();
@@ -653,6 +656,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
653
656
  }
654
657
  if (rejected.length > 0)
655
658
  throw new PlatformApiError(rejectedOpsMessage(rejected), 400);
659
+ stripGatewayEventFields(opRecords);
656
660
  const wantsRootAdd = opRecords.some(entry => entry.op === 'add_component' && (entry.parent === undefined || entry.parent === null || entry.parent === 'root'));
657
661
  let retargeted = [];
658
662
  if (wantsRootAdd) {
@@ -24,7 +24,7 @@ export function outlineWriteResponse(response) {
24
24
  if (Array.isArray(container.appliedPatch))
25
25
  container.appliedPatch = outlineAppliedPatch(container.appliedPatch);
26
26
  }
27
- export const opsParam = z.array(z.record(z.unknown())).describe("Micro-op listesi. Op sözlüğü: replace_prop {target, prop, value} (value ham değer, code sarmalı {type: 'code', ref: '{{ … }}'} ya da translation sarmalı {type: 'translation', ref: '<çeviri anahtarı>'} — prop tipini değiştirmek için komponenti silip yeniden EKLEMEYİN, ona yazan setter'ların hedefi silinir (FLOW-018); platform replace_prop'ta expression alanını OKUMAZ — gateway onu code sarmalına çevirir) | set_binding {target, prop, expression: '{{ Page.variables.x }}'} | add_component {parent (üst elementId ya da 'root'), index?, component} | remove_component {target} | move_component {target, parent, index?, position: {x, y}} (index ÇİZİM sırası değildir, runtime çocukları blocks.position'a göre (y, sonra x) çizer. page_schema_patch'te flex kapta gateway index'i görünen sıraya çevirir ve kardeş konumlarını yeniden yazar; ızgara kapta sırayı position.y verir, sona eklemeyen index PAGE-033 uyarısıdır. layout_schema_patch ve component_schema_patch bu çeviriyi YAPMAZ: orada sırayı position.y ile verin) | set_event {target, event, eventValue} | remove_event {target, event} (olayı diziden çıkarır; target: 'page' sayfa düzeyi olaylar. set_event ile eventValue: null olay SİLMEZ, değersiz ölü kayıt bırakır. Olmayan olay 422 missing-event) | set_validations {target, validations: [{type, message, value, isActive}]} (komponentin validation listesini TAMAMEN değiştirir; boş dizi hepsini siler) | set_appearance {target, appearance: {heightType?, widthType?, tooltip?, background?, …}} (kurulu düğümün appearance'ına YALNIZ verilen alanları yazar, gerisini korur; null alanı temizler; bilinmeyen alan unknown-appearance-field ile reddedilir. GÖRÜNÜRLÜK appearance'ta DEĞİLDİR: gizlemek için replace_prop {target, prop: 'isHidden', value: true}, koşul için set_binding {prop: 'isHidden'} — add_component gövdesindeki appearance.isHidden'ı gateway props.isHidden'a taşır) | resize_component {target, size?: {w,h}, appearance?: {widthType,widthString,heightType,heightString}, states?: {isWrapper,isScrollable,isSticky,isDroppable,isFloating,isQuickStack}, breakpoint?: 1024} (gateway adı: platforma set_block {target, breakpoint, size} + set_appearance {target, appearance} + set_states {target, states} olarak gider — o üç op'u doğrudan da gönderebilirsiniz. move_component'e konan size reddedilir. Izgarada blocks[].size, flex'te appearance geçerlidir; ebeveynin düzeni yazılanı etkisiz kılıyorsa PAGE-019 uyarısı çıkar. states bir PROP DEĞİL düğüm alanıdır ve replace_prop onu yazamaz — düğümü silmeden isWrapper bayrağını değiştirmenin yolu budur). set_prop_type {target, prop, propType: 'static'|'code'|'translation', ref?, data?} (replace_prop sarmalıyla aynı sonucu verir ve mevcut data'yı tasarım yedeği olarak taşır. Bu op prop'un TİPİNİ yerinde değiştirir. Taşıyıcı alan tipe göre: static → data, code ve translation → ref (runtime data'yı her turda ezer, anahtar yalnız ref'te yaşar). Eksik taşıyıcı istek gönderilmeden reddedilir; page_schema_validate aynı kararı verir) | reorder {parent ('root' ya da elementId), order: [çocuk elementId'lerinin TAMAMI, yeni sırada]} (yalnız DİZİ sırasını değiştirir; çizim sırası blocks.position'dır — görünen sırayı değiştirmek için konumları set_block/move_component ile de yazın) | set_state_binding {target, binding: {name, expression, targets}} (ada göre ekler/değiştirir) | remove_state_binding {target, name} | batch {gestureId?, label?, ops: [...]} (iç içe batch yok). target = elementId. İstek atomiktir: tek op reddedilirse hiçbiri yazılmaz.");
27
+ export const opsParam = z.array(z.record(z.unknown())).describe("Micro-op listesi. Op sözlüğü: replace_prop {target, prop, value} (value ham değer, code sarmalı {type: 'code', ref: '{{ … }}'} ya da translation sarmalı {type: 'translation', ref: '<çeviri anahtarı>'} — prop tipini değiştirmek için komponenti silip yeniden EKLEMEYİN, ona yazan setter'ların hedefi silinir (FLOW-018); platform replace_prop'ta expression alanını OKUMAZ — gateway onu code sarmalına çevirir) | set_binding {target, prop, expression: '{{ Page.variables.x }}'} | add_component {parent (üst elementId ya da 'root'), index?, component} | remove_component {target} | move_component {target, parent, index?, position: {x, y}} (index ÇİZİM sırası değildir, runtime çocukları blocks.position'a göre (y, sonra x) çizer. page_schema_patch'te flex kapta gateway index'i görünen sıraya çevirir ve kardeş konumlarını yeniden yazar; ızgara kapta sırayı position.y verir, sona eklemeyen index PAGE-033 uyarısıdır. layout_schema_patch ve component_schema_patch bu çeviriyi YAPMAZ: orada sırayı position.y ile verin) | set_event {target, event, eventValue, replaceBoundFlows?} (olayın BÜTÜN bağlarını bu tek akışa indirir; olayda başka akış bağlıysa gateway PAGE-034 ile reddeder — bilerek değiştirmek için replaceBoundFlows: true) | add_event {target, event, eventValue} (olaya bir akış DAHA bağlar; bağlı akışların hepsi paralel ve birbirinden bağımsız koşar, sıra garantisi yoktur — sıra gerekiyorsa flow-trigger. Aynı akış iki kez 422 duplicate-event. Mobil runtime yalnız İLK akışı koşar) | remove_event {target, event, eventValue?} (olayı diziden çıkarır; target: 'page' sayfa düzeyi olaylar. Olaya birden fazla akış bağlıysa eventValue ile hangisi olduğunu verin, yoksa 422 ambiguous-event. set_event ile eventValue: null olay SİLMEZ, değersiz ölü kayıt bırakır. Olmayan olay 422 missing-event) | set_validations {target, validations: [{type, message, value, isActive}]} (komponentin validation listesini TAMAMEN değiştirir; boş dizi hepsini siler) | set_appearance {target, appearance: {heightType?, widthType?, tooltip?, background?, …}} (kurulu düğümün appearance'ına YALNIZ verilen alanları yazar, gerisini korur; null alanı temizler; bilinmeyen alan unknown-appearance-field ile reddedilir. GÖRÜNÜRLÜK appearance'ta DEĞİLDİR: gizlemek için replace_prop {target, prop: 'isHidden', value: true}, koşul için set_binding {prop: 'isHidden'} — add_component gövdesindeki appearance.isHidden'ı gateway props.isHidden'a taşır) | resize_component {target, size?: {w,h}, appearance?: {widthType,widthString,heightType,heightString}, states?: {isWrapper,isScrollable,isSticky,isDroppable,isFloating,isQuickStack}, breakpoint?: 1024} (gateway adı: platforma set_block {target, breakpoint, size} + set_appearance {target, appearance} + set_states {target, states} olarak gider — o üç op'u doğrudan da gönderebilirsiniz. move_component'e konan size reddedilir. Izgarada blocks[].size, flex'te appearance geçerlidir; ebeveynin düzeni yazılanı etkisiz kılıyorsa PAGE-019 uyarısı çıkar. states bir PROP DEĞİL düğüm alanıdır ve replace_prop onu yazamaz — düğümü silmeden isWrapper bayrağını değiştirmenin yolu budur). set_prop_type {target, prop, propType: 'static'|'code'|'translation', ref?, data?} (replace_prop sarmalıyla aynı sonucu verir ve mevcut data'yı tasarım yedeği olarak taşır. Bu op prop'un TİPİNİ yerinde değiştirir. Taşıyıcı alan tipe göre: static → data, code ve translation → ref (runtime data'yı her turda ezer, anahtar yalnız ref'te yaşar). Eksik taşıyıcı istek gönderilmeden reddedilir; page_schema_validate aynı kararı verir) | reorder {parent ('root' ya da elementId), order: [çocuk elementId'lerinin TAMAMI, yeni sırada]} (yalnız DİZİ sırasını değiştirir; çizim sırası blocks.position'dır — görünen sırayı değiştirmek için konumları set_block/move_component ile de yazın) | set_state_binding {target, binding: {name, expression, targets}} (ada göre ekler/değiştirir) | remove_state_binding {target, name} | batch {gestureId?, label?, ops: [...]} (iç içe batch yok). target = elementId. İstek atomiktir: tek op reddedilirse hiçbiri yazılmaz.");
28
28
  /**
29
29
  * PAGE-008 — `set_event` uydurma bir olay adıyla 200 döner ve bağ SESSİZCE ölü kalır: motor olayı
30
30
  * komponentin yaydığı adla eşleştirir, eşleşme yoksa akış hiç çalışmaz. Kontrol yalnız hedefin
@@ -35,7 +35,7 @@ export const opsParam = z.array(z.record(z.unknown())).describe("Micro-op listes
35
35
  export function unknownEventOps(ops, typesByElementId, eventsBySlug) {
36
36
  return ops
37
37
  .map(op => op)
38
- .filter(op => op.op === 'set_event')
38
+ .filter(op => op.op === 'set_event' || op.op === 'add_event')
39
39
  .flatMap(op => {
40
40
  const target = String(op.target ?? '');
41
41
  const event = String(op.event ?? '');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appaflytech/zeyrek-ai-mcp",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
4
4
  "type": "module",
5
5
  "description": "Zeyrek (NgApps) platform APIs as MCP tools — data, logic and Studio design surface in one gateway",
6
6
  "license": "UNLICENSED",