@appaflytech/zeyrek-ai-mcp 0.0.28 → 0.0.30

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 (201) hide show
  1. package/.env.example +15 -0
  2. package/README.md +5 -5
  3. package/dist/config.js +37 -0
  4. package/dist/index.js +2 -0
  5. package/dist/knowledge/access.js +802 -0
  6. package/dist/knowledge/backendConstraints.js +18 -1
  7. package/dist/knowledge/case.js +41 -0
  8. package/dist/knowledge/componentCatalog.js +494 -34
  9. package/dist/knowledge/componentEvents.js +132 -7
  10. package/dist/knowledge/connectDataBind.js +819 -0
  11. package/dist/knowledge/connectDataDefinition.js +341 -0
  12. package/dist/knowledge/connectDataExecute.js +93 -0
  13. package/dist/knowledge/connectDataRules.js +128 -0
  14. package/dist/knowledge/decision.js +121 -0
  15. package/dist/knowledge/expressionPrelude.js +59 -0
  16. package/dist/knowledge/layoutSummary.js +140 -2
  17. package/dist/knowledge/microserviceParameters.js +58 -0
  18. package/dist/knowledge/mining.js +94 -0
  19. package/dist/knowledge/mobileComponentCatalog.js +859 -0
  20. package/dist/knowledge/mobileComponentEvents.js +239 -0
  21. package/dist/knowledge/normalize.js +236 -67
  22. package/dist/knowledge/outlines.js +36 -0
  23. package/dist/knowledge/platform/lint.js +385 -0
  24. package/dist/knowledge/platform/mobile.js +142 -0
  25. package/dist/knowledge/platform/mobileLayoutLint.js +324 -0
  26. package/dist/knowledge/platform/mobileRules.js +231 -0
  27. package/dist/knowledge/platform/mobileSpacing.js +82 -0
  28. package/dist/knowledge/platform/mobileTabs.js +108 -0
  29. package/dist/knowledge/platform/nativeBoxModel.js +44 -0
  30. package/dist/knowledge/platform/profile.js +25 -0
  31. package/dist/knowledge/platform/resolve.js +122 -0
  32. package/dist/knowledge/platform/web.js +39 -0
  33. package/dist/knowledge/policy.js +18 -4
  34. package/dist/knowledge/process.js +510 -0
  35. package/dist/knowledge/reservedTables.js +79 -0
  36. package/dist/knowledge/rowFilter.js +43 -0
  37. package/dist/knowledge/rules.js +319 -21
  38. package/dist/knowledge/schemaGuard.js +11 -0
  39. package/dist/knowledge/styleResolve.js +50 -4
  40. package/dist/knowledge/styleResolveNative.js +527 -0
  41. package/dist/knowledge/themeResolve.js +3 -3
  42. package/dist/observability/trace.js +6 -3
  43. package/dist/platform/argumentAliases.js +128 -0
  44. package/dist/platform/bodyFile.js +103 -0
  45. package/dist/platform/capture/actions.js +333 -0
  46. package/dist/platform/capture/browser.js +1 -0
  47. package/dist/platform/{screenshot.js → capture/capture.js} +105 -519
  48. package/dist/platform/capture/console-log.js +122 -0
  49. package/dist/platform/capture/index.js +11 -0
  50. package/dist/platform/capture/readiness.js +103 -0
  51. package/dist/platform/capture/summary.js +77 -0
  52. package/dist/platform/capture/target.js +5 -0
  53. package/dist/platform/capture/targets/studio-mobile.js +15 -0
  54. package/dist/platform/capture/targets/studio-web.js +5 -0
  55. package/dist/platform/diagnostics.js +10 -4
  56. package/dist/platform/domSummary.js +1 -1
  57. package/dist/platform/errors.js +162 -10
  58. package/dist/platform/http.js +29 -8
  59. package/dist/platform/impersonation.js +45 -0
  60. package/dist/platform/jsonArray.js +24 -0
  61. package/dist/platform/responses.js +39 -5
  62. package/dist/platform/schemaReadMemory.js +60 -0
  63. package/dist/platform/secrets.js +17 -1
  64. package/dist/platform/strictArguments.js +68 -12
  65. package/dist/platform/tokenVault.js +187 -4
  66. package/dist/resources/examples/bpm-gorev-kontrol.json +294 -0
  67. package/dist/resources/examples/bpm-iso-dokuman.json +228 -0
  68. package/dist/resources/examples/bpm-satinalma.json +376 -0
  69. package/dist/resources/examples/connect-data-convert-verify.json +107 -0
  70. package/dist/resources/examples/connect-data-dropdown-roles.json +66 -0
  71. package/dist/resources/examples/connect-data-edit-m2m.json +73 -0
  72. package/dist/resources/examples/connect-data-query-params.json +50 -0
  73. package/dist/resources/examples/connect-data-table-entity.json +147 -0
  74. package/dist/resources/examples/connect-data-workflow-microservice.json +65 -0
  75. package/dist/resources/examples/mobile-app-shell-drawer.json +243 -0
  76. package/dist/resources/examples/mobile-app-shell-stack.json +157 -0
  77. package/dist/resources/examples/mobile-app-shell-tab.json +208 -0
  78. package/dist/resources/examples/mobile-form-screen.json +365 -0
  79. package/dist/resources/examples/mobile-list-detail-form.json +672 -0
  80. package/dist/resources/examples/page-detail-profile.json +2 -1
  81. package/dist/resources/examples/page-dynamic-form.json +3 -2
  82. package/dist/resources/examples/page-flex-sections.json +1 -1
  83. package/dist/resources/examples/page-html-embed.json +1 -1
  84. package/dist/resources/examples/workflow-basic.json +8 -2
  85. package/dist/resources/guides/access.md +796 -0
  86. package/dist/resources/guides/bpm.md +686 -0
  87. package/dist/resources/guides/case.md +153 -0
  88. package/dist/resources/guides/component.md +91 -4
  89. package/dist/resources/guides/decision.md +185 -0
  90. package/dist/resources/guides/entity.md +50 -8
  91. package/dist/resources/guides/expressions.md +14 -5
  92. package/dist/resources/guides/functions.md +1 -1
  93. package/dist/resources/guides/getting-started.md +38 -1
  94. package/dist/resources/guides/layout.md +11 -7
  95. package/dist/resources/guides/microservice.md +81 -2
  96. package/dist/resources/guides/middleware.md +44 -2
  97. package/dist/resources/guides/mobile-layout.md +193 -0
  98. package/dist/resources/guides/mobile.md +495 -0
  99. package/dist/resources/guides/page.md +454 -75
  100. package/dist/resources/guides/theme.md +8 -2
  101. package/dist/resources/guides/translation.md +1 -1
  102. package/dist/resources/guides/workflow.md +58 -1
  103. package/dist/resources/read.js +41 -0
  104. package/dist/resources/rules.js +11 -5
  105. package/dist/resources/schema-fingerprint.json +7 -5
  106. package/dist/resources/schemas/AccessExplanationDto.json +144 -0
  107. package/dist/resources/schemas/AccessMatrixDto.json +170 -0
  108. package/dist/resources/schemas/AccessPermissionDto.json +57 -0
  109. package/dist/resources/schemas/AccessPolicyDto.json +223 -0
  110. package/dist/resources/schemas/AccessResourceDto.json +86 -0
  111. package/dist/resources/schemas/AppSessionDto.json +53 -0
  112. package/dist/resources/schemas/ConnectDataDescribeDto.json +344 -0
  113. package/dist/resources/schemas/ConnectDataRequestModel.json +202 -0
  114. package/dist/resources/schemas/ConnectDataResultDto.json +421 -0
  115. package/dist/resources/schemas/ConnectorCredentialDto.json +70 -0
  116. package/dist/resources/schemas/ConvertConnectDataToMicroserviceCommand.json +37 -0
  117. package/dist/resources/schemas/CreateAccessPolicyCommand.json +209 -0
  118. package/dist/resources/schemas/CreateDecisionCommand.json +221 -0
  119. package/dist/resources/schemas/CreateEntityCommand.json +118 -0
  120. package/dist/resources/schemas/CreateMicroserviceCommand.json +13 -1
  121. package/dist/resources/schemas/CreateProcessDefinitionCommand.json +1337 -0
  122. package/dist/resources/schemas/CreateQueryCommand.json +4 -0
  123. package/dist/resources/schemas/CreateWorkflowCommand.json +143 -1
  124. package/dist/resources/schemas/DataJobDto.json +6 -1
  125. package/dist/resources/schemas/DecisionDto.json +302 -0
  126. package/dist/resources/schemas/DetachConnectDataMicroserviceCommand.json +28 -0
  127. package/dist/resources/schemas/ExplainAccessQuery.json +33 -0
  128. package/dist/resources/schemas/GenerateProcessDocumentCommand.json +116 -0
  129. package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +21 -1
  130. package/dist/resources/schemas/MicroserviceDto.json +15 -3
  131. package/dist/resources/schemas/OrganizationProjectDto.json +335 -0
  132. package/dist/resources/schemas/OrganizationSchemaDto.json +196 -0
  133. package/dist/resources/schemas/OrganizationSchemaVersionDto.json +207 -0
  134. package/dist/resources/schemas/PatchProcessDefinitionCommand.json +76 -0
  135. package/dist/resources/schemas/PreviewMicroserviceCommand.json +10 -1
  136. package/dist/resources/schemas/PreviewWorkflowCommand.json +22 -1
  137. package/dist/resources/schemas/ProcessDefinitionDto.json +1480 -0
  138. package/dist/resources/schemas/ProcessSimulationDto.json +96 -0
  139. package/dist/resources/schemas/SaveConnectorCredentialCommand.json +90 -0
  140. package/dist/resources/schemas/SaveProcessEventSourceCommand.json +61 -0
  141. package/dist/resources/schemas/SaveProcessMailSourceCommand.json +89 -0
  142. package/dist/resources/schemas/SignProcessTaskCommand.json +73 -0
  143. package/dist/resources/schemas/SimulateProcessDefinitionQuery.json +92 -0
  144. package/dist/resources/schemas/StepDto.json +22 -1
  145. package/dist/resources/schemas/UpdateAccessPolicyCommand.json +209 -0
  146. package/dist/resources/schemas/UpdateDecisionCommand.json +216 -0
  147. package/dist/resources/schemas/UpdateEntityCommand.json +118 -0
  148. package/dist/resources/schemas/UpdateMicroserviceCommand.json +13 -1
  149. package/dist/resources/schemas/UpdateProcessDefinitionCommand.json +1357 -0
  150. package/dist/resources/schemas/UpdateQueryCommand.json +4 -0
  151. package/dist/resources/schemas/UpdateViewCommand.json +0 -2
  152. package/dist/resources/schemas/UpdateWorkflowCommand.json +143 -1
  153. package/dist/resources/schemas/ValidateProcessDefinitionQuery.json +1339 -0
  154. package/dist/server.js +27 -1
  155. package/dist/tools/access.js +369 -2
  156. package/dist/tools/accessOperations.js +135 -0
  157. package/dist/tools/accessPolicies.js +170 -0
  158. package/dist/tools/accessRuntime.js +203 -0
  159. package/dist/tools/applications.js +60 -3
  160. package/dist/tools/bpm.js +343 -0
  161. package/dist/tools/bpmGovernance.js +266 -0
  162. package/dist/tools/bpmOperations.js +230 -0
  163. package/dist/tools/bpmReporting.js +154 -0
  164. package/dist/tools/bpmRouting.js +150 -0
  165. package/dist/tools/bpmRuntime.js +396 -0
  166. package/dist/tools/case.js +178 -0
  167. package/dist/tools/caseRuntime.js +89 -0
  168. package/dist/tools/common.js +7 -0
  169. package/dist/tools/components.js +118 -14
  170. package/dist/tools/connectData.js +449 -0
  171. package/dist/tools/connectors.js +193 -0
  172. package/dist/tools/datasources.js +3 -3
  173. package/dist/tools/decisions.js +206 -0
  174. package/dist/tools/dependencies.js +1 -1
  175. package/dist/tools/deployments.js +2 -2
  176. package/dist/tools/designSchemaPatch.js +34 -9
  177. package/dist/tools/dtos.js +36 -5
  178. package/dist/tools/entities.js +79 -12
  179. package/dist/tools/externalComponents.js +26 -5
  180. package/dist/tools/flows.js +122 -14
  181. package/dist/tools/folders.js +1 -1
  182. package/dist/tools/imports.js +5 -2
  183. package/dist/tools/layouts.js +50 -17
  184. package/dist/tools/merges.js +1 -1
  185. package/dist/tools/microservices.js +77 -25
  186. package/dist/tools/mobile.js +221 -0
  187. package/dist/tools/modules.js +48 -0
  188. package/dist/tools/organizationStructure.js +137 -0
  189. package/dist/tools/pages.js +465 -98
  190. package/dist/tools/platformInfo.js +27 -8
  191. package/dist/tools/profileWrite.js +92 -0
  192. package/dist/tools/projects.js +71 -9
  193. package/dist/tools/rows.js +69 -13
  194. package/dist/tools/schemaOps.js +61 -1
  195. package/dist/tools/screenWrite.js +37 -8
  196. package/dist/tools/screens.js +95 -5
  197. package/dist/tools/templates.js +26 -1
  198. package/dist/tools/translations.js +1 -1
  199. package/dist/tools/variables.js +11 -5
  200. package/dist/tools/workflows.js +10 -5
  201. package/package.json +2 -2
