@appaflytech/zeyrek-ai-mcp 0.0.40 → 0.0.42

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 (60) hide show
  1. package/dist/knowledge/componentCatalog.js +95 -104
  2. package/dist/knowledge/componentEvents.js +34 -18
  3. package/dist/knowledge/mobileComponentCatalog.js +119 -20
  4. package/dist/knowledge/mobileComponentEvents.js +41 -0
  5. package/dist/knowledge/motionResolve.js +252 -0
  6. package/dist/knowledge/normalize.js +117 -71
  7. package/dist/knowledge/pageDiscover.js +2 -2
  8. package/dist/knowledge/platform/batchOps.js +70 -0
  9. package/dist/knowledge/platform/heroTag.js +153 -0
  10. package/dist/knowledge/platform/lint.js +128 -6
  11. package/dist/knowledge/platform/mobile.js +1 -1
  12. package/dist/knowledge/platform/mobileLayoutLint.js +8 -1
  13. package/dist/knowledge/platform/mobileRules.js +18 -0
  14. package/dist/knowledge/platform/pageTransition.js +57 -0
  15. package/dist/knowledge/platform/web.js +1 -1
  16. package/dist/knowledge/rules.js +217 -24
  17. package/dist/knowledge/styleResolve.js +0 -1
  18. package/dist/knowledge/styleResolveNative.js +88 -4
  19. package/dist/platform/capture/actions.js +27 -1
  20. package/dist/platform/capture/capture.js +10 -13
  21. package/dist/platform/capture/persistent-browser.js +130 -0
  22. package/dist/platform/capture/summary.js +4 -1
  23. package/dist/resources/examples/flow-list-to-form.json +8 -10
  24. package/dist/resources/examples/flow-load-table.json +1 -1
  25. package/dist/resources/examples/mobile-app-shell-drawer.json +1 -0
  26. package/dist/resources/examples/mobile-list-detail-form.json +7 -3
  27. package/dist/resources/examples/mobile-scan-code.json +285 -0
  28. package/dist/resources/guides/component.md +3 -3
  29. package/dist/resources/guides/entity.md +16 -0
  30. package/dist/resources/guides/expressions.md +8 -7
  31. package/dist/resources/guides/merge.md +98 -18
  32. package/dist/resources/guides/microservice.md +3 -3
  33. package/dist/resources/guides/middleware.md +2 -0
  34. package/dist/resources/guides/mobile-layout.md +2 -1
  35. package/dist/resources/guides/mobile-publish.md +1 -0
  36. package/dist/resources/guides/mobile-steps.md +67 -0
  37. package/dist/resources/guides/mobile.md +51 -70
  38. package/dist/resources/guides/page.md +229 -137
  39. package/dist/resources/guides/translation.md +67 -1
  40. package/dist/resources/guides/workflow.md +31 -1
  41. package/dist/resources/schema-fingerprint.json +2 -2
  42. package/dist/resources/schemas/ResolveMergeConflictsCommand.json +30 -1
  43. package/dist/tools/activities.js +3 -1
  44. package/dist/tools/applications.js +16 -2
  45. package/dist/tools/components.js +12 -6
  46. package/dist/tools/designSchemaPatch.js +18 -13
  47. package/dist/tools/entities.js +41 -4
  48. package/dist/tools/layouts.js +29 -9
  49. package/dist/tools/merges.js +29 -9
  50. package/dist/tools/mobile.js +15 -1
  51. package/dist/tools/pages.js +156 -168
  52. package/dist/tools/platformInfo.js +3 -2
  53. package/dist/tools/profileWrite.js +2 -2
  54. package/dist/tools/queries.js +1 -1
  55. package/dist/tools/rows.js +14 -3
  56. package/dist/tools/schemaOps.js +132 -34
  57. package/dist/tools/screenWrite.js +3 -3
  58. package/dist/tools/screens.js +42 -3
  59. package/dist/tools/translations.js +148 -6
  60. package/package.json +2 -2
