@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
package/.env.example CHANGED
@@ -51,21 +51,6 @@ REQUEST_TIMEOUT_MS=100000
51
51
  STUDIO_BASE_URL=http://localhost:3000
52
52
  SCREENSHOT_BROWSER_PATH=
53
53
 
54
- # Jeton kasasi (g-09): MCP_STATE_DIR verilirse tok_ referanslari ve test kullanicisi jetonlari
55
- # <dizin>/token-vault.json'a (0600) yazilir ve gateway yeniden baslayinca da cozulur. Yalniz lokal
56
- # hedefte (ORG_API_BASE_URL localhost) yazilir. MCP_TOKEN_TTL_MINUTES referansin omru (dakika).
57
- MCP_STATE_DIR=
58
- MCP_TOKEN_TTL_MINUTES=60
59
-
60
- # asUser: 'test:<ad>' icin test kullanicilari — satiri INSAN yazar, AI .env'i duzenlemez.
61
- # Bicim (JSON, tek satir): uygulamanin kendi giris ucu ve onun bekledigi govde. Ayrinti: docs/ortam.md
62
- # MCP_TEST_USER_OGRENCI={"application":"<uygulama slug>","microservice":"<giris ucu slug>","body":{"email":"…","password":"…"}}
63
-
64
- # bodyFile icin: tanim yazan araclarin (page_create, flow_create, microservice_create, *_patch …)
65
- # govdeyi okuyabilecegi ek dizin. Gateway surecinin calisma dizini her zaman izinlidir; bu dizin
66
- # ek olarak izinli olur. Bos birakilirsa yalniz calisma dizini okunur. Sembolik bag cozulur.
67
- MCP_INPUT_DIR=
68
-
69
54
  # generate-schemas icin swagger adresleri. Bos birakilirsa sirayla denenir:
70
55
  # 1) asagidaki override, 2) {API_BASE_URL}/swagger/v1/swagger.json, 3) localhost:3000 frontend proxy'si
71
56
  ORG_SWAGGER_URL=
package/dist/config.js CHANGED
@@ -35,7 +35,6 @@ export const moduleNames = [
35
35
  "merge",
36
36
  "activity",
37
37
  "revision",
38
- "mobile",
39
38
  ];