@@ -0,0 +1,324 @@
1
+ import { MOBILE_DROPPABLE_TYPES, MOBILE_OUT_OF_FLOW_TYPES, MOBILE_TRANSPARENT_WRAPPER_TYPES } from '../mobileComponentCatalog.js';
2
+ import { PAGE_CONTAINER_ELEMENT_ID } from '../normalize.js';
3
+ import { OVERLAY_TYPES, firstDrawn, safeAreaEdges, topSafeAreaMissing } from './mobile.js';
4
+ import { MOBILE_RULES } from './mobileRules.js';
5
+ import { FULL_WIDTH_TYPES, nativeMarginLeft, nativePaddingLeft } from './nativeBoxModel.js';
6
+ /**
7
+ * Boş görünmesi hata olmayan bırakılabilir tipler: içeriği çalışma anında gelir (`widget-area`, `slot`), öğesini
8
+ * prop'tan çizer (`carousel` items, `tabs` başlığı, `grouped-list` satırları) ya da akış dışı bir katmandır.
9
+ */
10
+ const EMPTY_ALLOWED_TYPES = ['widget-area', 'slot', 'carousel', 'tabs', 'grouped-list', ...OVERLAY_TYPES];
11
+ export function describeShell(views) {
12
+ const root = asNodes(views)[0];
13
+ const children = asNodes(root?.children);
14
+ return { kind: 'shell', topInset: children.length > 0 && !topSafeAreaMissing(children, 'studio'), appBar: someNode(asNodes(views), node => node.type === 'app-bar') };
15
+ }
16
+ /**
17
+ * MOB-015…021 — mizanpaj sözleşmesinin iskelet (S1/S3/S12/S13) ve hiza/ölçek (S5/S6/S8/S9/S11) maddeleri (plans/mobil-mizanpaj-dili 03, S1/S3/S12/S13).
18
+ * Ağacın tamamına bakar; yama yolunda yalnız yazımın getirdiği bulgular raporlanır (`newFindings`).
19
+ */
20
+ export function mobileLayoutFindings(input) {
21
+ const roots = asNodes(input.tree);
22
+ const screenRoot = input.kind === 'page' ? roots.find(node => node.elementId === PAGE_CONTAINER_ELEMENT_ID) : roots[0];
23
+ const screen = asNodes(screenRoot?.children);
24
+ const shell = input.kind === 'page' ? input.shell ?? { kind: 'none' } : { kind: 'none' };
25
+ const findings = [];
26
+ if (shell.kind === 'unknown') {
27
+ findings.push({ code: MOBILE_RULES['MOB-015'].id, severity: 'info', message: 'sayfanın kabuğu (layout) okunamadı — üst pay ve app-bar kuralları (MOB-015, MOB-017) bu yazımda denetlenmedi' });
28
+ }
29
+ // MOB-015 — üst güvenli alan tek yerde.
30
+ if (input.kind === 'layout' && screen.length > 0 && topSafeAreaMissing(screen, 'studio')) {
31
+ findings.push(rule('MOB-015', label(screenRoot), "kabuğun ilk çizilen öğesi üst güvenli alan değil: app-bar ve sayfalar durum çubuğunun/çentiğin altına girer. app-bar'ı `safe-area` (edges: 'top') içine alın — sayfalar ikinci pay vermez"));
32
+ }
33
+ if (input.kind === 'page' && shell.kind === 'none' && screen.length > 0 && topSafeAreaMissing(screen, 'studio')) {
34
+ findings.push(rule('MOB-015', label(screenRoot), "kabuksuz sayfanın ilk çizilen öğesi üst güvenli alan değil: içerik durum çubuğunun/çentiğin altına girer. Tepeye `safe-area` (edges: 'top') koyun ya da sayfayı üst payı veren bir kabuğa bağlayın (layoutId)"));
35
+ }
36
+ if (shell.kind === 'shell' && !shell.topInset) {
37
+ findings.push(rule('MOB-015', 'layout', "sayfanın kabuğu üst payı vermiyor: kabukta app-bar'ı `safe-area` (edges: 'top') içine alın (layout_schema_patch); payı sayfaya koymayın, her sayfada tekrarlanır"));
38
+ }
39
+ if (shell.kind === 'shell' && shell.topInset) {
40
+ // Katman (modal, drawer) kendi panelinde payı ayrıca çözer; ekranın akışındaki güvenli alanlar sayılır.
41
+ for (const node of collect(roots, node => node.type === 'safe-area' && safeAreaEdges(node) !== 'bottom', OVERLAY_TYPES)) {
42
+ findings.push(rule('MOB-015', label(node), `kabuk üst payı zaten veriyor; sayfadaki '${safeAreaEdges(node)}' güvenli alanı ikinci pay bırakır (içerik app-bar'dan kopar). Kaldırın; alt pay gerekiyorsa edges: 'bottom'`));
43
+ }
44
+ }
45
+ // MOB-016 — tek kaydırıcı: kök kaydırır ve içerik boyundadır.
46
+ if (screenRoot && propData(screenRoot, 'size') === 'grow') {
47
+ findings.push(rule('MOB-016', label(screenRoot), "kök kap `size: 'grow'`: çocuklar ekran boyunu eşit paylaşır, kısa içerik gerilir ya da üst üste biner (koşu 37 S-6/S-16). replace_prop size: 'shrink'"));
48
+ }
49
+ walkWithAncestors(roots, (node, ancestors) => {
50
+ if (node.type !== 'scroll-view' || setOrBound(node, 'horizontal'))
51
+ return;
52
+ const fixed = appearanceOf(node).heightType === 'fixed';
53
+ if (ancestors.some(item => item.type === 'scroll-view' && !setOrBound(item, 'horizontal')) && !fixed) {
54
+ findings.push(rule('MOB-016', label(node), "dikey kaydırıcı başka bir dikey kaydırıcının içinde: iç kaydırma jesti dışarıdakiyle çakışır. İçtekine sabit boy (heightType: 'fixed') verin ya da kaldırın"));
55
+ }
56
+ else if (!fixed && !setOrBound(node, 'refreshable') && !setOrBound(node, 'stickyHeader') && !asNodes(node.children).some(child => child.states?.isSticky === true)) {
57
+ findings.push(rule('MOB-016', label(node), "gerekçesiz dikey `scroll-view`: sayfa kökü zaten kaydırır. Kaldırın ya da gerekçesini verin — refreshable (çekerek yenile), stickyHeader / states.isSticky (yapışkan başlık), heightType: 'fixed' (sabit boylu iç liste)"));
58
+ }
59
+ });
60
+ // MOB-017 — ekranda tek app-bar, ilk çizilen öğe.
61
+ const appBars = collect(roots, node => node.type === 'app-bar', OVERLAY_TYPES);
62
+ if (shell.kind === 'shell' && shell.appBar) {
63
+ for (const node of appBars)
64
+ findings.push(rule('MOB-017', label(node), 'kabuk app-bar veriyor; sayfadaki ikinci app-bar ekranda çift başlık çubuğu çizer. Kaldırın; sayfa başlığını kabuğun app-bar\'ına bağlayın (title)'));
65
+ }
66
+ else if (shell.kind !== 'unknown' && appBars.length > 0) {
67
+ // Tepedeki app-bar: ilk çizilen öğenin kendisi ya da onun içindeki ilk app-bar (safe-area içi, saydam
68
+ // çubuk tarifi `stack` → görsel + kap → app-bar).
69
+ // Boş `safe-area` (kabuksuz sayfanın pay boşluğu) çizim yapmaz; tepedeki ilk içerikli öğeye bakılır.
70
+ const first = firstDrawn(screen.filter(item => !(item.type === 'safe-area' && asNodes(item.children).length === 0)));
71
+ const leading = first ? collect([first], item => item.type === 'app-bar', OVERLAY_TYPES)[0] : undefined;
72
+ for (const node of appBars.filter(item => item !== leading || leading?.type !== 'app-bar')) {
73
+ findings.push(rule('MOB-017', label(node), appBars.length > 1 && node !== appBars[0] ? 'ekranda ikinci app-bar: tek başlık çubuğu olmalı, kaldırın' : 'app-bar ilk çizilen öğe değil: başlık çubuğu ekranın tepesinde durur. Onu ilk sıraya (ya da tepedeki safe-area\'nın içine) taşıyın'));
74
+ }
75
+ }
76
+ // MOB-018 — boş kap bırakılmaz (tepedeki/dipteki güvenli alan bilinçli bir paydır).
77
+ walkWithAncestors(screen, (node, ancestors) => {
78
+ const type = String(node.type ?? '');
79
+ if (!MOBILE_DROPPABLE_TYPES.includes(type) || EMPTY_ALLOWED_TYPES.includes(type) || node.kind === 'widget')
80
+ return;
81
+ if (asNodes(node.children).length > 0 || boundVisibility(node))
82
+ return;
83
+ if (type === 'safe-area' && ancestors.length === 0)
84
+ return;
85
+ findings.push({ ...rule('MOB-018', label(node), `boş ${type}: ekranda boş şerit bırakır${type === 'safe-area' ? ` (edges: '${safeAreaEdges(node)}' payı kadar)` : ''}. İçeriğini ekleyin ya da silin`), severity: 'info' });
86
+ });
87
+ if (screenRoot)
88
+ findings.push(...alignmentFindings(screenRoot, input.kind), ...widthFindings(screenRoot), ...scaleFindings(screenRoot));
89
+ return findings;
90
+ }
91
+ /** Sütunda dizilen içerik (form alanları, düğmeler); satırda yan yana olmaları hata değildir. */
92
+ const FIELD_TYPES = ['textinput', 'textarea', 'password', 'numberinput', 'dropdown', 'multi-select', 'datepicker', 'daterangepicker', 'datetimepicker', 'timepicker', 'fileInput', 'slider'];
93
+ const BUTTON_TYPES = ['button', 'filebutton'];
94
+ /** Metin gibi görünen düğmeler içerik genişliğinde durur (S8'in istisnası). */
95
+ const INLINE_BUTTON_VARIANTS = ['text', 'link', 'ghost'];
96
+ /** Sütun akışını taşıyan kap tipleri ve yazılmamış yönleri (`containerDirection`: safe-area/scroll-view sütun doğar). */
97
+ const FLOW_TYPES = { container: 'row', form: 'row', 'safe-area': 'column', 'scroll-view': 'column' };
98
+ const SPACING_SCALE = [0, 4, 8, 16, 24, 32];
99
+ const RADIUS_SCALE = ['none', 'sm', 'md', 'lg', 'pill', '0', 0];
100
+ /**
101
+ * MOB-019 — kenar değişmezi (S6/S8): aynı kapsamda (ekran ya da yüzey kabı) sütun akışındaki öğelerin sol
102
+ * kenarı eşittir. Zeminsiz kap ve saydam sarmalayıcı (tooltip) içeriğini kendi margin + padding'i kadar
103
+ * içeri iter; yüzey kabı yeni kapsam açar, içeriği bilerek içeride durur. Hesap runtime'ın kutu modeliyle
104
+ * (`nativeBoxModel.ts`), 01'in çizimde ölçtüğü sapmanın şemadan öngörüsüdür.
105
+ */
106
+ function alignmentFindings(root, kind) {
107
+ const findings = [];
108
+ const scopes = [root];
109
+ for (let index = 0; index < scopes.length; index++) {
110
+ const owner = scopes[index];
111
+ const items = [];
112
+ collectFlowItems(asNodes(owner.children), 0, [], [], items, kind);
113
+ for (const item of items)
114
+ if (isSurface(item.node) && flowDirection(item.node) === 'column')
115
+ scopes.push(item.node);
116
+ // Ortalanmış ya da sağa hizalı kapsamda öğeler kendi genişliğince yerleşir; sol kenar eşitliği aranmaz.
117
+ if (!['center', 'right'].includes(String(propData(owner, 'horizontalAlign') ?? 'left')))
118
+ findings.push(...insetFindings(items));
119
+ }
120
+ return findings;
121
+ }
122
+ function collectFlowItems(nodes, inset, chain, shifts, items, kind) {
123
+ for (const node of nodes) {
124
+ const type = String(node.type ?? '');
125
+ if (MOBILE_OUT_OF_FLOW_TYPES.includes(type) || type === 'slot' || propData(node, 'isHidden') === true)
126
+ continue;
127
+ const margin = nativeMarginLeft(node);
128
+ if (transparent(node)) {
129
+ const shift = margin + nativePaddingLeft(node);
130
+ collectFlowItems(asNodes(node.children), inset + shift, [...chain, node], [...shifts, shift], items, kind);
131
+ }
132
+ else {
133
+ items.push({ node, inset: inset + margin, chain, shifts });
134
+ }
135
+ }
136
+ }
137
+ /** İçeriğini sütunda, sola ya da gerilerek dizen zeminsiz kap — kendi kutusu görünmez, içeriği görünür. */
138
+ function transparent(node) {
139
+ const type = String(node.type ?? '');
140
+ if (MOBILE_TRANSPARENT_WRAPPER_TYPES.includes(type))
141
+ return true;
142
+ if (!FLOW_TYPES[type] || isSurface(node))
143
+ return false;
144
+ return flowDirection(node) === 'column' && !['center', 'right'].includes(String(propData(node, 'horizontalAlign') ?? 'left'));
145
+ }
146
+ /**
147
+ * Taban, kapsamın kendi sütunundaki (en sığ) öğelerdir — çoğunluk değil. Böylece "başlık + başlıklı grup"
148
+ * düzeninde suçlu grubu içeri iten kaptır, başlık değil; gruba alan eklemek tabanı el değiştirmez.
149
+ */
150
+ function insetFindings(items) {
151
+ if (items.length === 0)
152
+ return [];
153
+ const depth = Math.min(...items.map(item => item.chain.length));
154
+ const counts = new Map();
155
+ for (const item of items.filter(entry => entry.chain.length === depth))
156
+ counts.set(item.inset, (counts.get(item.inset) ?? 0) + 1);
157
+ const baseline = [...counts].sort((a, b) => b[1] - a[1] || a[0] - b[0])[0]?.[0] ?? 0;
158
+ const reference = items.find(item => item.chain.length === depth && item.inset === baseline);
159
+ const groups = new Map();
160
+ for (const item of items.filter(entry => entry.inset !== baseline)) {
161
+ // Suçlu: ortak zincirden sonra içeriği gerçekten iten ilk kap; hiçbiri itmiyorsa fark öğenin kendi margin'idir.
162
+ const cause = item.chain.find((ancestor, index) => reference?.chain[index] !== ancestor && (item.shifts[index] ?? 0) !== 0) ?? item.node;
163
+ groups.set(cause, [...(groups.get(cause) ?? []), item]);
164
+ }
165
+ return [...groups].map(([cause, members]) => {
166
+ // Kabın kendi payı en az kayan üyededir; daha derindeki ek kayma iç kabın ayrı bulgusudur (kap düzelince görünür).
167
+ const delta = members.map(member => member.inset - baseline).sort((a, b) => Math.abs(a) - Math.abs(b))[0] ?? 0;
168
+ const where = `${Math.abs(delta)} dp ${delta > 0 ? 'içeride' : 'dışarıda'}`;
169
+ const ids = members.slice(0, 4).map(member => label(member.node)).join(', ') + (members.length > 4 ? ', …' : '');
170
+ if (members.length === 1 && cause === members[0]?.node) {
171
+ return rule('MOB-019', label(cause), `kardeşlerinden ${where} başlıyor: kendi margin'i farklı. Aralık ebeveynin gap'inden gelsin — marginType'ı kardeşleriyle aynı yapın`);
172
+ }
173
+ const recipe = MOBILE_TRANSPARENT_WRAPPER_TYPES.includes(String(cause.type ?? ''))
174
+ ? "paddingType:'none' ve marginType:'none' yazın"
175
+ : "grup görünsün isteniyorsa yüzey verin (surfaceStyle zemin ya da kenarlık — içerik bilerek içeride durur), istenmiyorsa paddingType:'none' ve marginType:'none' yazın";
176
+ return rule('MOB-019', label(cause), `${String(cause.type)} içeriği (${ids}) kardeşlerinden ${where} başlıyor — margin ve padding'i içeriği iter. ${recipe}`);
177
+ });
178
+ }
179
+ /**
180
+ * MOB-020 — genişlik (S8/S9): (1) sola hizalı sütunda tam genişlik sınıfındaki öğelerin bir kısmı `fill`, bir
181
+ * kısmı değilse fill olmayanlar; (2) form alanı taşıyan satır kabı (yönü yazılmamış container/form satırdır).
182
+ */
183
+ function widthFindings(root) {
184
+ const findings = [];
185
+ walkWithAncestors([root], node => {
186
+ const children = asNodes(node.children).filter(child => propData(child, 'isHidden') !== true);
187
+ if (!FLOW_TYPES[String(node.type ?? '')])
188
+ return;
189
+ if (flowDirection(node) === 'row') {
190
+ // Yazılmış `row` bilinçlidir (arama satırı: alan + düğme); yazılmamış yön unutulmuş sütundur.
191
+ if (node !== root && propData(node, 'direction') === undefined && children.some(child => FIELD_TYPES.includes(String(child.type ?? '')))) {
192
+ findings.push(rule('MOB-020', label(node), "yönü yazılmamış kap form alanı taşıyor — yazılmamış yön satırdır, alanlar yan yana sıkışır. direction:'column' ve horizontalAlign:'stretch' yazın"));
193
+ }
194
+ return;
195
+ }
196
+ if (propData(node, 'horizontalAlign') === 'stretch')
197
+ return;
198
+ const wide = children.filter(fullWidthClass);
199
+ if (!wide.some(fills))
200
+ return;
201
+ for (const child of wide.filter(child => !fills(child))) {
202
+ findings.push(rule('MOB-020', label(child), `kardeşleri tam genişlik (widthType: 'fill'), bu öğe değil: sağ kenarı hizadan kopar. widthType:'fill' yazın`));
203
+ }
204
+ });
205
+ return findings;
206
+ }
207
+ /**
208
+ * MOB-021 — ölçek (S5/S7/S11): özel boşluk ve sayısal gap ölçekte, yarıçap ölçek adıyla, yazı boyu seviyeyle.
209
+ * Gap jetonları (`small`/`medium`/`large`) zaten ölçektir ve sözleşmenin ritmi üçünü de kullanır; sayılmaz.
210
+ */
211
+ function scaleFindings(root) {
212
+ const findings = [];
213
+ walkWithAncestors([root], node => {
214
+ const appearance = appearanceOf(node);
215
+ const offScale = ['margin', 'padding'].flatMap(layer => {
216
+ if (appearance[`${layer}Type`] !== 'custom')
217
+ return [];
218
+ const values = appearance[`${layer}Values`];
219
+ const sides = ['top', 'right', 'bottom', 'left'].map(side => values?.[side]);
220
+ // `resolveSpacing`: tek değer yalnız yazılmamış kenarın yedeğidir.
221
+ const written = [...sides, ...(sides.some(side => side === undefined || side === null) ? [appearance[`${layer}Value`]] : [])];
222
+ const numbers = written.filter(value => value !== undefined && value !== null && value !== '').map(value => Number(String(value).replace(/px$/, '')));
223
+ return [...new Set(numbers.filter(value => !SPACING_SCALE.includes(value)))].map(value => `${layer} ${value}`);
224
+ });
225
+ const radius = nested(node, 'surfaceStyle', 'radius');
226
+ const fontSize = nested(node, 'typographyStyle', 'fontSize');
227
+ const gap = propData(node, 'gap');
228
+ const notes = [
229
+ ...offScale,
230
+ ...(radius !== undefined && !RADIUS_SCALE.includes(radius) && !/^var\(--ng-radius-/.test(String(radius)) ? [`radius ${String(radius)} (sm | md | lg | pill)`] : []),
231
+ ...(typeof fontSize === 'number' || (typeof fontSize === 'string' && /^\d+(\.\d+)?(px)?$/.test(fontSize.trim())) ? [`fontSize ${String(fontSize)} (text seviyesi)`] : []),
232
+ ...(typeof gap === 'number' && !SPACING_SCALE.includes(gap) ? [`gap ${gap}`] : [])
233
+ ];
234
+ if (notes.length > 0)
235
+ findings.push({ ...rule('MOB-021', label(node), `ölçek dışı değer: ${notes.join(', ')} — boşluk 0/4/8/16/24/32, yarıçap ve yazı temadan gelir`), severity: 'info' });
236
+ });
237
+ return findings;
238
+ }
239
+ function fullWidthClass(node) {
240
+ const type = String(node.type ?? '');
241
+ if (FIELD_TYPES.includes(type))
242
+ return true;
243
+ if (!BUTTON_TYPES.includes(type))
244
+ return false;
245
+ return propData(node, 'size') !== 'icon' && !INLINE_BUTTON_VARIANTS.includes(String(propData(node, 'variant') ?? ''));
246
+ }
247
+ function fills(node) {
248
+ return appearanceOf(node).widthType === 'fill' || FULL_WIDTH_TYPES.includes(String(node.type ?? ''));
249
+ }
250
+ function flowDirection(node) {
251
+ if (node.type === 'scroll-view' && setOrBound(node, 'horizontal'))
252
+ return 'row';
253
+ const written = propData(node, 'direction');
254
+ return written === 'row' || written === 'column' ? written : FLOW_TYPES[String(node.type ?? '')] ?? 'column';
255
+ }
256
+ /** Görünür yüzey: zemin, kenarlık ya da gölge taşıyan kap (`surfaceStyle`, `SURFACE_KEYS`). */
257
+ function isSurface(node) {
258
+ const style = propData(node, 'surfaceStyle');
259
+ if (!style || typeof style !== 'object')
260
+ return false;
261
+ return Object.entries(style).some(([key, value]) => /^(background|border|shadow)/.test(key) && unwrap(value) !== undefined && unwrap(value) !== null && unwrap(value) !== '');
262
+ }
263
+ function nested(node, group, key) {
264
+ const style = propData(node, group);
265
+ return style && typeof style === 'object' ? unwrap(style[key]) : undefined;
266
+ }
267
+ function unwrap(value) {
268
+ return value && typeof value === 'object' && 'data' in value ? value.data : value;
269
+ }
270
+ /**
271
+ * Yazımdan önce de var olan bulgular tekrar edilmez: yama yalnız getirdiğini söyler. Aynı düğümün aynı kuralı
272
+ * mesajı değişse de (ör. app-bar sayısı) yeni sayılmaz. Hedefsiz bulgu bir durum bilgisidir (kabuk okunamadı)
273
+ * ve her yazımda söylenir — yoksa denetim sessizce atlanırdı.
274
+ */
275
+ export function newFindings(before, after) {
276
+ const known = new Set(before.map(key));
277
+ return after.filter(finding => finding.target === undefined || !known.has(key(finding)));
278
+ }
279
+ function key(finding) {
280
+ return `${finding.code}|${finding.target ?? ''}`;
281
+ }
282
+ function rule(code, target, message) {
283
+ return { code: MOBILE_RULES[code].id, severity: MOBILE_RULES[code].severity, target, message, guide: "platform_guide('mobile-layout')" };
284
+ }
285
+ function asNodes(value) {
286
+ return Array.isArray(value) ? value.filter((item) => !!item && typeof item === 'object') : [];
287
+ }
288
+ function label(node) {
289
+ return String(node?.elementId ?? node?.name ?? node?.type ?? '?');
290
+ }
291
+ function appearanceOf(node) {
292
+ return node.appearance && typeof node.appearance === 'object' ? node.appearance : {};
293
+ }
294
+ function propData(node, prop) {
295
+ const value = node.props?.[prop];
296
+ return value && typeof value === 'object' && 'data' in value ? value.data : value;
297
+ }
298
+ /** Prop açık mı — ifadeye bağlıysa değeri çalışma anında belli olur, açık sayılır (yanlış uyarı vermemek için). */
299
+ function setOrBound(node, prop) {
300
+ const raw = node.props?.[prop];
301
+ if (raw && typeof raw === 'object' && (raw.type === 'code' || raw.type === 'translation'))
302
+ return true;
303
+ const value = propData(node, prop);
304
+ return value === true || value === 'true';
305
+ }
306
+ function boundVisibility(node) {
307
+ const hidden = node.props?.isHidden;
308
+ return !!hidden && typeof hidden === 'object' && (hidden.type === 'code' || hidden.type === 'translation');
309
+ }
310
+ function someNode(nodes, test) {
311
+ return nodes.some(node => test(node) || someNode(asNodes(node.children), test));
312
+ }
313
+ function collect(nodes, test, skipInside = []) {
314
+ return nodes.flatMap(node => [
315
+ ...(test(node) ? [node] : []),
316
+ ...(skipInside.includes(String(node.type ?? '')) ? [] : collect(asNodes(node.children), test, skipInside))
317
+ ]);
318
+ }
319
+ function walkWithAncestors(nodes, visit, ancestors = []) {
320
+ for (const node of nodes) {
321
+ visit(node, ancestors);
322
+ walkWithAncestors(asNodes(node.children), visit, [...ancestors, node]);
323
+ }
324
+ }
@@ -0,0 +1,231 @@
1
+ import { MOBILE_COMPONENT_NOTES, mobileComponentKnowledge } from '../mobileComponentCatalog.js';
2
+ import { STYLE_GROUP_FIELDS } from '../styleResolve.js';
3
+ export const MOBILE_RULES = {
4
+ 'MOB-001': {
5
+ id: 'MOB-001',
6
+ severity: 'warn',
7
+ sourceRef: 'zeyrek-frontend/packages/ng-apps-ui-native/src/renderer (NativeRenderer blocks okumaz; sıra dizi indeksinden)',
8
+ summary: 'Mobil sayfada blok (12 sütun ızgara) ya da grid/cssgrid yerleşimi — çizer yok sayar'
9
+ },
10
+ 'MOB-002': {
11
+ id: 'MOB-002',
12
+ severity: 'warn',
13
+ sourceRef: 'zeyrek-frontend/packages/ng-apps-ui-native/src/style/compose-native-style.ts (yalnız tipli gruplar derlenir)',
14
+ summary: "Mobil sayfada CSS-only alan (className, tooltip, zIndex, serbest style, hoverStyle, em/rem/calc ölçü, surfaceStyle'ın tanımadığı alt anahtar)"
15
+ },
16
+ 'MOB-003': {
17
+ id: 'MOB-003',
18
+ severity: 'warn',
19
+ sourceRef: 'zeyrek-backend PageSchemaValidator unknown-type (mobil uygulama mobil kataloğa karşı doğrulanır)',
20
+ summary: 'Mobil katalogda olmayan bileşen tipi — backend 422 unknown-type ile reddeder'
21
+ },
22
+ 'MOB-004': {
23
+ id: 'MOB-004',
24
+ severity: 'warn',
25
+ sourceRef: 'plans/mobil-uygulama-expo/05-mobil-ayarlar-ve-gezinme.md §2 (D14)',
26
+ summary: "Mobil layout/sayfada web kabuğu (navigations, sidebar, header) — app-bar, sekmeler ayarı ve drawer widget'ı var"
27
+ },
28
+ 'MOB-005': {
29
+ id: 'MOB-005',
30
+ severity: 'info',
31
+ sourceRef: 'zeyrek-frontend/packages/ng-apps-ui-native/src/runtime/runtime-tasks.ts',
32
+ summary: "Mobil uygulamada download paylaşım/açma sayfası açar; setcookie storage'a eşlenir"
33
+ },
34
+ 'MOB-006': {
35
+ id: 'MOB-006',
36
+ severity: 'warn',
37
+ sourceRef: 'zeyrek-backend/docs/harici-componentler (protokol v1 tarayıcı .mjs; mobil v2 Faz 3); plans/mobil-plantcare-acigi f-20 K7',
38
+ summary: "Harici bileşen mobil uygulamada desteklenmiyor — çağrı platforma gitmez; geçici yol 'html' bileşeni"
39
+ },
40
+ 'MOB-007': {
41
+ id: 'MOB-007',
42
+ severity: 'warn',
43
+ sourceRef: 'plans/mobil-uygulama-expo/05-mobil-ayarlar-ve-gezinme.md §2.2, §2.6 (K13)',
44
+ summary: "Sekme ayarı tutarsız ya da layout/sayfada eski tab-bar/drawer-menu düğümü"
45
+ },
46
+ 'MOB-008': {
47
+ id: 'MOB-008',
48
+ severity: 'warn',
49
+ sourceRef: 'zeyrek-frontend/apps/mobile-runtime/src/navigation/navigation.ts (hrefFor); zeyrek-backend FlowDtoValidator',
50
+ summary: "redirect.presentation kümede değil — push | modal | replace dışı değer backend'de 422, çizer push'a düşer"
51
+ },
52
+ 'MOB-011': {
53
+ id: 'MOB-011',
54
+ severity: 'warn',
55
+ sourceRef: 'zeyrek-backend FilePathMapper.MapFileProperties (okunan FILE alanı { type: ref, value: <dosya id>, absolute_path }); zeyrek-frontend ng-apps-ui-native image-logic.ts resolveImageSource (plans/mobil-kosu-acik-yuksekler f-01)',
56
+ summary: "Görsel kaynağı ifadesi '.value' ile bitiyor — kayıttan okunan FILE alanında bu dosya kimliğidir (GUID), görsel çizilmez"
57
+ },
58
+ 'MOB-012': {
59
+ id: 'MOB-012',
60
+ severity: 'info',
61
+ sourceRef: 'zeyrek-frontend/packages/ng-apps-ui-native/src/components/image/image-logic.ts (imageFrameStyle)',
62
+ summary: "Mobil görselin boyu ne kabuktan (fixed + ölçü, fill) ne orandan (aspectRatio + heightType auto + belli genişlik) geliyor — 96 dp taban boyla çizilir"
63
+ },
64
+ 'MOB-013': {
65
+ id: 'MOB-013',
66
+ severity: 'info',
67
+ sourceRef: 'zeyrek-frontend/packages/ng-apps-ui-native/src/components/button/index.tsx (accessibleLabel: ariaLabel → text → ikon adı)',
68
+ summary: "İkon boyunda (size: icon) düğmenin adı yok — ne ariaLabel ne text; ekran okuyucu ikonun ligatür adını (notifications) okur"
69
+ },
70
+ 'MOB-014': {
71
+ id: 'MOB-014',
72
+ severity: 'warn',
73
+ sourceRef: 'docs/adr/0004-state-saklama-turu.md; zeyrek-frontend/packages/ng-apps-flow/src/machine/state-persistence.ts (depoyu barındırıcı verir; web Runtime.svelte vermez)',
74
+ summary: "State saklama türü (storage: device | secure) uygulamanın platformunda uygulanmıyor — kayda yazılır, state bellekte çalışır"
75
+ },
76
+ 'MOB-015': {
77
+ id: 'MOB-015',
78
+ severity: 'warn',
79
+ sourceRef: 'zeyrek-mcp guides/mobile-layout.md §Sözleşme S1; zeyrek-frontend/apps/studio/src/features/mobile/mobile-native-canvas.functions.ts (topSafeAreaMissing); apps/mobile-runtime PageScreen.tsx (host payı uygulamaz, plan D15)',
80
+ summary: 'Üst güvenli alan tek yerde değil: kabuksuz sayfada/kabukta eksik ya da kabuk verirken sayfada ikinci pay'
81
+ },
82
+ 'MOB-016': {
83
+ id: 'MOB-016',
84
+ severity: 'warn',
85
+ sourceRef: 'zeyrek-mcp guides/mobile-layout.md §Sözleşme S3; zeyrek-frontend/apps/mobile-runtime/src/runtime/PageScreen.tsx (sayfa her zaman ScrollView içinde)',
86
+ summary: "Kaydırma tek yerde değil: kök `size: grow`, gerekçesiz ya da iç içe dikey scroll-view"
87
+ },
88
+ 'MOB-017': {
89
+ id: 'MOB-017',
90
+ severity: 'warn',
91
+ sourceRef: 'zeyrek-mcp guides/mobile-layout.md §Sözleşme S12',
92
+ summary: 'Ekranda tek app-bar yok: kabuk verirken sayfada ikinci app-bar ya da app-bar ilk çizilen öğe değil'
93
+ },
94
+ 'MOB-018': {
95
+ id: 'MOB-018',
96
+ severity: 'info',
97
+ sourceRef: 'zeyrek-mcp guides/mobile-layout.md §Sözleşme S13',
98
+ summary: 'Boş kap: çocuksuz, görünürlüğe bağlı olmayan bırakılabilir kap ekranda boş şerit bırakır'
99
+ },
100
+ 'MOB-019': {
101
+ id: 'MOB-019',
102
+ severity: 'warn',
103
+ sourceRef: 'zeyrek-mcp guides/mobile-layout.md §Sözleşme S6/S8; zeyrek-frontend ng-apps-screen-builder spacing.ts + ng-apps-ui-native renderer.functions.ts nodeSpacingStyle (ayna: nativeBoxModel.ts)',
104
+ summary: 'Kenar kayması: sütun akışındaki öğe kardeşlerinden içeride/dışarıda başlıyor (zeminsiz kap ya da sarmalayıcı içeriği iter)'
105
+ },
106
+ 'MOB-020': {
107
+ id: 'MOB-020',
108
+ severity: 'warn',
109
+ sourceRef: 'zeyrek-mcp guides/mobile-layout.md §Sözleşme S8/S9; renderer.functions.ts containerDirection (yazılmamış yön satır)',
110
+ summary: "Genişlik tutarsızlığı: sütunda fill olmayan tam genişlik öğesi ya da form alanı taşıyan satır kabı"
111
+ },
112
+ 'MOB-021': {
113
+ id: 'MOB-021',
114
+ severity: 'info',
115
+ sourceRef: 'zeyrek-mcp guides/mobile-layout.md §Sözleşme S5/S7/S11',
116
+ summary: 'Ölçek dışı değer: özel boşluk ya da sayısal gap 0/4/8/16/24/32 dışında, ham yarıçap, px yazı boyu'
117
+ }
118
+ };
119
+ /**
120
+ * MOB-011: dosya nesnesini görsel olarak çizen prop'lar (mobil `resolveImageSource` tüketicileri).
121
+ * Hedefin tipi bilinmiyorsa (var olan düğüme `set_binding`) prop adı yeter — `IMAGE_SOURCE_PROP_NAMES`.
122
+ */
123
+ export const IMAGE_SOURCE_PROPS = {
124
+ image: ['src'],
125
+ avatar: ['src'],
126
+ 'list-item': ['leadingImage'],
127
+ card: ['image']
128
+ };
129
+ export const IMAGE_SOURCE_PROP_NAMES = new Set(Object.values(IMAGE_SOURCE_PROPS).flat());
130
+ const TEMPLATE = /\{\{([\s\S]*?)\}\}/g;
131
+ const MEMBER_PATH = /^[A-Za-z_$][\w$]*(?:\??\.[A-Za-z_$][\w$]*|\[\s*(?:\d+|'[^']*'|"[^"]*")\s*\])+$/;
132
+ /**
133
+ * MOB-011'in desen denetimi. Gateway sayfa şemasından bir alanın FILE olduğunu BİLEMEZ (alan tipi
134
+ * entity'de, ifade yalnız bir yol) — bu yüzden kural metinseldir ve uyarı düzeyindedir: en az üç
135
+ * parçalı bir üye yolu (`item.foto.value`, `pages.x.params.kayit.foto.value`) `.value` ile bitiyorsa
136
+ * ilk eşleşen `{{ }}` şablonunu döner. İki parçalı yol (`urlInput.value` — bileşenin kendi değeri) ve
137
+ * `.value.value` (dosya alanının base64'ü: `fileInput1.value.value`) eşleşmez.
138
+ */
139
+ export function fileIdImageExpression(template) {
140
+ for (const match of template.matchAll(TEMPLATE)) {
141
+ const body = (match[1] ?? '').trim();
142
+ if (!MEMBER_PATH.test(body))
143
+ continue;
144
+ const segments = body.match(/[A-Za-z_$][\w$]*|\[[^\]]*\]/g) ?? [];
145
+ if (segments.length >= 3 && segments[segments.length - 1] === 'value' && segments[segments.length - 2] !== 'value')
146
+ return match[0];
147
+ }
148
+ return undefined;
149
+ }
150
+ /** `redirect` adımının tanıdığı sunum biçimleri (b-01 `StepArgument.Presentation`). */
151
+ export const REDIRECT_PRESENTATIONS = new Set(['push', 'modal', 'replace']);
152
+ /**
153
+ * Bileşen dalgaları (`plans/mobil-uygulama-expo/01-mimari-tasarim.md` §11). MOB-003 bir web bileşeninin
154
+ * mobilde NE ZAMAN geleceğini söyler; `web-only` hiç gelmeyecekler ve karşılıkları.
155
+ *
156
+ * Mobil pakete giren bileşen Dalga 2/3 listesinden DÜŞER: yayında olduğunu mobil sözlükteki kaydı
157
+ * söyler (`mobileComponentKnowledge`, parite testiyle `components.json`'a kilitli). Katalog A
158
+ * (2026-09-24) sekiz web ikizini buradan indirdi: `radio`, `checkboxgroup`, `slider`, `tabs`, `badge`,
159
+ * `avatar`, `progress`, `collapsible-container`; Katalog C (09) Dalga 3'ten `video`, `signature`, `chart`,
160
+ * `fileInput` ve `filebutton`ı, Dalga 2'den `datepicker` ve `timepicker`ı (`fileUpload`un sürükle-bırak alanı
161
+ * dokunmatikte yok: web'e özgü, karşılığı iki dosya alanı); dil dalgası (plans/mobil-coklu-dil F1, 2026-09-25)
162
+ * Dalga 2'den `language-selector`ı; faz 10b (2026-09-27) Dalga 3'ten `datatable`ı; plans/mobil-plantcare-acigi f-11
163
+ * (2026-09-27) listede olmayan `daterangepicker`i doğrudan pakete aldı; f-20 (2026-09-28) Dalga 3'ten `html`ı (mobilde
164
+ * geçici çözüm kapısı, K7); f-12 (2026-09-28) listede olmayan `eventcalendar`ı (web ikizi, ay/hafta/gün); f-14 (2026-09-28)
165
+ * Dalga 3'ten `timeline`ı ve listede olmayan `dropdown-button`ı (web ikizleri) ile mobile özgü `skeleton` ve `photo-viewer`ı. Dalga 1
166
+ * listesi artık yalnız `mobileNotes`'un bilgi satırıdır — MOB-003'te yayındaki bileşeni sözlük tanır
167
+ * (`box-section` Dalga 1'de ama pakette yok).
168
+ */
169
+ export const MOBILE_COMPONENT_WAVES = Object.fromEntries([
170
+ ...['container', 'box-section', 'form', 'slot', 'text', 'icon', 'image', 'divider', 'spacer', 'button', 'textinput', 'textarea', 'numberinput', 'password', 'checkbox', 'switch', 'dropdown', 'repeater', 'card', 'emptystate', 'form-validation-errors', 'modal', 'drawer', 'app-bar'].map(slug => [slug, 1]),
171
+ ...['box', 'phoneinput', 'statcard', 'detailview', 'page-header', 'autocomplete'].map(slug => [slug, 2]),
172
+ ...['kanban', 'texteditor', 'stepper', 'tree-view'].map(slug => [slug, 3])
173
+ ]);
174
+ export const MOBILE_WEB_ONLY = {
175
+ iframe: "'webview' (src ya da html; bağlantılar varsayılan olarak sistem tarayıcısında açılır)",
176
+ sidebar: "'drawer' widget'ı + app-bar.menuTarget",
177
+ navigations: 'Ayarlar › Sekmeler (application_update properties.mobile.tabs)',
178
+ header: "'app-bar'",
179
+ 'layout-shell': "tek slot'lu sıradan layout + 'app-bar'",
180
+ 'layout-region': "tek slot'lu sıradan layout",
181
+ 'auth-layout': "tek slot'lu sıradan layout",
182
+ popover: "'modal' widget'ı",
183
+ 'theme-toggle': "uygulama içi tema ayarı: 'choice-chips' (variant: segmented, Sistem/Açık/Koyu) → storage adımı name: 'zeyrek:theme-mode', value: system | light | dark; barındırıcı temayı anında değiştirir, cihazda kalıcı (seçim yazılmadıysa cihaz teması)",
184
+ pagination: "'repeater' + daha fazla yükle düğmesi",
185
+ breadcrumb: "yığın gezinmesi (app-bar geri düğmesi)",
186
+ fileUpload: "'fileInput' (alan) ya da 'filebutton' (düğme) — dokunmatikte sürükle-bırak yok; ikisi de aynı base64 değeri yazar"
187
+ };
188
+ const STYLE_REPLACEMENTS = {
189
+ className: "yerine surfaceStyle (zemin, kenar, köşe, gölge) ve typographyStyle tipli gruplarını kullanın",
190
+ tooltip: "dokunmatikte üzerine gelme yok — düğümü 'tooltip' bileşenine sarın (dokunuşla açılır) ya da açıklamayı metin olarak koyun",
191
+ tooltipPosition: "dokunmatikte üzerine gelme yok — 'tooltip' bileşeninde balonun yanı `side` prop'udur",
192
+ zIndex: "görselin üstüne rozet/hap/etiket/açıklama bindirmek için 'stack' (ilk çocuk taban, ötekiler üst katman; katman olarak fill×fill bir container koyup kendi hizasıyla köşeye yerleştirin), ekrana sabit içerik (FAB, alt eylem çubuğu, üst şerit) için 'floating-container' (anchor 9 nokta, offsetX/offsetY, fullWidth), kaydırırken üstte kalan başlık için scroll-view çocuğunda states.isSticky, sayfayı örten katman için 'modal' ya da 'drawer' widget'ı",
193
+ style: "serbest CSS nesnesi React Native'de çizilmez — surfaceStyle ve typographyStyle tipli gruplarına taşıyın; position/zIndex ile ekrana sabitlemek yerine 'floating-container' kullanın",
194
+ hoverStyle: 'dokunmatikte üzerine gelme durumu yok'
195
+ };
196
+ export function styleReplacement(key) {
197
+ if (key.startsWith('surfaceStyle.')) {
198
+ const fields = (STYLE_GROUP_FIELDS.surfaceStyle ?? []).map(field => `\`${field}\``).join(', ');
199
+ return `yüzey grubunun alanları ${fields} (döndürme derece, ölçek yüzde — 100 aynı boy, kaydırma px)`;
200
+ }
201
+ return STYLE_REPLACEMENTS[key] ?? 'mobil çizer bu alanı okumaz';
202
+ }
203
+ export function missingComponentAdvice(slug) {
204
+ // Yayındaki bileşen: katalog senkronu bu uygulamaya henüz yazmamış olabilir — "gelecek" demek yanlış olur.
205
+ if (Object.hasOwn(mobileComponentKnowledge, slug))
206
+ return `'${slug}' mobil pakette var ama bu uygulamanın kataloğunda henüz yok — components-sync mobil kataloğu yazdı mı (component_list)? Yazmadıysa backend 422 unknown-type döner`;
207
+ const wave = Object.hasOwn(MOBILE_COMPONENT_WAVES, slug) ? MOBILE_COMPONENT_WAVES[slug] : undefined;
208
+ if (wave === 2)
209
+ return `'${slug}' web'de var, mobilde Dalga 2 (F6 sonrası) — şimdilik Dalga 1 bileşenleriyle kurun (component_list)`;
210
+ if (wave === 3)
211
+ return `'${slug}' web'de var, mobilde Faz 3 (ayrı iş) — bu planda mobil karşılığı yok`;
212
+ const alternative = Object.hasOwn(MOBILE_WEB_ONLY, slug) ? MOBILE_WEB_ONLY[slug] : undefined;
213
+ if (alternative)
214
+ return `'${slug}' web'e özgüdür, mobilde gelmeyecek — ${alternative}`;
215
+ return `'${slug}' mobil katalogda yok — component_list ile mobil uygulamanın bileşenlerini görün`;
216
+ }
217
+ /**
218
+ * `component_get` mobil uygulamada (G4): kayıt mobil kataloğun satırıdır, davranış notları web
219
+ * runtime'ından (`knowledge`), bileşene özgü farklar mobil sözlükten (`mobileKnowledge`) gelir; bu
220
+ * liste BÜTÜN mobil bileşenlere geçen notlardır — kural kodlu üç not + `MOBILE_COMPONENT_NOTES`.
221
+ */
222
+ export function mobileComponentNotes(slug) {
223
+ const wave = slug === undefined ? undefined : MOBILE_COMPONENT_WAVES[slug];
224
+ return [
225
+ "Yerleşim yalnız flex: `layout` prop'u mobil şemalarda yoktur (grid/cssgrid yok), blocks/size yok sayılır, sıra dizi indeksidir; ölçü appearance ile verilir (widthType/heightType — MOB-001).",
226
+ "Görünüm tipli gruplarla: surfaceStyle ve typographyStyle React Native stiline derlenir; ham CSS `style` prop'u `html` dışında hiçbir mobil şemada yoktur (`html`'in metin `style`'ı kendi belgesine kapsamlı girer), className, `tooltip` alanı (ipucu için `tooltip` bileşeni), zIndex ve hoverStyle çizilmez (MOB-002). Karşılığı olmayan değer sessizce düşmez, uyarıya yazılır; düğümün gerçek stili: page_runtime_get(resolveStyles) → nativeStyle.",
227
+ "Basılabilir bileşen role=\"button\" taşır; page_screenshot click onu kabuğun içinde bulur.",
228
+ ...MOBILE_COMPONENT_NOTES,
229
+ ...(wave === undefined ? [] : [`Mobil bileşen dalgası: ${wave} (plans/mobil-uygulama-expo §11).`])
230
+ ];
231
+ }
@@ -0,0 +1,82 @@
1
+ import { MOBILE_TRANSPARENT_WRAPPER_TYPES } from '../mobileComponentCatalog.js';
2
+ import { PAGE_CONTAINER_ELEMENT_ID } from '../normalize.js';
3
+ /**
4
+ * Boşluğunu runtime'ın kendisi çözen tipler: kabuk parçaları ve katmanlar margin'siz çizilir
5
+ * (`ng-apps-ui-native` `FLUSH_SPACING_TYPES`, `@ng-apps/screen-builder` `HIDE_SPACING_TYPES`). `tooltip`
6
+ * f-01'den beri runtime'da da muaf, ama burada bilerek yok: mobil runtime uygulamaya gömülüdür ve f-01'den
7
+ * önce derlenmiş uygulamada açık `none` hizayı yine korur.
8
+ */
9
+ const RUNTIME_FLUSH_TYPES = ['safe-area', 'scroll-view', 'app-bar', 'auth-layout', 'modal', 'drawer'];
10
+ export const emptyMobileSpacingFix = () => ({ margin: [], wrappers: [], legacyParents: [] });
11
+ /**
12
+ * Mizanpaj sözleşmesi S5/S6 (plans/mobil-mizanpaj-dili 02, K3-b): mobilde kardeşler arası boşluk yalnız
13
+ * ebeveynin `gap`'inden gelir. Runtime margin tipi yazılmamış düğüme 4 dp verir ve bu her iç içe katmanda
14
+ * birikir; yeni düğüme `marginType: 'none'` yazılır. Kardeşleri varsayılan margin'le duran (eski) bir
15
+ * kaba eklenen düğüme dokunulmaz — yeni düğüm eskilerden 4 dp kayardı. Saydam sarmalayıcı (`tooltip`)
16
+ * içeriğini içeri itmesin diye padding'siz ve tam genişlik yazılır. Açıkça yazılmış değer korunur.
17
+ */
18
+ export function applyMobileSpacingDefaults(nodes, existingSiblings, fix, parentId = '') {
19
+ const list = asNodes(nodes);
20
+ // Boşluğunu runtime'ın çözdüğü kardeş (kabuksuz sayfanın `safe_area_top`'u gibi) hizaya karar vermez.
21
+ const followsNone = existingSiblings
22
+ .filter(sibling => !RUNTIME_FLUSH_TYPES.includes(String(sibling.type ?? '')))
23
+ .every(sibling => appearanceOf(sibling).marginType === 'none');
24
+ if (!followsNone && list.length > 0 && !fix.legacyParents.includes(parentId))
25
+ fix.legacyParents.push(parentId);
26
+ for (const node of list) {
27
+ const type = String(node.type ?? '');
28
+ const appearance = appearanceOf(node);
29
+ const id = String(node.elementId ?? type);
30
+ if (followsNone && !RUNTIME_FLUSH_TYPES.includes(type) && appearance.marginType === undefined) {
31
+ node.appearance = { ...appearance, marginType: 'none' };
32
+ fix.margin.push(id);
33
+ }
34
+ if (MOBILE_TRANSPARENT_WRAPPER_TYPES.includes(type) && (appearance.paddingType === undefined || appearance.widthType === undefined)) {
35
+ node.appearance = {
36
+ ...appearanceOf(node),
37
+ ...(appearance.paddingType === undefined ? { paddingType: 'none' } : {}),
38
+ ...(appearance.widthType === undefined ? { widthType: 'fill' } : {})
39
+ };
40
+ fix.wrappers.push(id);
41
+ }
42
+ applyMobileSpacingDefaults(node.children, [], fix, id);
43
+ }
44
+ }
45
+ /** `add_component` op'larının (batch içindekiler dâhil) yeni düğümlerine aynı kural; kardeşler kayıtlı ağaçtan. */
46
+ export function applyMobileSpacingToOps(ops, savedSchema, fix) {
47
+ for (const op of ops) {
48
+ if (op.op === 'batch')
49
+ applyMobileSpacingToOps(asNodes(op.ops), savedSchema, fix);
50
+ if (op.op !== 'add_component' || !op.component || typeof op.component !== 'object')
51
+ continue;
52
+ const saved = asNodes(savedSchema);
53
+ const requested = typeof op.parent === 'string' ? op.parent : 'root';
54
+ // Kök ekleme sayfada `page_container`'a gider (yazımda `retargetRootAdds`, kuru koşuda platform).
55
+ const parentId = requested === 'root' && findNode(saved, PAGE_CONTAINER_ELEMENT_ID) ? PAGE_CONTAINER_ELEMENT_ID : requested;
56
+ const siblings = parentId === 'root' ? saved : asNodes(findNode(saved, parentId)?.children);
57
+ applyMobileSpacingDefaults([op.component], siblings, fix, parentId);
58
+ }
59
+ }
60
+ export function mobileSpacingNote(fix) {
61
+ return [
62
+ ...(fix.margin.length > 0 ? [`mobil: margin tipi yazılmamış ${fix.margin.length} düğüme marginType:'none' yazıldı (${fix.margin.slice(0, 8).join(', ')}${fix.margin.length > 8 ? ', …' : ''}) — kardeşler arası boşluk yalnız ebeveynin gap'inden gelir; runtime'ın varsayılan 4 dp margin'i iç içe katmanlarda birikir (mizanpaj sözleşmesi S5). Bilerek boşluk istediğin düğüme marginType yaz`] : []),
63
+ ...(fix.wrappers.length > 0 ? [`mobil: saydam sarmalayıcıya (${fix.wrappers.join(', ')}) paddingType:'none' ve widthType:'fill' yazıldı — padding'i içeriğini kardeşlerinden içeri iterdi (S6)`] : []),
64
+ ...(fix.legacyParents.length > 0 ? [`mobil: kardeşleri varsayılan margin'le duran kapta (${fix.legacyParents.join(', ')}) yeni düğümün margin'ine dokunulmadı — eskilerle aynı hizada kalsın diye. Kabı sözleşmeye taşımak için bütün çocuklarına marginType:'none' yazın`] : [])
65
+ ];
66
+ }
67
+ function asNodes(value) {
68
+ return Array.isArray(value) ? value.filter((item) => !!item && typeof item === 'object') : [];
69
+ }
70
+ function appearanceOf(node) {
71
+ return node.appearance && typeof node.appearance === 'object' ? node.appearance : {};
72
+ }
73
+ function findNode(nodes, elementId) {
74
+ for (const node of nodes) {
75
+ if (node.elementId === elementId)
76
+ return node;
77
+ const found = findNode(asNodes(node.children), elementId);
78
+ if (found)
79
+ return found;
80
+ }
81
+ return undefined;
82
+ }