@@ -0,0 +1,130 @@
1
+ import { mkdir } from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ /**
5
+ * Tarayıcı SABİT bir profille açılır. `chromium.launch()` her başlatmada geçici bir profil
6
+ * kuruyordu ve Chrome her yeni profilde Windows parolasının boş olup olmadığını sınıyor; bu
7
+ * sınama Security log'a başarısız oturum açma (4625, LogonType 2, 0xc000006a, chrome.exe)
8
+ * olarak düşüyor ve 10 dakikada 10 çekim yerel hesabı kilitliyordu (0xc0000234).
9
+ *
10
+ * Profil yalnız bu araca aittir; kullanıcının gerçek Chrome profili (Google\Chrome\User Data)
11
+ * hiçbir koşulda kullanılmaz. Profil dizini aynı anda tek süreç tarafından açılabildiği için
12
+ * (iki MCP oturumu) dolu dizinde sıradaki SABİT yuvaya geçilir — yuva sayısı sınırlıdır, her
13
+ * yuvanın sınaması ömründe bir kez olur.
14
+ */
15
+ export const screenshotProfileRoot = () => path.join(process.env.LOCALAPPDATA ?? path.join(os.homedir(), '.cache'), 'zeyrek-mcp');
16
+ const profileSlots = ['chrome-profile', 'chrome-profile-2', 'chrome-profile-3'];
17
+ /** Yuvaları sırayla dener; her yuvada önce verilen tarayıcı, sonra sistem Chrome'u (`channel: 'chrome'`). */
18
+ export function persistentContextOpener(chromium, primary) {
19
+ return async (deviceScaleFactor) => {
20
+ const root = screenshotProfileRoot();
21
+ let firstError;
22
+ for (const slot of profileSlots) {
23
+ const userDataDir = path.join(root, slot);
24
+ await mkdir(userDataDir, { recursive: true });
25
+ for (const launch of [primary, { channel: 'chrome' }]) {
26
+ try {
27
+ return await chromium.launchPersistentContext(userDataDir, { headless: true, deviceScaleFactor, ...launch });
28
+ }
29
+ catch (error) {
30
+ firstError ??= error;
31
+ }
32
+ }
33
+ }
34
+ throw firstError;
35
+ };
36
+ }
37
+ /**
38
+ * Kalıcı bağlamı `ScreenshotBrowser` gibi gösterir. Kalıcı bağlamda ikinci bağlam açılamaz;
39
+ * çekimler seri koştuğu için (`StudioScreenshotter` kuyruğu) her `newContext` aynı bağlamın
40
+ * üstünde bir oturum verir. `deviceScaleFactor` yalnız açılışta verilebildiği için değişince
41
+ * bağlam AYNI profille yeniden açılır.
42
+ */
43
+ export class PersistentScreenshotBrowser {
44
+ open;
45
+ current;
46
+ constructor(open) {
47
+ this.open = open;
48
+ }
49
+ async newContext(options) {
50
+ if (this.current && this.current.deviceScaleFactor !== options.deviceScaleFactor) {
51
+ await this.close();
52
+ }
53
+ if (!this.current) {
54
+ const context = await this.open(options.deviceScaleFactor);
55
+ const entry = { context, deviceScaleFactor: options.deviceScaleFactor };
56
+ // Tarayıcı düşerse sonraki çekim yeniden açsın (eski `browser.newContext` hatası gibi).
57
+ context.on('close', () => {
58
+ if (this.current === entry)
59
+ this.current = undefined;
60
+ });
61
+ this.current = entry;
62
+ }
63
+ return new PersistentContextSession(this.current.context, options);
64
+ }
65
+ async close() {
66
+ const expired = this.current;
67
+ this.current = undefined;
68
+ await expired?.context.close();
69
+ }
70
+ }
71
+ /**
72
+ * Tek çekimlik oturum. Eski taze bağlamın yalıtımı korunur: başlık ve init script'i bağlama
73
+ * değil sayfaya yazılır (sonraki çekime sızmaz — `impersonated-user-id` dahil), kapanışta
74
+ * çerezler ve gezilen kökenlerin depoları (localStorage, IndexedDB, …) diskteki profilden silinir.
75
+ */
76
+ class PersistentContextSession {
77
+ context;
78
+ options;
79
+ initScripts = [];
80
+ headers;
81
+ pages = [];
82
+ constructor(context, options) {
83
+ this.context = context;
84
+ this.options = options;
85
+ }
86
+ addCookies(cookies) {
87
+ return this.context.addCookies(cookies);
88
+ }
89
+ async addInitScript(script) {
90
+ this.initScripts.push(script);
91
+ }
92
+ async setExtraHTTPHeaders(headers) {
93
+ this.headers = headers;
94
+ }
95
+ async newPage() {
96
+ const page = await this.context.newPage();
97
+ this.pages.push(page);
98
+ await page.setViewportSize(this.options.viewport);
99
+ await page.emulateMedia({ reducedMotion: this.options.reducedMotion ?? 'no-preference' });
100
+ if (this.headers)
101
+ await page.setExtraHTTPHeaders(this.headers);
102
+ for (const script of this.initScripts)
103
+ await page.addInitScript(script);
104
+ return page;
105
+ }
106
+ async close() {
107
+ const origins = new Set();
108
+ for (const page of this.pages) {
109
+ try {
110
+ const origin = new URL(page.url()).origin;
111
+ if (origin.startsWith('http'))
112
+ origins.add(origin);
113
+ }
114
+ catch {
115
+ // about:blank vb.
116
+ }
117
+ }
118
+ const probe = this.pages.find(page => !page.isClosed());
119
+ if (probe && origins.size > 0) {
120
+ const cdp = await this.context.newCDPSession(probe).catch(() => undefined);
121
+ for (const origin of origins) {
122
+ await cdp?.send('Storage.clearDataForOrigin', { origin, storageTypes: 'all' }).catch(() => undefined);
123
+ }
124
+ await cdp?.detach().catch(() => undefined);
125
+ }
126
+ await this.context.clearCookies().catch(() => undefined);
127
+ for (const page of this.pages)
128
+ await page.close().catch(() => undefined);
129
+ }
130
+ }
@@ -45,7 +45,10 @@ export function domSummaryScript(viewportHeight) {
45
45
  var field = node.querySelector('input, select, textarea');
46
46
  var chosen = node.querySelector('.selected-item');
47
47
  var holder = field || chosen;
48
- var value = !owned(holder) ? '' : (field ? (field.type === 'password' ? '***' : clean(field.value)) : clean(chosen.textContent));
48
+ // 37 S-23: OTP'nin gizli girdisi type=text'tir ve kod özet satırında açık yazıyordu; sır sinyali
49
+ // tarayıcının otomatik tamamlama ipucudur (one-time-code · current-password · new-password).
50
+ var secret = !!field && (field.type === 'password' || /(^|\\s)(one-time-code|[\\w-]*-password)(\\s|$)/i.test(String(field.getAttribute('autocomplete') || '')));
51
+ var value = !owned(holder) ? '' : (field ? (secret ? '***' : clean(field.value)) : clean(chosen.textContent));
49
52
  var media = node.querySelector('img, svg, canvas, video, iframe');
50
53
  // İç içe <li> hem grubu hem alt mesajları sayıyordu: tek grup + 2 alt hata 3 görünüyordu (B-7).
51
54
  var errorItems = Array.prototype.slice.call(node.querySelectorAll('.form-validation-errors li:not(:has(li))'))
@@ -1,22 +1,23 @@
1
1
  {
2
2
  "_meta": {
3
3
  "synthetic": false,
4
- "note": "Liste sayfasından kayıt sayfasına geçiş: satıra çift tıkla → edit (?id=), 'Yeni' butonu → create (parametresiz). Tek yeni kavram redirect adımıdır; query alanı SABİT DEĞİL İFADEDİR.",
5
- "harvestedFrom": "qm projesi '[SY] Departmanlar' sayfası (table-doubleClick + button-click akışları), 2026-08-17; ayrıntı: zeyrek-mcp/docs/18-sayfa-arketipleri.md §2",
4
+ "note": "Liste sayfasından kayıt sayfasına geçiş: satıra çift tıkla → edit (?id=), 'Yeni' butonu → create (parametresiz). Tek yeni kavram redirect adımıdır; query alanı SABİT DEĞİL İFADEDİR. Tablo `datatable`'dır: olay `rowDoubleClick`, liste tek `mount` akışıyla dolar.",
5
+ "harvestedFrom": "qm projesi '[SY] Departmanlar' sayfası (table-doubleClick + button-click akışları), 2026-08-17; ayrıntı: zeyrek-mcp/docs/18-sayfa-arketipleri.md §2. 2026-10-13'te kaldırılan `table` bileşeninden `datatable`'a taşındı (olay adları ve bağ sayısı değişti; redirect adımları aynı).",
6
6
  "pairsWith": "page-form-upsert (geçilen sayfa), flow-load-table (listenin kendisini dolduran akış)"
7
7
  },
8
8
  "_rules": {
9
9
  "queryIsExpression": "redirect.args.query bir İFADEDİR, sabit string değil: template literal (`id=${…}`) ve koşullu ifade yazılabilir. Sayfalar arası parametre geçmenin yolu budur; hedef sayfa Page.queryParams.<ad> ile okur. variable_create gerekmez.",
10
- "selfAndNames": "İfade içinde `self` tetikleyen komponenttir (self.selectedRow.id). Komponente adıyla da erişilir (table?.selectedRow?.id) — addaki '-' silinir. Sözlük: platform_guide('expressions') §2.",
10
+ "selfAndNames": "İfade içinde `self` tetikleyen komponenttir (self.selectedRow.id). Komponente adıyla da erişilir (records_table?.selectedRow?.id) — addaki '-' silinir. Sözlük: platform_guide('expressions') §2.",
11
11
  "pageIdAndPath": "redirect args'ında pageId (bağlayıcı referans) ve path (slug) birlikte verilir; sabit URL yazma — slug değişince pageId referansı ayakta kalır.",
12
- "eventsAreSeparate": "Bu örnek `table` komponentini varsayar: olaylar AYRI akışlara bağlanır (mount = yükle · pagination · filter · sort · doubleClick = kaydı aç). `datatable` seçersen tek `mount` akışı yeterlidir — o komponent süzme/sıralama/sayfalamayı kendi içinde yapıp prop'ları yazdıktan sonra mount'u yeniden tetikler (Kural 1e). Hangi olayların var olduğunu component_get ile doğrulayın; olay adları `on` öneki ALMAZ (button → click).",
12
+ "singleMountFlow": "Tablo `datatable`'dır ve liste TEK `mount` akışıyla dolar: kullanıcı süzünce/sıralayınca/sayfa değiştirince komponent kendi filters/sortField/sortType/offset prop'larını yazar ve `mount`'u yeniden tetikler (Kural 1e) — pagination/filter/sort diye ayrı olay YOKTUR, bağlamaya çalışmak PAGE-008 ile reddedilir. Kaydı açan olay `rowDoubleClick`'tir (tek tık için `rowClick`); ikisi de olaydan ÖNCE tıklanan satırı self.selectedRow'a yazar. Hangi olayların var olduğunu component_get ile doğrulayın; olay adları `on` öneki ALMAZ (button → click).",
13
+ "editModeAndDoubleClick": "editMode none DIŞINDAYKEN çift tık satırı ayrıca düzenlemeye açar; liste → form geçişi için tabloyu editMode: none bırakın (ya da geçişi satır aksiyonuyla kurun: actions[].flow).",
13
14
  "newButton": "'Yeni' butonu ayrı bir komponenttir ve akışı parametresiz redirect'tir; hedef sayfa parametre görmediği için create kipine düşer."
14
15
  },
15
16
  "openRecordFlowCreateArgs": {
16
17
  "host": "page",
17
18
  "hostId": "<liste sayfası id>",
18
- "name": "records_table - doubleClick",
19
- "slug": "records-table-doubleclick",
19
+ "name": "records_table - rowDoubleClick",
20
+ "slug": "records-table-rowdoubleclick",
20
21
  "steps": [
21
22
  {
22
23
  "stepId": "start", "name": "start", "type": "start", "kind": "normal", "args": {},
@@ -56,10 +57,7 @@
56
57
  },
57
58
  "schemaPatchOps": [
58
59
  { "op": "set_event", "target": "records_table", "event": "mount", "eventValue": "<liste yükleme flow id>" },
59
- { "op": "set_event", "target": "records_table", "event": "doubleClick", "eventValue": "<openRecord flow id>" },
60
- { "op": "set_event", "target": "records_table", "event": "pagination", "eventValue": "<liste yükleme flow id>" },
61
- { "op": "set_event", "target": "records_table", "event": "filter", "eventValue": "<liste yükleme flow id>" },
62
- { "op": "set_event", "target": "records_table", "event": "sort", "eventValue": "<liste yükleme flow id>" },
60
+ { "op": "set_event", "target": "records_table", "event": "rowDoubleClick", "eventValue": "<openRecord flow id>" },
63
61
  { "op": "set_event", "target": "btn_new", "event": "click", "eventValue": "<newRecord flow id>" }
64
62
  ]
65
63
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "_meta": {
3
3
  "synthetic": true,
4
- "note": "Komponentin mount olayına bağlanan flow tabloya veri yükler: yükleniyor bayrağı → microservice → satır eşleme + toplam kayıt. Bu dosyanın 2026-08-16 öncesi sürümü ÜÇ HATA taşıyordu ve ürettiği akışlar ne Studio'da çiziliyor ne runtime'da çalışıyordu; aşağıdaki şekil UI kodu okunarak düzeltildi (bkz. _rules).",
4
+ "note": "Komponentin mount olayına bağlanan flow tabloya (`datatable`) veri yükler: yükleniyor bayrağı → microservice → satır eşleme + toplam kayıt. Aynı akış süzme/sıralama/sayfa değişiminde de koşar: `datatable` kendi filters/sortField/sortType/offset prop'larını yazıp `mount`'u yeniden tetikler, bu yüzden adım parametreleri o prop'lardan okunur. Bu dosyanın 2026-08-16 öncesi sürümü ÜÇ HATA taşıyordu ve ürettiği akışlar ne Studio'da çiziliyor ne runtime'da çalışıyordu; aşağıdaki şekil UI kodu okunarak düzeltildi (bkz. _rules).",
5
5
  "harvestedFrom": "kod doğrulaması: NgApps.UI packages/ng-apps-flow + apps/studio/src/features/event-actions (2026-08-16)"
6
6
  },
7
7
  "_rules": {
@@ -12,6 +12,7 @@
12
12
  "catalog": "app-bar, slot ve drawer mobil kataloğun Dalga 1 bileşenleridir. component_list mobil uygulamada bunları döndürmüyorsa katalog henüz yazılmamıştır ve layout_create 422 unknown-type alır (MOB-003).",
13
13
  "verify": "Kabuğu layout'a bağlı bir sayfada page_screenshot ile doğrula (mobilde varsayılan device: 'phone').",
14
14
  "menuTarget": "app-bar.menuTarget çekmecenin elementId'sidir (nav_drawer); iç rol app_bar__menu düğmesidir — page_screenshot actions:[{ action:'click', target:'app_bar__menu' }] çekmeceyi açar.",
15
+ "drawerButtons": "Çekmecedeki düğmeler ŞABLONDUR: mobile_app_create her sayfa için bir düğme üretir — elementId menu_<slug> (slug'daki tire ve diğer işaretler alt çizgiye döner: ana-sayfa → menu_ana_sayfa), metin pageName. Buradaki iki düğme bu örneğin kendi pages listesine (gorevler, profil) karşılıktır; başka uygulamaya kopyalanmaz (37 S-9).",
15
16
  "drawerFlows": "Menü düğmelerine akış ayrı adımda bağlanır: flow_create (host: 'layout', adım redirect { path: '<sayfa slug'ı>' }) → layout_schema_patch set_event { target: 'menu_gorevler', event: 'click' }.",
16
17
  "drawerNode": "Çekmece mobil kataloğun `drawer` bileşenidir (kind 'component', çocuk alır): layout'a doğrudan yazılır, prop'ları katalogdakilerdir (`position`, `size`, `title`, `isDismissDisabled`); kapalı doğar, `app-bar` menüsü `isOpen: true` yazarak açar. Katalogda olmayan prop (ör. `width`, `overlay`) ya da seçenek dışı değer (düğmede `variant: 'ghost'` — geçerli olanlar `solid`/`text`/`outline`) layout_create'i 422 ile düşürür.",
17
18
  "layoutContract": "Mizanpaj sözleşmesi: üst güvenli alan tek yerde, kabukta (app-bar 'safe-area' edges:'top' içinde; sayfa ikinci pay vermez). Ekran kenarı boşluğu tek değer (16) ve tek yerde: slot'un yatay ve üst margin'i — içerik app-bar'ın 16 dp altından başlar, kabuksuz sayfadaki gibi. shell_root 'shrink' ve padding'siz — düz container olduğu için tip yazılmazsa runtime 10 dp padding verir ve app-bar kenardan kopar.",
@@ -3,7 +3,7 @@
3
3
  "synthetic": true,
4
4
  "note": "Mobil uygulamada üç sayfalık liste → detay → form akışı (G6 koşusunun girdisi): 'gorevler' mount'ta görevleri yükleyip repeater'a yazar, satıra dokunmak 'detay'ı id ile açar, 'yeni' formu kaydedip toast gösterir ve listeye döner. Yerleşim flex (blocks yok), görünüm tipli gruplarla.",
5
5
  "pairsWith": "mobile-app-shell-stack ya da mobile-app-shell-tab (kabuk), microservice-table-list (liste ucu), page-form-upsert (web'deki form ikizi)",
6
- "sourceRef": "platform_guide('mobile') §Kurulum sırası, §Akış adımları; ngapps://examples/flow-list-to-form (redirect query sözleşmesi)"
6
+ "sourceRef": "platform_guide('mobile') §Kurulum sırası; platform_guide('mobile-steps') §Adım sözlüğü; ngapps://examples/flow-list-to-form (redirect query sözleşmesi)"
7
7
  },
8
8
  "_rules": {
9
9
  "order": "Önce kabuk ve sayfalar (mobile_app_create), sonra microservice'ler, sonra her sayfanın schemaPatchOps'u, en son akışlar (flow_create) ve bağları (set_event).",
@@ -15,7 +15,7 @@
15
15
  "verify": "page_screenshot({ applicationSlug: 'saha', pageSlug: 'yeni', actions: [{ action: 'set', target: 'in_title', value: 'Sayaç okuma' }, { action: 'submit', target: 'frm_task' }] }) → submitVia 'bridge', flowRuns ≥ 1, son kare 'gorevler'.",
16
16
  "propNames": "Prop adları mobil kataloğun adlarıdır (component_get): metin `text.content`, satırlar `repeater.rows`. Mobilde istemci tarafı form doğrulaması yok (`isRequired` prop'u yok) — zorunlu alanı kayıt akışı denetler. Kod bağı ifadeyi `ref`'te taşır: `{ type: 'code', ref: '{{ item.title }}' }` — `data`'ya yazılan ifade çizilmez (boş metin).",
17
17
  "detailFromSteps": "Detay alanları uçtan gelen veriyi SETTER adımıyla alır (`fill_detail`, `value.type: 'storage'`): `{{ Steps.… }}` kod bağı ilk çizimde okunur ve `Steps`'e yazmak düğümü yeniden okutmaz — sayfa boş kalırdı (web `page-detail-profile` ile aynı desen).",
18
- "mobileBehavior": "Çekerek yenile listenin değil onu saran `scroll-view`'ın işidir (`refreshable: true` + `refresh` olayı). `redirect.presentation` push | modal | replace alır; küme dışı değer MOB-008 uyarısı ve backend 422'sidir. Sekme hedefine giden redirect'te modal `navigate`'e düşer.",
18
+ "mobileBehavior": "Çekerek yenile listenin değil onu saran `scroll-view`'ın işidir (`refreshable: true` + `refresh` olayı). `redirect.presentation` push | modal | replace alır; küme dışı değer MOB-008 uyarısı ve backend 422'sidir. Sekme hedefine giden redirect'te modal `navigate`'e düşer. `redirect.transition` hedefin nasıl gireceğini söyler: default | none | fade | slide | slideUp (küme dışı değeri backend 422 ile reddeder); alan yoksa uygulamanın varsayılanı (`properties.mobile.transition`) geçerlidir. `modal` sunumda ve sekme hedefinde geçiş KULLANILMAZ: 'yeni' formunun kayıttan sonraki dönüşü (`go_list`) `fade` yazar ve bu yalnız yığın kabuğunda (mobile-app-shell-stack) oynar — sekme kabuğunda (mobile-app-shell-tab) 'gorevler' bir sekme sayfasıdır, sekme değişir ve geçiş oynamaz. Liste → detay adımı (`go_detail`) `modal` olduğu için geçiş taşımaz.",
19
19
  "layoutContract": "Mizanpaj sözleşmesi platform_guide('mobile-layout'): başlık text type (fontSize değil), form direction:'column' + horizontalAlign:'stretch' (yönü yazılmamış form satırdır), aralık gap'ten. Sayfalar MOB-015…021'den sıfır bulguyla geçer."
20
20
  },
21
21
  "pages": [
@@ -85,6 +85,7 @@
85
85
  "type": "view",
86
86
  "kind": "component",
87
87
  "props": {},
88
+ "blocks": [],
88
89
  "children": [
89
90
  {
90
91
  "elementId": "row_card",
@@ -118,6 +119,7 @@
118
119
  "type": "view",
119
120
  "kind": "component",
120
121
  "props": {},
122
+ "blocks": [],
121
123
  "children": [
122
124
  {
123
125
  "elementId": "lst_tasks_bos",
@@ -130,6 +132,7 @@
130
132
  "data": "Kayıt yok"
131
133
  }
132
134
  },
135
+ "blocks": [],
133
136
  "children": []
134
137
  }
135
138
  ]
@@ -678,7 +681,8 @@
678
681
  "args": {
679
682
  "pageId": "<gorevler sayfa id>",
680
683
  "path": "gorevler",
681
- "query": ""
684
+ "query": "",
685
+ "transition": "fade"
682
686
  },
683
687
  "branches": [
684
688
  {
@@ -0,0 +1,285 @@
1
+ {
2
+ "_meta": {
3
+ "synthetic": true,
4
+ "note": "Mobil sayfada kod tarama (scan-code adımı, kök ADR 0017): 'Barkod okut' düğmesi tam ekran tarayıcıyı açar; okunan değer barkod alanına yazılır, kullanıcı vazgeçerse ya da kamera kullanılamıyorsa error dalı tek bir uyarı gösterir. Tek yeni kavram scan-code adımıdır ve iki dalı da bağlıdır. Sayfa mizanpaj sözleşmesine uyar (MOB-015…021 bulgusuz), akış FLOW-042'den temiz geçer (test/mobile-rules.test.ts, test/mobile-layout-lint.test.ts).",
5
+ "pairsWith": "mobile-app-shell-stack (kabuk), mobile-form-screen (alanın içinde durduğu form düzeni), mobile-list-detail-form (okunan değerle kayıt arama / kaydetme akışı)",
6
+ "sourceRef": "platform_guide('mobile-steps') §Adım sözlüğü — scan-code satırı; FLOW-042; FLOW-022 (metin şablonu)"
7
+ },
8
+ "_rules": {
9
+ "value": "args.value izinli kod türleridir: sözlükten virgüllü DÜZ METİN — ifade, dizi ya da büyük harf değil (FLOW-042). Boş bırakılırsa bütün türler okunur; ürün barkodu için daraltmak yanlış okumayı azaltır. upc_a yoktur: UPC-A etiketi ean13 adıyla ve başında 0 ile on üç hane döner. types / formats / barcodeTypes gibi anahtarlar okunmaz.",
10
+ "title": "args.title tarayıcı ekranındaki yönergedir ve METİN ŞABLONUDUR (FLOW-022): düz metin olduğu gibi çizilir, çeviri için \"{{ t('urun.barkod.okut') }}\" yazılır. Boşsa ekranın kendi yönergesi çizilir.",
11
+ "result": "Sonuç Steps.<adım adı>.data = { value, type, source }. source 'camera' ya da 'manual' (ekrandaki 'Elle gir'); elle girişte adım birden çok tür istiyorsa type null gelir — türe göre dallanacaksan null'u da ele al.",
12
+ "errorBranch": "Kullanıcının tarayıcıyı kapatması bir HATADIR. error dalı bağlı değilse akışın hata akışı (errorSteps) koşar; o da yoksa akış always ile sürer ve yazma adımı Steps.scan.data.value yerine null okur. error dalında Steps.<adım adı>.error = { status, message }: cancelled (kapattı) · denied (kamera izni yok) · unavailable (derleme kamerayı taşımıyor ya da tarayıcı zaten açık) · unsupported (web uygulaması) · invalid (sözlük dışı tür). Buradaki uyarı vazgeçmeyi öteki durumlardan ayırır; error dalı koşunca always KOŞMAZ.",
13
+ "setter": "Okunan değer setter ile alana yazılır (element + property 'value', değer kaynağı code: Steps.scan.data.value). Değeri bir uca gönderecekseniz setter yerine microservice adımının parametresine aynı ifadeyi yazın.",
14
+ "placement": "Tarama düğmesi SAYFADA durur. Özel sekme çubuğunun (tabs.style: custom) bileşenindeki bir düğmeden başlatılan tarama sonuç vermez: tarayıcı açılınca çubuk sökülür ve akışı durur (bilinen açık — koddan okundu, cihazda ölçülmedi: zeyrek-frontend/docs/issues.md ARC-298).",
15
+ "focus": "Tarayıcı kapanınca sayfanın focus akışları koşar (başka ekrandan dönüş bir odaktır) — taramanın devamından SONRA. Odakta alanı sıfırlayan bir akış varsa okunan değeri ezer.",
16
+ "preview": "Önizleme (/dev, page_screenshot) kamera açmaz, telefon çerçevesinin içinde 'kod gir' penceresi çizer. page_screenshot ile sürmek için captureUnready: true ŞARTTIR — akış pencereyi beklerken 'koşuyor' sayılır ve araç onsuz 'akış hâlâ koşuyor' ile düşer: actions [click btn_tara, set __scan-value, click __scan-type-<tür> (çok türlü adımda), click __scan-confirm]; error dalı için __scan-cancel. Pencere açıkken her eylem bekleme bütçesini (15 sn) doldurur. Ölçüldü 2026-10-12: onaydan sonra in_barkod value=8690000000017, txt_kaynak 'ean13 · elle girildi'; vazgeçince runtimeErrors code 'cancelled'.",
17
+ "build": "Kamera izni ve modülü mağaza derlemesine kendiliğinden girer: adım production'daki bir akıştaysa derleme kamerayı taşır. Kamerayı taşımayan derleme (adım derlemeden SONRA deploy edildiyse) unavailable döner; 0.5.0'dan eski kabuk adımı hiç tanımaz. İkisinde de kullanıcıya yeni sürüm gerekir — yayın ön kontrolünün P-14 satırı söyler (platform_guide('mobile-publish'))."
18
+ },
19
+ "pages": [
20
+ {
21
+ "pageCreateArgs": {
22
+ "name": "Ürün sorgula",
23
+ "slug": "urun-sorgula"
24
+ },
25
+ "schemaPatchOps": [
26
+ {
27
+ "op": "add_component",
28
+ "parent": "page_container",
29
+ "component": {
30
+ "elementId": "txt_baslik",
31
+ "name": "txt_baslik",
32
+ "type": "text",
33
+ "kind": "component",
34
+ "props": {
35
+ "content": {
36
+ "type": "static",
37
+ "ref": null,
38
+ "data": "Ürün sorgula"
39
+ },
40
+ "type": {
41
+ "type": "static",
42
+ "ref": null,
43
+ "data": "h2"
44
+ }
45
+ },
46
+ "appearance": {
47
+ "marginType": "none",
48
+ "widthType": "fill"
49
+ },
50
+ "blocks": [],
51
+ "events": [],
52
+ "validations": [],
53
+ "stateBindings": [],
54
+ "children": []
55
+ }
56
+ },
57
+ {
58
+ "op": "add_component",
59
+ "parent": "page_container",
60
+ "component": {
61
+ "elementId": "grp_barkod",
62
+ "name": "grp_barkod",
63
+ "type": "container",
64
+ "kind": "component",
65
+ "props": {
66
+ "direction": {
67
+ "type": "static",
68
+ "ref": null,
69
+ "data": "column"
70
+ },
71
+ "horizontalAlign": {
72
+ "type": "static",
73
+ "ref": null,
74
+ "data": "stretch"
75
+ },
76
+ "gap": {
77
+ "type": "static",
78
+ "ref": null,
79
+ "data": "medium"
80
+ }
81
+ },
82
+ "appearance": {
83
+ "marginType": "none",
84
+ "widthType": "fill",
85
+ "paddingType": "none"
86
+ },
87
+ "blocks": [],
88
+ "events": [],
89
+ "validations": [],
90
+ "stateBindings": [],
91
+ "children": [
92
+ {
93
+ "elementId": "in_barkod",
94
+ "name": "in_barkod",
95
+ "type": "textinput",
96
+ "kind": "component",
97
+ "props": {
98
+ "label": {
99
+ "type": "static",
100
+ "ref": null,
101
+ "data": "Barkod"
102
+ },
103
+ "placeholder": {
104
+ "type": "static",
105
+ "ref": null,
106
+ "data": "Okut ya da yaz"
107
+ }
108
+ },
109
+ "appearance": {
110
+ "marginType": "none",
111
+ "widthType": "fill"
112
+ },
113
+ "blocks": [],
114
+ "events": [],
115
+ "validations": [],
116
+ "stateBindings": [],
117
+ "children": []
118
+ },
119
+ {
120
+ "elementId": "txt_kaynak",
121
+ "name": "txt_kaynak",
122
+ "type": "text",
123
+ "kind": "component",
124
+ "props": {
125
+ "content": {
126
+ "type": "static",
127
+ "ref": null,
128
+ "data": ""
129
+ }
130
+ },
131
+ "appearance": {
132
+ "marginType": "none",
133
+ "widthType": "fill"
134
+ },
135
+ "blocks": [],
136
+ "events": [],
137
+ "validations": [],
138
+ "stateBindings": [],
139
+ "children": []
140
+ },
141
+ {
142
+ "elementId": "btn_tara",
143
+ "name": "btn_tara",
144
+ "type": "button",
145
+ "kind": "component",
146
+ "props": {
147
+ "text": {
148
+ "type": "static",
149
+ "ref": null,
150
+ "data": "Barkod okut"
151
+ }
152
+ },
153
+ "appearance": {
154
+ "marginType": "none",
155
+ "widthType": "fill"
156
+ },
157
+ "blocks": [],
158
+ "events": [],
159
+ "validations": [],
160
+ "stateBindings": [],
161
+ "children": []
162
+ }
163
+ ]
164
+ }
165
+ }
166
+ ],
167
+ "scanFlowCreateArgs": {
168
+ "host": "page",
169
+ "hostId": "<urun-sorgula sayfa id>",
170
+ "name": "barkod okut",
171
+ "slug": "barkod-okut",
172
+ "steps": [
173
+ {
174
+ "stepId": "start",
175
+ "name": "start",
176
+ "type": "start",
177
+ "kind": "normal",
178
+ "args": {},
179
+ "branches": [
180
+ {
181
+ "id": "<GUID>",
182
+ "name": "always",
183
+ "nextStepId": "scan"
184
+ }
185
+ ]
186
+ },
187
+ {
188
+ "stepId": "scan",
189
+ "name": "scan",
190
+ "type": "scan-code",
191
+ "kind": "normal",
192
+ "args": {
193
+ "value": "ean13,ean8,qr",
194
+ "title": "Ürünün barkodunu okut"
195
+ },
196
+ "branches": [
197
+ {
198
+ "id": "<GUID>",
199
+ "name": "always",
200
+ "nextStepId": "write_code"
201
+ },
202
+ {
203
+ "id": "<GUID>",
204
+ "name": "error",
205
+ "nextStepId": "warn"
206
+ }
207
+ ]
208
+ },
209
+ {
210
+ "stepId": "write_code",
211
+ "name": "write_code",
212
+ "type": "setter",
213
+ "kind": "normal",
214
+ "args": {
215
+ "iteratees": [
216
+ {
217
+ "id": "<GUID>",
218
+ "type": "change",
219
+ "source": "component",
220
+ "element": "in_barkod",
221
+ "property": "value",
222
+ "mapping": {
223
+ "source": "",
224
+ "mappings": {}
225
+ },
226
+ "value": {
227
+ "type": "code",
228
+ "ref": "Steps.scan.data.value"
229
+ }
230
+ },
231
+ {
232
+ "id": "<GUID>",
233
+ "type": "change",
234
+ "source": "component",
235
+ "element": "txt_kaynak",
236
+ "property": "content",
237
+ "mapping": {
238
+ "source": "",
239
+ "mappings": {}
240
+ },
241
+ "value": {
242
+ "type": "code",
243
+ "ref": "`${Steps.scan.data.type ?? 'tür bilinmiyor'} · ${Steps.scan.data.source === 'camera' ? 'kameradan' : 'elle girildi'}`"
244
+ }
245
+ }
246
+ ]
247
+ },
248
+ "branches": [
249
+ {
250
+ "id": "<GUID>",
251
+ "name": "always",
252
+ "nextStepId": null
253
+ }
254
+ ]
255
+ },
256
+ {
257
+ "stepId": "warn",
258
+ "name": "warn",
259
+ "type": "alert",
260
+ "kind": "normal",
261
+ "args": {
262
+ "message": "{{ Steps.scan.error.status === 'cancelled' ? 'Kod okunmadı' : 'Kamera kullanılamıyor; barkodu elle yazabilirsin' }}",
263
+ "alertType": "info"
264
+ },
265
+ "branches": [
266
+ {
267
+ "id": "<GUID>",
268
+ "name": "always",
269
+ "nextStepId": null
270
+ }
271
+ ]
272
+ }
273
+ ]
274
+ },
275
+ "bindOps": [
276
+ {
277
+ "op": "set_event",
278
+ "target": "btn_tara",
279
+ "event": "click",
280
+ "eventValue": "<barkod okut akış id>"
281
+ }
282
+ ]
283
+ }
284
+ ]
285
+ }
@@ -88,7 +88,7 @@ arayüzdür**, yetkiyi backend her istekte yeniden denetler.
88
88
 
89
89
  - **Satır metası.** Table-source uçtan dönen satırda korumalı alan varsa
90
90
  `__protected: { <alanSlug>: 'masked' | 'plain' }` bulunur; `hidden` alan satırda hiç yoktur.
91
- `table` · `datatable` · `detailview` · `text` (`record` + `recordField` ile) `masked` alanı
91
+ `datatable` · `detailview` · `text` (`record` + `recordField` ile) `masked` alanı
92
92
  kilitli maske olarak çizer; "Göster" → gerekçe → değer 30 sn görünür, sonra yeniden maskelenir.
93
93
  "Göster" yalnız erişim özeti yüklüyken çıkar: `off`'ta (basit proje hep `off`) hiç çıkmaz —
94
94
  backend her reveal'i reddeder; `shadow`'da çıkar — backend reveal'i yine denetler, yetkisiz
@@ -109,7 +109,7 @@ arayüzdür**, yetkiyi backend her istekte yeniden denetler.
109
109
  maskeli okumak yazmayı kapatmaz. Maskeli hücre düzenlemeye **boş** açılır, yazılan değer gider, boş bırakılan
110
110
  maske gitmez; hücre seçimine girmez, toplu düzenleme maskeli kolonu sunmaz. Korumalı alanı güncellemek için
111
111
  form kurmak zorunlu değildir. Eski değeri görerek düzeltmek gerekiyorsa önce "Göster" (30 sn, editöre ön
112
- doldurulmaz). Bağlı olmayan tabloda ve `table` bileşeninde maskeli hücre düzenlenmez.
112
+ doldurulmaz). Bağlı olmayan tabloda maskeli hücre düzenlenmez.
113
113
  - **Boş değerin gönderilmemesi isteğin kuruluşuna bağlıdır.** Microservice çağrısının parametre
114
114
  bağı boş (`null`/`''`) değeri gövdeye koymaz → saklı değer korunur. Gövdeyi akış kodunda ya da
115
115
  setter göreviyle elle kuruyorsan (`{ tckn: TextInput1.value }`) `null` gider: `canWrite` yoksa
@@ -278,7 +278,7 @@ Yol joker olabilir: `series[].data` kaydı `series.1.data` prop'unu kapsar. Bağ
278
278
 
279
279
  ## Komponent seçimi
280
280
 
281
- "Hangi işe hangi komponent" tablosu ve `table` / `datatable` karşılaştırması:
281
+ "Hangi işe hangi komponent" tablosu ve `datatable` kurulumu:
282
282
  `platform_guide('page')` Kural 1e ve 7e. Kaçınılacak tek şey yarım kurulumdur (süzme
283
283
  arayüzü açık, arkası statik uç). Hazır komponentlerin hiçbiri işi karşılamıyorsa `html`
284
284
  kaçış kapısıdır — yeteneklerini `platform_guide('html')` anlatır; "platform bunu yapamaz"
@@ -453,6 +453,8 @@ Companion'ı adından değil **ana entity'nin `localizationEntityId` alanından*
453
453
  UUIDv7) anahtar doğru kurulmuştur.
454
454
 
455
455
  Gateway bu iki kuralı ENT-006 (PK identity/tip uyumu) ve ENT-007 (enum şekli) olarak denetler.
456
+ `identity: true` olup `properties.generationType` 0/1 taşımayan anahtar ENT-006 **block**'tur: platform
457
+ create'te de update'te de 400 "Please select a valid generation type" döner (38 S-1).
456
458
 
457
459
  > **Çok dilli bir entity TEK adımda silinir.** `entity_delete` ana entity'ye uygulanır, companion
458
460
  > da onunla düşer. (Aynı gün daha erken bir ölçümdeki "önce companion'ı sil" adımı artık geçersiz;
@@ -486,6 +488,20 @@ boş bırakmak geçerli bir tercihtir** (ENT-002 warn'ı bunu hatırlatır, zoru
486
488
  > yolunda bu çeviriyi **platform** yapar; ham PUT atarken **siz yapmazsanız her güncelleme bağı
487
489
  > sessizce siler.**
488
490
 
491
+ Var olan entity'yi `entity_patch` ile uygulamaya bağlamanın çalışan şekli — yamalanan belge
492
+ `entity_get`'in okuma şeklidir, yol `/applications`, öğe `{ applicationId }`:
493
+
494
+ ```jsonc
495
+ // Bağ listesini yazar (var olan bağlar da listede olmalı — replace tam listedir)
496
+ { "op": "replace", "path": "/applications", "value": [{ "applicationId": "<application id>" }] }
497
+ // Tek bağ ekler
498
+ { "op": "add", "path": "/applications/-", "value": { "applicationId": "<application id>" } }
499
+ ```
500
+
501
+ `/applicationIds` yolu belgede yoktur; çıplak guid öğesi platformda 500 `JsonException`, `{ id }`
502
+ öğesi boş `applicationId` ile yanıltıcı "This record is in use. Related table: 'Applications'" verir
503
+ (37 S-1). Gateway üçünü de yukarıdaki şekle çevirir ve `gatewayNormalized` ile söyler.
504
+
489
505
  ## İlişkiler (doğru şekil — 57 alanla doğrulandı)
490
506
 
491
507
  ```jsonc