40
39
  export const modulePresets = {
41
40
  core: [
@@ -76,7 +75,6 @@ export const modulePresets = {
76
75
  "access",
77
76
  "translation",
78
77
  "revision",
79
- "mobile",
80
78
  ],
81
79
  admin: ["platform", "template", "organization", "deployment", "environment", "merge", "activity"],
82
80
  };
@@ -120,9 +118,6 @@ const environmentSchema = z.object({
120
118
  STUDIO_BASE_URL: z.string().url().optional(),
121
119
  SCREENSHOT_BROWSER_PATH: z.string().min(1).optional(),
122
120
  SCREENSHOT_QUEUE_WAIT_MS: z.coerce.number().int().positive().default(120000),
123
- MCP_INPUT_DIR: z.string().min(1).optional(),
124
- MCP_STATE_DIR: z.string().min(1).optional(),
125
- MCP_TOKEN_TTL_MINUTES: z.coerce.number().int().positive().optional(),
126
121
  });
127
122
  export const environmentKeys = Object.keys(environmentSchema.shape);
128
123
  /**
@@ -231,11 +226,5 @@ export function loadConfig(env) {
231
226
  studioBaseUrl: values.STUDIO_BASE_URL?.replace(/\/+$/, ""),
232
227
  screenshotBrowserPath: values.SCREENSHOT_BROWSER_PATH,
233
228
  screenshotQueueWaitMs: values.SCREENSHOT_QUEUE_WAIT_MS,
234
- inputDirectory: values.MCP_INPUT_DIR,
235
- stateDirectory: values.MCP_STATE_DIR,
236
- tokenTtlMinutes: values.MCP_TOKEN_TTL_MINUTES,
237
- testUsers: Object.fromEntries(Object.entries(env)
238
- .filter((entry) => entry[0].startsWith("MCP_TEST_USER_") && typeof entry[1] === "string" && entry[1] !== "")
239
- .map(([key, value]) => [key.slice("MCP_TEST_USER_".length).toLowerCase(), value])),
240
229
  };
241
230
  }
package/dist/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
3
3
  import { configureLogging, logGatewayEvent } from './observability/trace.js';
4
- import { configureTokenVault } from './platform/tokenVault.js';
5
4
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
6
5
  import { Platform, requestAuthStorage } from './platform/http.js';
7
6
  import { createGatewayServer } from './server.js';
@@ -32,7 +31,6 @@ catch (error) {
32
31
  const platform = new Platform(config);
33
32
  const useStdio = process.argv.includes('--stdio');
34
33
  configureLogging(config, useStdio ? 'stdio' : 'http');
35
- configureTokenVault(config);
36
34
  if (useStdio) {
37
35
  if (config.authMode === 'passthrough') {
38
36
  logger.error('stdio kipi passthrough auth ile kullanılamaz; AUTH_MODE=local yapın.');
@@ -32,7 +32,7 @@ export const TABLE_COLUMN_TYPE_NOTE = "KOLON TİPİ SÖZLÜĞÜ (bekler → çiz
32
32
  "tags: [{ value, color }] dizisi ya da metin dizisi → çok rozet, renk SATIR VERİSİNDEN (serbest hex burada geçer), sığmayanlar +N ile toplanır. " +
33
33
  "action: veri kolonu DEĞİL — aksiyon sütunu ayrı çizilir; kolon tanımında seçilirse ham değer metin olarak görünür.";
34
34
  export const STYLE_GROUP_NOTES = {
35
- surfaceStyle: "{ background, radius, borderTop, borderRight, borderBottom, borderLeft, shadow, contentOverflow(visible|hidden|auto), opacity(0–100 yüzde; 100/boş yazılmaz), rotate(derece, −360…360), scale(YÜZDE 10–500; 100 aynı boy ve varsayılan — 1.1 değil 110), translateX/translateY(px, −1000…1000) }. radius: tema kademesi ('var(--ng-radius-md)'; sm/lg 'Border radius'un oranı, md temanın kendisi), tek uzunluk ('18px') YA DA dört köşe ('90px 14px 14px 14px' — sol-üst, sağ-üst, sağ-alt, sol-alt). KENARLIK YÖN BAŞINADIR: her yön kendi CSS kısayolunu taşır ('1px solid var(--ng-border-primary)'), 'none' o kenarı kapatır; eski borderWidth/borderStyle/borderColor üçlüsü EMEKLİ (yalnız hiçbir yön yazılmamışsa okunur). shadow: çok katmanlı `box-shadow` metni ('0px 2px 5px 0px rgba(0,0,0,.2), inset 8px 5px 7px 0px #000'), tema token'ı ('var(--ng-shadow-md)') ya da eski preset anahtarı (none|sm|md|lg). DÖNDÜR/ÖLÇEKLE/KAYDIR (ADR 0005): dört alan TEK transform'a derlenir — sıra kaydır → döndür → ölçekle, köken merkez, düğüm çocuklarıyla döner; nötr değer (0°, 100, 0 px) hiçbir şey yazmaz. Değer BİRİMSİZ sayıdır ('15deg', '20px', '110%' yazılmaz) ve aralık dışı yazılmaz — mobilde ikisi de nativeStyle.warnings'e düşer. Transform'lu düğümün İÇİNDEKİ portal açmayan açılır menü/liste düğümün kutusuna hapsolur. hoverStyle.lift düğümün transform'unu ezmez, önüne eklenir. Katmanlar (modal, drawer, popover) ve uygulama kabukları (layout, layout-region, layout-shell, auth-layout, qm-content, qm-sidebar) bu dört alanı TAŞIMAZ: transform'lu ata position:fixed katmanı ekrandan koparır. Döndürmeyi serbest style.transform'a yazma — mobil yalnız tipli alanı çizer (MOB-002).",
35
+ surfaceStyle: "{ background, radius, borderTop, borderRight, borderBottom, borderLeft, shadow, contentOverflow(visible|hidden|auto), opacity(0–100 yüzde; 100/boş yazılmaz) }. radius: tema kademesi ('var(--ng-radius-md)'; sm/lg 'Border radius'un oranı, md temanın kendisi), tek uzunluk ('18px') YA DA dört köşe ('90px 14px 14px 14px' — sol-üst, sağ-üst, sağ-alt, sol-alt). KENARLIK YÖN BAŞINADIR: her yön kendi CSS kısayolunu taşır ('1px solid var(--ng-border-primary)'), 'none' o kenarı kapatır; eski borderWidth/borderStyle/borderColor üçlüsü EMEKLİ (yalnız hiçbir yön yazılmamışsa okunur). shadow: çok katmanlı `box-shadow` metni ('0px 2px 5px 0px rgba(0,0,0,.2), inset 8px 5px 7px 0px #000'), tema token'ı ('var(--ng-shadow-md)') ya da eski preset anahtarı (none|sm|md|lg).",
36
36
  typographyStyle: "{ textColor, fontFamily('var(--ng-font)' tema gövde | 'var(--ng-font-heading)' tema başlık | 'monospace' — token tema fontunu izler; serbest Google fontu yazma, tema yüklemez ve sessizce düşer), fontSize(CSS uzunluğu: '15px'|'1.1em'; çıplak sayı px sayılır), fontWeight(400|500|600|700), fontStyle(normal|italic), textDecoration(none|underline|line-through), lineHeight(TEK alan, üç yazım: oran '1.4' | yüzde '140%' | uzunluk '24px'; eski kayıtlardaki çıplak 140 yüzde sayılır), letterSpacing(CSS uzunluğu, negatif meşru: '-0.4px'), textAlign(left|center|right|justify) }. Komponentin içeriğine miras kalır.",
37
37
  hoverStyle: "{ background, borderColor, shadow(none|sm|md|lg), lift(CSS uzunluğu: '2px'), transitionMs }. Hiçbir alan dolu değilse hover bloğu ve geçiş HİÇ yazılmaz.",
38
38
  colorTokens: "RENK ALANLARI (background, textColor, borderColor, hover.background/borderColor) hex kadar tema token'ı da alır: 'var(--ng-accent-primary)', 'var(--ng-status-error)', 'var(--ng-background-inverse)'. Token yazılan alan tema değişince izler, hex yazılan kopar; tasarım sistemi renklerinde ÖNCE token'ı tercih et (studio panelinde aynı seçim tema chip'iyle yapılır).",
@@ -333,7 +333,7 @@ export const componentKnowledge = {
333
333
  "layout:'grid' iken diğer altı prop okunmaz — Studio panelinde de gizlenir. Katalogda tanımlı oldukları için doğrulama duvarı susar: ızgara kapsayıcıya direction yazmak sessizce hiçbir şey yapmaz (gateway PAGE-015 ile uyarır).",
334
334
  "Katalog varsayılanı 2026-08-25'te grid → FLEX oldu ama varsayılan düğüm KURULURKEN props'a yazılır (Studio bırakma yolu definition.defaults'u kopyalar), renderer'da geriye dönük bir etkisi YOKTUR: layout prop'u hiç olmayan düğüm — bütün eski kayıtlar — ızgara çizilmeye devam eder. Yani şemayı sen kuruyorsan varsayılana güvenme, layout'u açıkça yaz.",
335
335
  "YERLEŞİM (layout) KURARKEN DE TERCİH EDİLEN KAPTIR: layout views'ü sıradan bir komponent ağacıdır ve kökü container'dır — Studio'nun yeni layout iskeleti (getDefaultLayout) tam olarak bunu kurar: states.isWrapper:true + appearance.heightType:'fill' + block x0/w12 kök (sayfanın page_container'ının yerleşim ikizi: çerçevesiz, başlıksız, ekranı dolduran), içinde yan menü kabı (w2, fill) + içerik sütunu (w10, fill, direction:'column') → üst şerit + slot (fill). layout-shell/layout-region ikilisi emekliye ayrıldı (platform_guide('layout')).",
336
- "DAR EKRAN TUZAĞI — flex çocukları telefon genişliğinde ALT ALTA DÜŞMEZ: runtime'ın dar ekran kuralı yalnız ızgara özelliklerini ezer (RenderComponent.svelte, @media max-width:767px → grid-row/grid-column/height), flex çocuğu genişliğini getFlexChildStyle'ın flex-basis'inden alır ve flex-wrap:nowrap altında satır bozulmaz. 375px'te ölçüldü: aynı span'lerle ızgara kabın çocukları 375/375px alt alta, flex kabınkiler 49/310px yan yana. Telefonda yığılması gereken satır için layout:'grid' kullan; wrap artık varsayılan açık (2026-09-01) ama sarma yalnız sığmayan çocuğu alta atar, ızgara yığılmasının yerini tutmaz (zeyrek-frontend docs/issues.md ARC-031)."
336
+ "MOBİL TUZAĞI — flex çocukları telefonda ALT ALTA DÜŞMEZ: runtime'ın mobil kuralı yalnız ızgara özelliklerini ezer (RenderComponent.svelte, @media max-width:767px → grid-row/grid-column/height), flex çocuğu genişliğini getFlexChildStyle'ın flex-basis'inden alır ve flex-wrap:nowrap altında satır bozulmaz. 375px'te ölçüldü: aynı span'lerle ızgara kabın çocukları 375/375px alt alta, flex kabınkiler 49/310px yan yana. Telefonda yığılması gereken satır için layout:'grid' kullan; wrap artık varsayılan açık (2026-09-01) ama sarma yalnız sığmayan çocuğu alta atar, ızgara yığılmasının yerini tutmaz (zeyrek-frontend docs/issues.md ARC-031)."
337
337
  ],
338
338
  propNotes: {
339
339
  surfaceStyle: STYLE_GROUP_NOTES.surfaceStyle + ' ' + STYLE_GROUP_NOTES.colorTokens + " Kapsayıcının TABANI tema token'larıdır (--ng-background-surface zemin, 1px --ng-border-primary çerçeve, --ng-rounded köşe); alan boş bırakılırsa tema izlenir. contentOverflow gövdeye ('&__body') yazılır.",
@@ -769,7 +769,7 @@ export const componentKnowledge = {
769
769
  value: "Seçili dosya(lar). Panelde ÇİZİLİR ve akıştan yazılabilir (2026-09-17'ye kadar gizliydi, bu yüzden AI sözlüğünde de görünmüyordu): kayıtlı bir dosyayı forma geri basmanın yolu budur. Tekli kipte nesne, çoklu kipte dizi; boşaltmak için null (çoklu kipte []). Kipiyle uyuşmayan şekil de OKUNUR: çoklu kipte yazılan tek nesne listelenir, tekli kipte yazılan dizinin ilk dosyası çizilir (2026-09-17'ye kadar ikisi de sessizce hiçbir şey göstermiyordu). İçeriği olmayan kayıt (ne value ne data ne absolute_path) hiç çizilmez.",
770
770
  text: 'Butonun üzerindeki metin (varsayılan "Attach File"); ikon sabittir. Alanın başlığı ayrı bir prop\'tur: label.',
771
771
  description: 'Butonun altındaki yardım metni. Dosya seçilir seçilmez yerini dosya listesi alır, seçim temizlenince geri gelir (fileInput ile aynı kural).',
772
- allowedFiletypes: 'İzin verilen uzantılar (çoklu seçim, kapalı liste). Hem dosya diyaloğunun accept süzgecini hem de seçimden SONRAKİ denetimi besler: liste dışı bir dosya seçilirse değere girmez ve komponentin içinde "desteklenmiyor" uyarısı çizilir (2026-09-17; öncesinde yalnız diyalog süzgeciydi, "Tüm dosyalar" ile aşılabiliyordu). Hem allowedFiletypes hem customFiletypes boşsa her tür serbesttir. Akış prop\'a düz metin yazabilir ("pdf,png", customFiletypes ile aynı ayraçlar) — 2026-09-25`e kadar bu komponenti `input.map is not a function` ile düşürüyordu (fileInput okuyordu).',
772
+ allowedFiletypes: 'İzin verilen uzantılar (çoklu seçim, kapalı liste). Hem dosya diyaloğunun accept süzgecini hem de seçimden SONRAKİ denetimi besler: liste dışı bir dosya seçilirse değere girmez ve komponentin içinde "desteklenmiyor" uyarısı çizilir (2026-09-17; öncesinde yalnız diyalog süzgeciydi, "Tüm dosyalar" ile aşılabiliyordu). Hem allowedFiletypes hem customFiletypes boşsa her tür serbesttir.',
773
773
  customFiletypes: 'Listede olmayan uzantılar, serbest metin ("heic, dwg;psd"). Virgül, noktalı virgül ve boşluk ayraçtır; baştaki nokta ve yıldız atılır, büyük/küçük harf fark etmez. allowedFiletypes ile BİRLEŞİR, onun yerine geçmez (fileInput ile aynı ad ve aynı kural). Çok parçalı uzantı da yazılabilir ("tar.gz"): eşleşme adın SONUNA bakar, son noktadan sonrasına değil.',
774
774
  maxFileSizeMb: 'Dosya BAŞINA en büyük boyut (MB). Boş/0/negatif değer sınırsız demektir; aşan dosya değere girmez, uyarı komponentin içinde çizilir.',
775
775
  maxFileCount: "Çoklu kipte toplam dosya sayısı sınırı — zaten seçili olanlar da sayılır. Boş/0/negatif sınırsız. Tekli kipte hiç okunmaz ve panelde de gizlidir (isMultiple'a bağlı).",
@@ -1206,8 +1206,12 @@ export const componentKnowledge = {
1206
1206
  "Öğe bazlı isHidden VARDIR: canlıda elenir, Studio'nun düzenleyici kipinde görünmeye devam eder. Satır anahtarı gizli satırlar ELENDİKTEN SONRA alınır; ham liste üzerinden alınan anahtar ekrandaki başka bir satırı adlandırırdı.",
1207
1207
  "activeHref boşken menü tarayıcıdaki adrese uyan satırı kendi bulur; adres mount'ta BİR KEZ okunur (bağlantılar tam sayfa yükler). Yalnız YAPRAKLAR yarışır — bir dalın kendi href'i asla aktif satır olmaz.",
1208
1208
  "defaultExpanded 'active' iken ilk yüklemede aktif sayfaya giden dallar, drill panelleri ve alt drawer'lar AÇIK gelir; arama yapılırken tüm modlar geçici olarak yerinde ağaca döner ve eşleşen dallar her seviyede açılır.",
1209
- 'Menü olay YAYMAZ (şemada yalnız mount); gezinme ham <a href> ile tam sayfa yükleyerek olur.',
1210
- "KAPSAYICIDIR (isDroppable, 2026-09-24): layout gibi içine komponent bırakılır ve bırakılan çocuklar menünün YANINDA/ALTINDA kendi içerik alanında çizilir, menü satırlarının arasında değil. Akış yönünü düzen belirler (navigation-content.ts): sidebar/hybrid/dual menü solda içerik sağda — sidebarSide right ise ters; toc tocPosition ile; header/tabs/breadcrumb içerik altta; footer/bottom ve dock bottom içerik üstte (dock top altta); drawer/command/launcher akışta yer kaplamaz, içerik alanı tek başına kalır. İçerik alanı navigations dizisi BOŞKEN de durur — son menü öğesi silinince bırakılan bileşenler kaybolmaz.",
1209
+ "Aktif satır (ve kırıntı yolu) activeHref boşken adresten bulunur; uygulamanın KÖK adresinde (slug'sız, ana sayfa çizilirken) adres yerine çizilen sayfanın slug'ı kullanılır — ana sayfa menüde seçili, kırıntı yolunda adıyla görünür.",
1210
+ "Menü satırları olay YAYMAZ; gezinme ham <a href> ile tam sayfa yükleyerek olur. Tek olay 'logout'tur: sidebar düzeninin alt kısmındaki kullanıcı menüsünde 'Çıkış yap'a basılınca yayılır (veri taşımaz) — oturumu kapatan akış buna bağlanır (layout bileşeninin logout'uyla aynı sözleşme).",
1211
+ "sidebar düzeninde MARKA ve KULLANICI bileşenin içindedir (2026-09-25): başlık bandı logo/ikon + title + subTitle çizer (logo kutusu uygulamanın ana sayfasına giden bir bağlantıdır — kırıntı yolunun ev halkasıyla aynı hedef), collapsible açıksa daraltma düğmesi bandın SAĞINDADIR (şeritte alt kısma iner). Alt kısım userName/userEmail doluysa kullanıcı düğmesidir (avatar ya da baş harfler, ad, e-posta); tıklanınca yukarı doğru bir panel açılır: ad, e-posta, userPosition, userRole ve 'Çıkış yap'. Panel Escape ve dış tıklamayla kapanır (dismiss-chain). Daraltınca marka, arama ve kullanıcı birlikte şeride iner.",
1212
+ "Daraltma durumu tarayıcıda (localStorage, 'ng-nav-collapsed:<elementId>') saklanır ve SONRAKİ SAYFADA okunur — menü linkleri tam sayfa yüklediği için bellekteki durum her tıklamada kayboluyordu. Tuvalde okunmaz.",
1213
+ "Menü içi arama bir ALAN gibi çizilir (kenarlık + açık zemin + '/' ipucu); sayfada yazı alanı dışındayken '/' aramaya odaklanır (kısayolun sayfada tek sahibi vardır). command düzeninin tetikleyicisi de alan görünümündedir ve Ctrl K / ⌘ K ipucu taşır; iki yerde de yer tutucu searchPlaceholder'dır.",
1214
+ "KAPSAYICIDIR (isDroppable, 2026-09-24): layout gibi içine komponent bırakılır ve bırakılan çocuklar menünün YANINDA/ALTINDA kendi içerik alanında çizilir, menü satırlarının arasında değil. Akış yönünü düzen belirler (navigation-content.ts): sidebar/hybrid/dual menü solda içerik sağda — sidebarSide right ise ters; toc tocPosition ile; header/tabs/breadcrumb içerik altta; footer/bottom ve dock bottom içerik üstte (dock top altta); drawer/command/launcher akışta yer kaplamaz, içerik alanı tek başına kalır. İçerik alanı navigations dizisi BOŞKEN de durur — son menü öğesi silinince bırakılan bileşenler kaybolmaz. İçine hiçbir şey bırakılmamışsa alan akıştan çıkar (menünün yanına boşluk düşmez); kenara yapışık panelde (sidebar/hybrid/dual, floating değil) menü ile içerik arasında boşluk YOKTUR, panelin kenarlığı ayırır. Bırakılan içerik alanın boyunu doldurur (fill zinciri) — yan menü + içerik sütunu kabuğu tek navigations ile kurulabilir. Kanvasta çocukların çizilmesi için kayıtlı düğümde states.isDroppable true olmalı.",
1211
1215
  "sidebar ve layout bileşenleri bu ağacı GÖMÜLÜ kipte mount eder (embedded): panel kromunu — genişlik, kenarlık, zemin, logo, kullanıcı alanı — o kabuklar çizer, navigations ikinci kez çizmez.",
1212
1216
  "Satır görünümü TİPLİ navigationStyle grubuyla verilir (layout ile aynı grup); serbest style'ın &__item, &__item--active ve &__icon parçaları gruptan SONRA gelir (son söz).",
1213
1217
  'Karar kaydı: zeyrek-frontend/docs/adr/0001-navigation-duzen-kaydi.md.'
@@ -1234,7 +1238,7 @@ export const componentKnowledge = {
1234
1238
  width: "Panel genişliği (px, 160–480). Rail modunda YOK SAYILIR — orada genişlik satır yüksekliği ve ikon kutusundan hesaplanır (navigation-tokens.ts railWidth). Drawer menü tipinde de okunmaz; onun genişliği drawerWidth’tir.",
1235
1239
  collapsible: "Panelin başlığına daraltma düğmesi koyar; ziyaretçi menüyü rail’e indirip geri açabilir. Rail zaten daraltılmış olduğu için orada, drawer’da da anlamsızdır.",
1236
1240
  levelStyle: "Derin seviyelerin TİPOGRAFİSİ: 'uniform' her seviye aynı punto; 'tapered' 2. seviyeden itibaren satır kısalır ve yazı bir tık küçülüp soluklaşır, böylece derinlik göz kararıyla okunur. Girintiyi DEĞİŞTİRMEZ (o indent/maxIndent’in işi). Rail’de karşılığı yoktur.",
1237
- nestedStyle: "Çocuğu olan satır nasıl çizilsin: 'indent' normal bir satırdır (açılır/kapanır); 'subheader' satır olmaktan çıkıp küçük bir GRUP BAŞLIĞI olur ve çocukları hep açık durur. Yalnız variant flat iken anlamlıdır — akordiyonda üst öğe zaten tıklanabilir bir satırdır.",
1241
+ nestedStyle: "Çocuğu olan satır nasıl çizilsin: 'indent' normal bir satırdır (açılır/kapanır); 'subheader' küçük bir BÖLÜM BAŞLIĞI olur ve çocukları hep açık durur. Düz menüde her dal böyledir. Akordiyonda (2026-09-25) yalnız ADRESSİZ ve isExpandable İŞARETLENMEMİŞ iç öğe başlığa döner, çocukları başlıkla aynı hizada çizilir — «Yetki yönetimi» (isExpandable: true) açılır modül kalır, içindeki «Organizasyon» etikettir. Varsayılan 'indent' kayıtlı menüleri değiştirmez.",
1238
1242
  hMenuStyle: "Header’da bir üst öğenin alt menüsü nasıl açılır: 'dropdown' altında küçük panel; 'mega' çubuğun tamamı boyunca çok kolonlu panel; 'tabs' alt öğeler ikinci bir satırda sekme çubuğu olur (açılır panel yok). Yalnız header düzeninde geçerlidir.",
1239
1243
  hGroupStyle: "Kök GRUPLARIN çubuğa nasıl dizileceği: 'inline' grupların çocukları yan yana tek çubuğa serilir (grup başlığı çizilmez); 'menus' her grup kendi açılır menüsü olur. header ve hybrid düzenlerinde.",
1240
1244
  hAlign: "Çubuktaki öğelerin yatay hizası: 'start' | 'center' | 'end'. header ve hybrid düzenlerinde.",
@@ -1259,7 +1263,7 @@ export const componentKnowledge = {
1259
1263
  crumbSeparator: "Parçaları ayıran işaret: 'chevron' | 'slash' | 'dot'. Yalnız breadcrumb düzeninde geçerlidir.",
1260
1264
  crumbSiblings: "Her parçaya, o seviyedeki KARDEŞ sayfaların açılır listesini ekler — ziyaretçi yolu terk etmeden yan dala geçebilir. Yalnız breadcrumb düzeninde geçerlidir.",
1261
1265
  crumbMax: "Gösterilecek en fazla parça (0–6; 0 = sınırsız). Aşan ortadaki parçalar tek bir \"…\" düğmesinde toplanır; kök ve son parça her zaman görünür. Yalnız breadcrumb düzeninde geçerlidir.",
1262
- crumbHome: "Yolun başına ev ikonu koyar. Yalnız breadcrumb düzeninde geçerlidir.",
1266
+ crumbHome: "Yolun başına uygulamanın ana sayfasına giden tıklanabilir bir ev ikonu koyar; ardından ayırıcı gelir. Yalnız breadcrumb düzeninde geçerlidir.",
1263
1267
  tocMarker: "Aktif başlığın işareti: 'line' sol kenarda çizgi; 'dot' nokta. Yalnız toc düzeninde geçerlidir.",
1264
1268
  tocDepth: "Listede gösterilecek seviye sayısı (2–4). Daha derin başlıklar hiç listelenmez. Yalnız toc düzeninde geçerlidir.",
1265
1269
  tocPosition: "İçindekiler listesinin ekrandaki yanı: 'right' | 'left'. Yalnız toc düzeninde geçerlidir.",
@@ -1277,7 +1281,17 @@ export const componentKnowledge = {
1277
1281
  expandOnHover: "Fareyle üzerine gelince dalı açar. Yalnız KATMAN açan dallarda geçerlidir (flyout/cascade) — yerinde açılan ağaçta ve drill panelinde yok sayılır, pasif satırda da çalışmaz (navigation-nesting.ts shouldOpenOnHover). ≤768px’te otomatik kapanır.",
1278
1282
  highlightParent: "Aktif sayfaya giden ÜST dalları da vurgular (kalın yazı + vurgu rengi ikon), böylece kapalı bir dalın içinde bulunduğun anlaşılır. Kapatılırsa yalnız yaprağın kendisi vurgulanır.",
1279
1283
  animated: "Menünün hareketini açar/kapatır: dalın açılma kaydırması, chevron dönüşü ve satır renk geçişleri. Kapatıldığında ÜÇÜ birden durur. Ziyaretçinin işletim sistemi hareket azaltma tercihi (prefers-reduced-motion) bu proptan BAĞIMSIZ ve koşulsuz okunur — prop açık olsa da hareket çizilmez.",
1280
- showHeader: "Panelin üst bandı: menü adı, daraltma düğmesi ve drill başlığı buraya çizilir. Kapatılırsa menü doğrudan satırlarla başlar.",
1284
+ showHeader: "Panelin üst bandı: marka (logo/ikon + title + subTitle), daraltma düğmesi ve drill başlığı buraya çizilir. Kapatılırsa menü doğrudan satırlarla başlar ve daraltma düğmesi alt kısma iner.",
1285
+ title: "Marka adı (panelin başlık bandı; header/hybrid çubuğunda ve drill kök başlığında da kullanılır). Boşsa sözlükteki 'Ana menü' yazılır.",
1286
+ subTitle: 'Marka adının altındaki küçük satır; boşsa çizilmez.',
1287
+ icon: "Logo yokken marka kutusunda çizilen ikon; boşsa 'menu'.",
1288
+ logo: 'Marka görseli; verilirse ikonun yerine 32px kutuya sığdırılır.',
1289
+ userName: "Alt kısımdaki kullanıcı düğmesinin adı. userName ve userEmail ikisi de boşsa kullanıcı düğmesi çizilmez. Genelde layout'un mount akışında setComponentProperty ile doldurulur.",
1290
+ userEmail: 'Kullanıcı düğmesinde adın altındaki satır.',
1291
+ userAvatar: 'Kullanıcı görseli; boşsa addan (yoksa e-postadan) baş harfler çizilir.',
1292
+ userPosition: "Kullanıcı menüsünde 'Pozisyon' satırı; boşsa çizilmez.",
1293
+ userRole: "Kullanıcı menüsünde 'Rol' satırı; boşsa çizilmez.",
1294
+ searchPlaceholder: "Menü içi aramanın ve command tetikleyicisinin yer tutucusu; boşsa sözlükteki 'Menüde ara'.",
1281
1295
  showSearch: "Menünün içine arama kutusu koyar. Arama açıkken TÜM modlar geçici olarak yerinde ağaca döner ve eşleşen dallar her seviyede açılır; eşleşme Türkçe’ye duyarlıdır (toLocaleLowerCase(\"tr\") — \"İ/ı\" doğru eşleşir).",
1282
1296
  showFooter: "Panelin alt bandı (kullanıcı alanı vb.). Kapatılırsa liste panelin dibine kadar uzar.",
1283
1297
  showBadges: "Öğelerin badge alanını çizer. Rail modunda rozetin yazacak yeri olmadığı için sayı tek bir NOKTAYA iner.",
@@ -1290,7 +1304,7 @@ export const componentKnowledge = {
1290
1304
  guideLines: "Çocuk listesinin soluna, ÜST satırın ikon ekseninde dikey bir kılavuz çizgisi çizer; aktif sayfaya giden kol vurgu renginde olur. Rail’de çizilecek girinti olmadığı için basılmaz.",
1291
1305
  dividers: "Kök GRUPLAR arasına ayraç çizgisi koyar. Grup içi satırları ayırmaz.",
1292
1306
  iconSize: "Satır ikonunun boyutu (px, 12–32). Yalnız MENÜ ikonunu ölçekler; chevron, taşma oku ve dış bağlantı işareti yazıyla birlikte ölçeklenmeye devam eder. iconContainer \"tile\" ise ikon kutusu bu değerin 12px fazlasıdır.",
1293
- iconContainer: "İkonun zemini: 'none' yalın ikon; 'tile' ikonun çevresine yuvarlak köşeli bir kutucuk. Kutucuk rail genişliğini de büyütür.",
1307
+ iconContainer: "İkonun zemini: 'none' yalın ikon; 'tile' her ikonun çevresine yuvarlak köşeli bir kutucuk; 'branch' kutucuk yalnız alt öğesi olan (modül) satırlarında, yapraklar yalın kalır. Kutucuk rail genişliğini de büyütür.",
1294
1308
  childIcons: "Alt seviye satırlarda da ikon gösterir. Kapalıyken ikon yalnız kök seviyede çizilir (rail hariç — orada her satır ikonuyla vardır). Sadece-metin modunda anlamsızdır.",
1295
1309
  chevron: "Dalın açılır göstergesi: 'chevron' ok; 'caret' üçgen (açılınca DÖNER, ikon değişmez); 'plus' artı/eksi (yönsüz olduğu için durumu ikonun KENDİSİ söyler: kapalı '+', açık '−'). Düz menüde dal zaten açık olduğu için gösterge yoktur.",
1296
1310
  chevronPosition: "Göstergenin satırdaki yeri: 'end' etiketin sonunda; 'start' ikondan önce. Yalnız YERİNDE açılan dallarda çizilir — katman/panel açan dal onun yerine alt öğe sayısını ve sağ oku gösterir.",
@@ -1310,13 +1324,13 @@ export const componentKnowledge = {
1310
1324
  behavior: [
1311
1325
  "Gövdesi BLOK akışındadır ve çocukları 12 kolonluk IZGARA yerleştirir — hem runtime'da hem Studio tuvalinde. Gövde satır flex'i olsaydı renderer'ın çocuk sarmalayıcısı içerik genişliğine çökerdi (ölçüldü: runtime'da 1200px yerine 213px, tuvalde 0). Yani bölgelerin konumu dock prop'undan DEĞİL, blocks[]'ten gelir.",
1312
1326
  "sidebarOpen CANLI DURUMDUR: prop'un kendisi durumdur, toggle onu doğrudan yazar (qm-sidebar'ın isCollapsed deseni). Bir Durum değişkenine bağlanabilir ya da change-property akışıyla çevrilebilir.",
1313
- "mobileBehavior yalnız yan panelin dar ekranda AÇIK BAŞLAYIP başlamayacağını belirler (resolveInitialSidebarOpen: drawer → kapalı, stack → açık). 2026-08-28'e kadar var olan 'stack ise gövdeyi column yap' CSS kuralı, gövde blok akışına geçtiği için KALDIRILDI — artık yerleşimi ızgara belirliyor.",
1327
+ "mobileBehavior yalnız yan panelin mobilde AÇIK BAŞLAYIP başlamayacağını belirler (resolveInitialSidebarOpen: drawer → kapalı, stack → açık). 2026-08-28'e kadar var olan 'stack ise gövdeyi column yap' CSS kuralı, gövde blok akışına geçtiği için KALDIRILDI — artık yerleşimi ızgara belirliyor.",
1314
1328
  "isViewportFilling / isShellRoot yetenekleri ve states.isShell bayrağı BACKEND'DE SAKLANMAZ (components tablosunda sütun yok, States kaydında isShell yok — zeyrek-frontend docs/issues.md BE-002). Davranışı tip adına düşen yedek listeler taşır; şema yazarken bu bayraklara güvenme."
1315
1329
  ],
1316
1330
  propNotes: {
1317
1331
  surfaceStyle: NODE_SURFACE_NOTE,
1318
1332
  sidebarOpen: 'Yan panelin açık olup olmadığı; canlı durumdur, bağlanabilir.',
1319
- mobileBehavior: "'drawer' (varsayılan; dar ekranda kapalı başlar) | 'stack' (dar ekranda açık başlar).",
1333
+ mobileBehavior: "'drawer' (varsayılan; mobilde kapalı başlar) | 'stack' (mobilde açık başlar).",
1320
1334
  style: "&__body ve &__overlay parçaları uygulanır."
1321
1335
  }
1322
1336
  },
@@ -1553,8 +1567,6 @@ export const componentKnowledge = {
1553
1567
  'aggregate/sort/topN/nullMode veriyi çizimden ÖNCE dönüştürür — aynı işi akışta yapmana gerek yok.',
1554
1568
  'isDrillDown:true iken veri noktasına tıklamak kırıntı seviyesi ekler; dataPointClick yükü kaynak satırın TAMAMINI (row) taşır, böylece alt seviye sorgusu tek olayla kurulur. Geri dönüş breadcrumbClick ile bildirilir ve yeniden sorgulamak akışın işidir.',
1555
1569
  'isExportable "tablo olarak gör" + dışa aktarma araç çubuğunu açar; showRefresh refresh olayını yayan bir yenile düğmesi ekler.',
1556
- "OLAYLAR (2026-09-25 düzeltmesi): nokta tıklaması YALNIZ dataPointClick, kategori ekseninin etiketine basış YALNIZ axisLabelClick yayar (yatay çubukta etiketler y eksenindedir). Öncesinde axisLabelClick her nokta tıklamasında noktanın değeriyle geliyor, etikete basış ise hiç gelmiyordu: triggerEvent eksen yerine axisLabel altındaydı ve olay sorgusu nesne olduğu için süzmüyordu. Hedef çizgisi, bant ve eksen başlığı tıklaması olay değildir; selectionChange yalnız selectionMode açıkken gelir (kapalıyken ECharts'ın boş seçimi self.selected'ı eziyordu); dataPointClick.row kategori adıyla bulunur (nullMode skip sonrası kayan dataIndex yanlış satırı veriyordu). Eşleme mobil chart ile ortak (chart-events.ts).",
1557
- 'BOZUK BAĞLI VERİ (2026-09-25): series/rows beklenmedik şekilde gelirse (dizi yerine metin, okunamayan alan) grafik düşmez — veri çözülemezse emptyText, seçenek kurulamazsa önceki çizim kalır. Eksen başlığı (categoryAxis.title / valueAxis.title) varsayılan ızgarada tuvalden taşar ve görünmez (ARC-129).',
1558
1570
  "PASTA/HALKA ETİKETİ (2026-08-27 düzeltmesi): dataLabels.show artık pie/donut'a da geçiyor. Öncesinde prop bu dala hiç ulaşmıyordu ve dilim etiketleri HİÇBİR ayarla kapatılamıyordu — çekme çizgileri legend'in üstüne bindiği için halka fiilen kullanılamıyordu. Temiz halka için dataLabels:{ show:false } yaz.",
1559
1571
  'LEGENDDEN SERİ GİZLEME ÇALIŞIYOR (2026-09-16 düzeltmesi): legend tıklaması artık kalıcı. Öncesinde tıklama ~8 ms sonra kendiliğinden geri alınıyordu — olay yükü prop’lara yazılıyor, tepkime notMerge çizim yapıyor ve ECharts’ın legend.selected durumu siliniyordu. Seçim çizimden önce option’a geri yazılıyor; listede olmayan seri adları elenir.',
1560
1572
  'YATAY + İKİNCİL EKSEN ÇİZİLİYOR (2026-09-16 düzeltmesi): orientation:"horizontal" ile bir seride axis:"secondary" birlikte verilince grafik SESSİZCE boş çiziliyordu — yatayda değer eksenleri xAxis dizisindedir, yAxis tek kategori eksenidir. Kombo grafiği artık iki yönelimde de kurulabilir.',
@@ -121,7 +121,7 @@ export const componentEventContracts = {
121
121
  toggle: { payload: { sidebarOpen: 'boolean (panelin YENİ durumu)' }, reads: 'self.sidebarOpen; params verildiği için event.data.sidebarOpen de dolar' }
122
122
  },
123
123
  'layout-region': { mount: NO_DATA },
124
- navigations: { mount: NO_DATA },
124
+ navigations: { mount: NO_DATA, logout: NO_DATA },
125
125
  // `change` her tuşta (input), `changed` değer kesinleşince (native change: blur/Enter) yayılır;
126
126
  // ikisi de setter'dır — pahalı akışı (sorgu, kayıt) `changed`'e bağla.
127
127
  textinput: {
@@ -454,11 +454,11 @@ export const componentEventContracts = {
454
454
  },
455
455
  chart: {
456
456
  mount: NO_DATA,
457
- dataPointClick: { payload: { name: 'string (kategori); scatter grafiğinde x değeri', seriesName: 'string', value: 'number | null; scatter grafiğinde y', dataIndex: 'number (çizimdeki sıra)', row: 'kaynak satırın TAMAMI | null' }, reads: 'YALNIZ seri noktasına basışta; eksen etiketi tıklaması axisLabelClick’tir (2026-09-16 öncesinde o da boş bir dataPointClick üretiyordu), hedef çizgisi (referenceLines) ve bant (plotBands) olay değildir. row kategori adıyla — scatter’da x değeriyle — bulunur (nullMode skip ya da huni sıralaması dataIndex`i kaydırır); kategorisi olmayan noktada (histogram kutusu, radar serisi, funnelflow düğümü/bağı) null. scatter noktası ECharts tarafında [x, y, size] dizisidir, yük tek değere indirgenir: name = x, value = y' },
457
+ dataPointClick: { payload: { name: 'string (kategori); scatter grafiğinde x değeri', seriesName: 'string', value: 'number | null; scatter grafiğinde y', dataIndex: 'number', row: 'kaynak satırın TAMAMI | null' }, reads: 'YALNIZ seri noktasında yayılır; eksen etiketi tıklaması axisLabelClick’tir (2026-09-16 öncesinde o da boş bir dataPointClick üretiyordu). scatter noktası ECharts tarafında [x, y, size] dizisidir, yük tek değere indirgenir: name = x, value = y' },
458
458
  breadcrumbClick: { payload: { filterValue: 'hedef seviyenin filtre değeri (kök seviyede undefined)' } },
459
- selectionChange: { payload: { selected: 'seçili veri noktaları' }, reads: "yalnız selectionMode 'single'/'multi' iken; 'none' iken ECharts'ın her tıklamada attığı boş seçim yayılmaz (2026-09-25 öncesi yayılıyordu)" },
459
+ selectionChange: { payload: { selected: 'seçili veri noktaları' } },
460
460
  legendClick: { payload: { name: 'string (seri adı)', selected: 'seri → görünürlük haritası' } },
461
- axisLabelClick: { payload: { value: 'string (tıklanan eksen etiketi)' }, reads: 'yalnız kategori ekseninin etiketine basışta; kategori ekseni hangi eksene düşerse oradan gelir — yatay yönelimde yAxis (2026-09-16 öncesinde yatayda hiç yayılmıyordu). Nokta tıklaması ve eksen başlığı (categoryAxis.title) bu olayı yaymaz — 2026-09-25 öncesinde nesne sorgusu süzmediği için her nokta tıklamasında noktanın değeriyle yayılıyordu' },
461
+ axisLabelClick: { payload: { value: 'string (tıklanan eksen etiketi)' }, reads: 'Kategori ekseni hangi eksene düşerse oradan gelir — yatay yönelimde yAxis (2026-09-16 öncesinde yatayda hiç yayılmıyordu)' },
462
462
  zoom: { payload: { start: 'number | null (görünen aralığın başlangıç yüzdesi)', end: 'number | null (bitiş yüzdesi)', startValue: 'number | null (eksenin kendi birimindeki mutlak alt sınır)', endValue: 'number | null (mutlak üst sınır)' }, reads: 'Yük normalize edilir: ECharts iki dataZoom bileşeniyle alanları batch içine koysa da akış her zaman bu dört alanı görür. Aralığı sorguya çevirecekseniz yüzdeleri değil startValue/endValue kullanın (2026-09-16)' },
463
463
  refresh: NO_DATA,
464
464
  retry: NO_DATA
@@ -725,15 +725,8 @@ export const eventPayloadNote = "Olay yükü motorda komponentin KENDİ prop'una
725
725
  "payload boşsa olay veri taşımaz; 'reads' verilmişse değer yükte değil komponentin başka bir prop'undadır. " +
726
726
  "self yerine value { type:'property', ref:{ source:'component', element:'<elementId>', property:'<prop>' } } de kullanılabilir. " +
727
727
  "payload alanı yoksa yük sözlüğe henüz işlenmemiştir; tahmin etmeyin.";
728
- /**
729
- * `platformContracts` platforma özgü sözlüktür (mobilde `mobileComponentEvents.ts`): aynı olayda
730
- * web sözleşmesinin ÖNÜNE geçer, web'de karşılığı olmayan bileşenin tek kaynağıdır.
731
- */
732
- export function eventContractsFor(slug, names, platformContracts) {
733
- const own = (source) => slug !== undefined && source !== undefined && Object.hasOwn(source, slug) ? source[slug] : undefined;
734
- const web = own(componentEventContracts);
735
- const platform = own(platformContracts);
736
- const contracts = web || platform ? { ...web, ...platform } : undefined;
728
+ export function eventContractsFor(slug, names) {
729
+ const contracts = slug ? componentEventContracts[slug] : undefined;
737
730
  return names.map(name => {
738
731
  const contract = contracts?.[name];
739
732
  if (!contract)
@@ -1,8 +1,7 @@
1
1
  import { logStep } from '../observability/trace.js';
2
2
  import { PlatformApiError } from '../platform/errors.js';
3
3
  import { ensureWorkflowErrorPath } from './errorPath.js';
4
- import { DOCUMENT_STYLE_TYPES, TYPED_STYLE_COMPONENTS } from './styleResolve.js';
5
- import { STEP_ALIASES } from './platform/profile.js';
4
+ import { TYPED_STYLE_COMPONENTS } from './styleResolve.js';
6
5
  const SORT_DEFAULT = { name: 'sortField', type: 'sortType', source: 'query-string' };
7
6
  const LIMIT_DEFAULT = { name: 'limit', offset: 'offset', source: 'query-string' };
8
7
  function asArray(value) {
@@ -270,11 +269,12 @@ export function normalizeWorkflowDefinition(definition) {
270
269
  */
271
270
  /**
272
271
  * Adım tipi sözlüğü backend'in, KOŞAN tipler barındırıcının: motor yedi tip register eder, web
273
- * `Runtime.svelte` altı, mobil `runtime-tasks.ts` yedi tip ekler ve aynı sayfa kaydı ikisinde de
272
+ * `Runtime.svelte` altı, mobil `ScreenRuntime.tsx` beş tip ekler ve aynı sayfa kaydı ikisinde de
274
273
  * koşar. Register edilmemiş bir tipe gelen akış sessizce ölür (`flow-engine.ts` `process()`
275
274
  * callback bulamazsa next/completed/stopped hiç çağrılmaz), üstelik `run()` promise'i
276
275
  * çözülmediği için `__ngAppsFlowActivity.running` sıfıra inmez. İki hâl tamamen türetilebilir
277
- * olduğu için lint değil NORMALİZASYON; `FLOW-023` kalan hâlleri uyarır.
276
+ * olduğu için lint değil NORMALİZASYON — kalanı (`confirm`/`download` mobilde yok) yalnız
277
+ * uygulama tarafında kapanabilir, `FLOW-023` onları uyarır.
278
278
  */
279
279
  function normalizeFlowHostGaps(steps, list) {
280
280
  const fixes = [];
@@ -288,15 +288,12 @@ function normalizeFlowHostGaps(steps, list) {
288
288
  .map(step => String(step.stepId ?? ''))
289
289
  .filter(id => id.length > 0));
290
290
  for (const step of list) {
291
- // navigate ile redirect'in argümanı AYNI ({ path, pageId, query, targetBlank, presentation })
292
- // ama navigate'i yalnız mobil register eder; redirect ise iki barındırıcıda da koşar (mobilde
293
- // aynı navigate uygulamasına eşlenir), yani redirect kesin üstün. `presentation`
294
- // (push | modal | replace) yalnız mobil barındırıcıda okunur; web yok sayar.
295
- const alias = STEP_ALIASES[String(step.type ?? '')];
296
- if (alias) {
297
- const original = String(step.type);
298
- step.type = alias;
299
- fixes.push(`'${label(step)}': type ${original} → ${alias} yazıldı — argüman şekli aynı ({ path, pageId, query, targetBlank, presentation }) ama ${original}'i web barındırıcısı register etmez, ${alias} ikisinde de koşar`);
291
+ // navigate ile redirect'in argümanı AYNI ({ path, pageId, query, targetBlank }) ama navigate'i
292
+ // yalnız mobil register eder; redirect ise iki barındırıcıda da koşar (mobilde aynı navigate
293
+ // uygulamasına eşlenir), yani redirect kesin üstün.
294
+ if (step.type === 'navigate') {
295
+ step.type = 'redirect';
296
+ fixes.push(`'${label(step)}': type navigate → redirect yazıldı — argüman şekli aynı ({ path, pageId, query, targetBlank }) ama navigate'i web barındırıcısı register etmez, redirect ikisinde de koşar`);
300
297
  }
301
298
  if (endStepIds.size === 0)
302
299
  continue;
@@ -653,44 +650,6 @@ export function styleKeyNote(fixed) {
653
650
  + 'değer kaydedilir ama hiç uygulanmaz ve tek işareti tarayıcı konsoludur (F-06). '
654
651
  + "Seçici anahtarlara ('&__body', ':hover', '--token') dokunulmadı."];
655
652
  }
656
- /**
657
- * Kabın `states.isDroppable`ı KATALOĞUN kararıdır. Gateway düğümü kurarken bu alanı
658
- * yazmıyordu (`DEFAULT_STATES` hepsini `false` bırakır) ve Studio kanvası düğümü kap saymıyordu:
659
- * imleç `scroll-view`un İÇİNDEYKEN bırakma planı sayfa kabına düşüyor, çizgi kabın üst kenarına
660
- * çiziliyordu (ölçüldü 2026-09-21, `test/mobile app` › `Satislar`). Aynı tipi paletten ekleyen
661
- * Studio katalog değerini yazdığı için tek şemada iki türlü davranan kap oluyordu.
662
- *
663
- * Değer uygulamanın KENDİ sözlüğünden gelir (`listComponents`; mobil uygulamada `type=mobile`
664
- * satırları), yani kural platformdan bağımsızdır ve kuruluşun kendi komponentlerini de kapsar.
665
- * Yalnız `true` yazılır: katalog bir tipi kap saymıyorsa düğüme dokunulmaz.
666
- */
667
- export function normalizeDroppableStates(nodes, droppable) {
668
- const fixed = [];
669
- if (droppable.size === 0)
670
- return fixed;
671
- for (const node of asObjects(nodes)) {
672
- const type = typeof node.type === 'string' ? node.type : '';
673
- if (droppable.get(type) === true) {
674
- // `states` normalde `normalizeComponentDefaults`tan geçmiş olur; gelmediyse burada da
675
- // eksiksiz kurulur — yarım `states` platformda NullReference üretir.
676
- if (!node.states || typeof node.states !== 'object' || Array.isArray(node.states)) {
677
- node.states = { ...DEFAULT_STATES };
678
- }
679
- const states = node.states;
680
- if (states.isDroppable !== true) {
681
- states.isDroppable = true;
682
- fixed.push(`'${String(node.elementId ?? node.name ?? type)}' (${type})`);
683
- }
684
- }
685
- fixed.push(...normalizeDroppableStates(node.children, droppable));
686
- }
687
- return fixed;
688
- }
689
- export function droppableStatesNote(fixed) {
690
- return fixed.length === 0
691
- ? []
692
- : [`states.isDroppable katalogdan yazıldı (${fixed.join(' · ')}) — kataloğun kap saydığı düğüm Studio kanvasında da kap olmalı; alanı bilerek kapatmak için resize_component/set_states kullanın`];
693
- }
694
653
  export function normalizeComponentDefaults(nodes) {
695
654
  const fixed = [];
696
655
  for (const node of asObjects(nodes)) {
@@ -925,11 +884,11 @@ export function pageContainerNode() {
925
884
  * çekilirlerse sayfa içeriğinin akışına girer ve katman olarak açılmazlar.
926
885
  */
927
886
  const ROOT_LAYER_TYPES = new Set(['modal', 'drawer']);
928
- export function ensurePageContainer(schema, rootContainer = pageContainerNode) {
887
+ export function ensurePageContainer(schema) {
929
888
  const nodes = asObjects(schema);
930
889
  if (nodes.some(node => node.elementId === PAGE_CONTAINER_ELEMENT_ID))
931
890
  return { schema: nodes, fixes: [] };
932
- const container = rootContainer();
891
+ const container = pageContainerNode();
933
892
  const layers = nodes.filter(node => ROOT_LAYER_TYPES.has(String(node.type ?? '')));
934
893
  container.children = nodes.filter(node => !ROOT_LAYER_TYPES.has(String(node.type ?? '')));
935
894
  const moved = container.children.length;
@@ -1886,22 +1845,12 @@ export function nestedRepeaterNodes(nodes) {
1886
1845
  walk(asObjects(nodes), false);
1887
1846
  return found;
1888
1847
  }
1889
- /**
1890
- * Mobil profilde gruplu listenin (gün/kategori başlığı altında satırlar) hazır bileşeni vardır:
1891
- * `grouped-list` (ui-native, ADR 0007). Web'de karşılığı yok — web mesajı textarea tarifinde kalır.
1892
- */
1893
- export function nestedRepeaterMessage(ids, platform = 'web') {
1894
- const measured = `PAGE-013 — repeater içinde repeater (istek gönderilmedi): ${ids.join(' · ')}. `
1848
+ export function nestedRepeaterMessage(ids) {
1849
+ return `PAGE-013 — repeater içinde repeater (istek gönderilmedi): ${ids.join(' · ')}. `
1895
1850
  + 'Ölçüldü (2026-08-19): iç repeater OKUMADA çalışır ama girilen değer dış satıra GERİ YAZILMAZ '
1896
1851
  + '(_repeaterValues null kalır) ve tek düzenlemeden sonra DOM bozulur. Sebep yapısal: şemada iç '
1897
- + 'repeater tek düğümdür, dış satır başına ayrı düğüm yoktur. ';
1898
- const textarea = "Satır içi listeyi tek bir textarea ile topla (satır başına bir öğe) ve kaydederken split('\n') ile böl — platform_guide('page') Kural 1g.";
1899
- if (platform !== 'mobile')
1900
- return measured + textarea;
1901
- return measured
1902
- + 'Başlıklar altında gruplanmış liste (güne/kategoriye göre) için mobilde tek `grouped-list` kur: düz `rows` + '
1903
- + '`groupBy` alan yolu; çocuk 0 bölüm başlığı (`{{ Section.title }}`), çocuk 1 öğe şablonu (`Row`, `{{ item.<alan> }}`) — '
1904
- + "platform_guide('mobile') §Güne göre görev listesi. Satır içinde düzenlenen alt liste gerekiyorsa: " + textarea;
1852
+ + 'repeater tek düğümdür, dış satır başına ayrı düğüm yoktur. Satır içi listeyi tek bir textarea '
1853
+ + "ile topla (satır başına bir öğe) ve kaydederken split('\n') ile böl — platform_guide('page') Kural 1g.";
1905
1854
  }
1906
1855
  /**
1907
1856
  * `style` prop'u düz CSS STRING verilmiş düğümler. Çalışır (emotion string'i CSS metni sayar) ama
@@ -1915,7 +1864,7 @@ export function stringStyleNodes(nodes) {
1915
1864
  for (const node of list) {
1916
1865
  const props = (node.props ?? {});
1917
1866
  const style = props.style;
1918
- if (style && typeof style.data === 'string' && style.data.includes(':') && !DOCUMENT_STYLE_TYPES.has(String(node.type))) {
1867
+ if (style && typeof style.data === 'string' && style.data.includes(':')) {
1919
1868
  found.push(String(node.elementId ?? node.name ?? '?'));
1920
1869
  }
1921
1870
  walk(asObjects(node.children));