@appaflytech/zeyrek-ai-mcp 0.0.30 → 0.0.32
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.
- package/.env.example +0 -15
- package/README.md +5 -5
- package/dist/config.js +0 -37
- package/dist/index.js +0 -2
- package/dist/knowledge/backendConstraints.js +1 -18
- package/dist/knowledge/boxSections.js +184 -0
- package/dist/knowledge/componentCatalog.js +232 -562
- package/dist/knowledge/componentEvents.js +21 -141
- package/dist/knowledge/layoutSummary.js +2 -140
- package/dist/knowledge/normalize.js +67 -236
- package/dist/knowledge/outlines.js +0 -36
- package/dist/knowledge/policy.js +4 -18
- package/dist/knowledge/reservedTables.js +0 -79
- package/dist/knowledge/rules.js +21 -319
- package/dist/knowledge/schemaGuard.js +0 -11
- package/dist/knowledge/styleResolve.js +38 -55
- package/dist/knowledge/themeResolve.js +3 -3
- package/dist/observability/trace.js +3 -6
- package/dist/platform/diagnostics.js +4 -10
- package/dist/platform/domSummary.js +1 -1
- package/dist/platform/errors.js +10 -162
- package/dist/platform/http.js +8 -29
- package/dist/platform/responses.js +5 -39
- package/dist/platform/{capture/capture.js → screenshot.js} +519 -105
- package/dist/platform/secrets.js +1 -17
- package/dist/platform/strictArguments.js +12 -68
- package/dist/platform/tokenVault.js +4 -187
- package/dist/resources/examples/layout-app-shell.json +707 -229
- package/dist/resources/examples/page-detail-profile.json +1 -2
- package/dist/resources/examples/page-dynamic-form.json +2 -3
- package/dist/resources/examples/page-flex-sections.json +1 -1
- package/dist/resources/examples/page-html-embed.json +1 -1
- package/dist/resources/examples/workflow-basic.json +2 -8
- package/dist/resources/guides/component.md +4 -91
- package/dist/resources/guides/entity.md +8 -50
- package/dist/resources/guides/expressions.md +5 -14
- package/dist/resources/guides/functions.md +1 -1
- package/dist/resources/guides/getting-started.md +1 -38
- package/dist/resources/guides/layout.md +58 -55
- package/dist/resources/guides/microservice.md +2 -81
- package/dist/resources/guides/middleware.md +2 -44
- package/dist/resources/guides/page.md +75 -454
- package/dist/resources/guides/theme.md +2 -8
- package/dist/resources/guides/translation.md +1 -1
- package/dist/resources/guides/workflow.md +1 -58
- package/dist/resources/rules.js +5 -11
- package/dist/resources/schema-fingerprint.json +5 -7
- package/dist/resources/schemas/CreateEntityCommand.json +0 -118
- package/dist/resources/schemas/CreateMicroserviceCommand.json +1 -13
- package/dist/resources/schemas/CreateQueryCommand.json +0 -4
- package/dist/resources/schemas/CreateWorkflowCommand.json +1 -143
- package/dist/resources/schemas/DataJobDto.json +1 -6
- package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +1 -21
- package/dist/resources/schemas/MicroserviceDto.json +3 -15
- package/dist/resources/schemas/PreviewMicroserviceCommand.json +1 -10
- package/dist/resources/schemas/PreviewWorkflowCommand.json +1 -22
- package/dist/resources/schemas/StepDto.json +1 -22
- package/dist/resources/schemas/UpdateEntityCommand.json +0 -118
- package/dist/resources/schemas/UpdateMicroserviceCommand.json +1 -13
- package/dist/resources/schemas/UpdateQueryCommand.json +0 -4
- package/dist/resources/schemas/UpdateViewCommand.json +2 -0
- package/dist/resources/schemas/UpdateWorkflowCommand.json +1 -143
- package/dist/server.js +1 -27
- package/dist/tools/access.js +2 -369
- package/dist/tools/applications.js +3 -60
- package/dist/tools/common.js +0 -7
- package/dist/tools/components.js +14 -118
- package/dist/tools/datasources.js +3 -3
- package/dist/tools/dependencies.js +1 -1
- package/dist/tools/deployments.js +2 -2
- package/dist/tools/designSchemaPatch.js +18 -34
- package/dist/tools/dtos.js +5 -36
- package/dist/tools/entities.js +12 -79
- package/dist/tools/externalComponents.js +5 -26
- package/dist/tools/flows.js +14 -122
- package/dist/tools/folders.js +1 -1
- package/dist/tools/imports.js +2 -5
- package/dist/tools/layouts.js +17 -50
- package/dist/tools/merges.js +1 -1
- package/dist/tools/microservices.js +25 -77
- package/dist/tools/pages.js +112 -465
- package/dist/tools/platformInfo.js +8 -27
- package/dist/tools/projects.js +9 -71
- package/dist/tools/rows.js +13 -69
- package/dist/tools/schemaOps.js +1 -61
- package/dist/tools/screenWrite.js +8 -37
- package/dist/tools/screens.js +5 -95
- package/dist/tools/templates.js +1 -26
- package/dist/tools/translations.js +1 -1
- package/dist/tools/variables.js +5 -11
- package/dist/tools/workflows.js +5 -10
- package/package.json +2 -2
- package/dist/knowledge/access.js +0 -802
- package/dist/knowledge/case.js +0 -41
- package/dist/knowledge/connectDataBind.js +0 -819
- package/dist/knowledge/connectDataDefinition.js +0 -341
- package/dist/knowledge/connectDataExecute.js +0 -93
- package/dist/knowledge/connectDataRules.js +0 -128
- package/dist/knowledge/decision.js +0 -121
- package/dist/knowledge/expressionPrelude.js +0 -59
- package/dist/knowledge/microserviceParameters.js +0 -58
- package/dist/knowledge/mining.js +0 -94
- package/dist/knowledge/mobileComponentCatalog.js +0 -859
- package/dist/knowledge/mobileComponentEvents.js +0 -239
- package/dist/knowledge/platform/lint.js +0 -385
- package/dist/knowledge/platform/mobile.js +0 -142
- package/dist/knowledge/platform/mobileLayoutLint.js +0 -324
- package/dist/knowledge/platform/mobileRules.js +0 -231
- package/dist/knowledge/platform/mobileSpacing.js +0 -82
- package/dist/knowledge/platform/mobileTabs.js +0 -108
- package/dist/knowledge/platform/nativeBoxModel.js +0 -44
- package/dist/knowledge/platform/profile.js +0 -25
- package/dist/knowledge/platform/resolve.js +0 -122
- package/dist/knowledge/platform/web.js +0 -39
- package/dist/knowledge/process.js +0 -510
- package/dist/knowledge/rowFilter.js +0 -43
- package/dist/knowledge/styleResolveNative.js +0 -527
- package/dist/platform/argumentAliases.js +0 -128
- package/dist/platform/bodyFile.js +0 -103
- package/dist/platform/capture/actions.js +0 -333
- package/dist/platform/capture/browser.js +0 -1
- package/dist/platform/capture/console-log.js +0 -122
- package/dist/platform/capture/index.js +0 -11
- package/dist/platform/capture/readiness.js +0 -103
- package/dist/platform/capture/summary.js +0 -77
- package/dist/platform/capture/target.js +0 -5
- package/dist/platform/capture/targets/studio-mobile.js +0 -15
- package/dist/platform/capture/targets/studio-web.js +0 -5
- package/dist/platform/impersonation.js +0 -45
- package/dist/platform/jsonArray.js +0 -24
- package/dist/platform/schemaReadMemory.js +0 -60
- package/dist/resources/examples/bpm-gorev-kontrol.json +0 -294
- package/dist/resources/examples/bpm-iso-dokuman.json +0 -228
- package/dist/resources/examples/bpm-satinalma.json +0 -376
- package/dist/resources/examples/connect-data-convert-verify.json +0 -107
- package/dist/resources/examples/connect-data-dropdown-roles.json +0 -66
- package/dist/resources/examples/connect-data-edit-m2m.json +0 -73
- package/dist/resources/examples/connect-data-query-params.json +0 -50
- package/dist/resources/examples/connect-data-table-entity.json +0 -147
- package/dist/resources/examples/connect-data-workflow-microservice.json +0 -65
- package/dist/resources/examples/mobile-app-shell-drawer.json +0 -243
- package/dist/resources/examples/mobile-app-shell-stack.json +0 -157
- package/dist/resources/examples/mobile-app-shell-tab.json +0 -208
- package/dist/resources/examples/mobile-form-screen.json +0 -365
- package/dist/resources/examples/mobile-list-detail-form.json +0 -672
- package/dist/resources/guides/access.md +0 -796
- package/dist/resources/guides/bpm.md +0 -686
- package/dist/resources/guides/case.md +0 -153
- package/dist/resources/guides/decision.md +0 -185
- package/dist/resources/guides/mobile-layout.md +0 -193
- package/dist/resources/guides/mobile.md +0 -495
- package/dist/resources/read.js +0 -41
- package/dist/resources/schemas/AccessExplanationDto.json +0 -144
- package/dist/resources/schemas/AccessMatrixDto.json +0 -170
- package/dist/resources/schemas/AccessPermissionDto.json +0 -57
- package/dist/resources/schemas/AccessPolicyDto.json +0 -223
- package/dist/resources/schemas/AccessResourceDto.json +0 -86
- package/dist/resources/schemas/AppSessionDto.json +0 -53
- package/dist/resources/schemas/ConnectDataDescribeDto.json +0 -344
- package/dist/resources/schemas/ConnectDataRequestModel.json +0 -202
- package/dist/resources/schemas/ConnectDataResultDto.json +0 -421
- package/dist/resources/schemas/ConnectorCredentialDto.json +0 -70
- package/dist/resources/schemas/ConvertConnectDataToMicroserviceCommand.json +0 -37
- package/dist/resources/schemas/CreateAccessPolicyCommand.json +0 -209
- package/dist/resources/schemas/CreateDecisionCommand.json +0 -221
- package/dist/resources/schemas/CreateProcessDefinitionCommand.json +0 -1337
- package/dist/resources/schemas/DecisionDto.json +0 -302
- package/dist/resources/schemas/DetachConnectDataMicroserviceCommand.json +0 -28
- package/dist/resources/schemas/ExplainAccessQuery.json +0 -33
- package/dist/resources/schemas/GenerateProcessDocumentCommand.json +0 -116
- package/dist/resources/schemas/OrganizationProjectDto.json +0 -335
- package/dist/resources/schemas/OrganizationSchemaDto.json +0 -196
- package/dist/resources/schemas/OrganizationSchemaVersionDto.json +0 -207
- package/dist/resources/schemas/PatchProcessDefinitionCommand.json +0 -76
- package/dist/resources/schemas/ProcessDefinitionDto.json +0 -1480
- package/dist/resources/schemas/ProcessSimulationDto.json +0 -96
- package/dist/resources/schemas/SaveConnectorCredentialCommand.json +0 -90
- package/dist/resources/schemas/SaveProcessEventSourceCommand.json +0 -61
- package/dist/resources/schemas/SaveProcessMailSourceCommand.json +0 -89
- package/dist/resources/schemas/SignProcessTaskCommand.json +0 -73
- package/dist/resources/schemas/SimulateProcessDefinitionQuery.json +0 -92
- package/dist/resources/schemas/UpdateAccessPolicyCommand.json +0 -209
- package/dist/resources/schemas/UpdateDecisionCommand.json +0 -216
- package/dist/resources/schemas/UpdateProcessDefinitionCommand.json +0 -1357
- package/dist/resources/schemas/ValidateProcessDefinitionQuery.json +0 -1339
- package/dist/tools/accessOperations.js +0 -135
- package/dist/tools/accessPolicies.js +0 -170
- package/dist/tools/accessRuntime.js +0 -203
- package/dist/tools/bpm.js +0 -343
- package/dist/tools/bpmGovernance.js +0 -266
- package/dist/tools/bpmOperations.js +0 -230
- package/dist/tools/bpmReporting.js +0 -154
- package/dist/tools/bpmRouting.js +0 -150
- package/dist/tools/bpmRuntime.js +0 -396
- package/dist/tools/case.js +0 -178
- package/dist/tools/caseRuntime.js +0 -89
- package/dist/tools/connectData.js +0 -449
- package/dist/tools/connectors.js +0 -193
- package/dist/tools/decisions.js +0 -206
- package/dist/tools/mobile.js +0 -221
- package/dist/tools/modules.js +0 -48
- package/dist/tools/organizationStructure.js +0 -137
- package/dist/tools/profileWrite.js +0 -92
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import { ENGINE_STEP_TYPES, STATE_STORAGE_KINDS, STEP_ALIASES } from './profile.js';
|
|
2
|
-
import { PAGE_CONTAINER_ELEMENT_ID } from '../normalize.js';
|
|
3
|
-
/**
|
|
4
|
-
* Mobil uygulamanın kök kapsayıcısı. Web'in `pageContainerNode()`'undan üç farkı var:
|
|
5
|
-
*
|
|
6
|
-
* - `layout` prop'u YOK: mobil `container` şeması onu düşürür (`@ng-apps/ui-native`
|
|
7
|
-
* `components/container/schema.ts` `omitProps(['layout', …])`) — mobil kanvas yalnız flex akışıdır
|
|
8
|
-
* ve backend doğrulayıcısı mobil kataloğa bakar, yazılan `layout` bilinmeyen prop olurdu.
|
|
9
|
-
* - `direction: 'column'`, `wrap: false`: telefon ekranı dikey akar; web kökü satır + sarma ile
|
|
10
|
-
* 12 sütunu taklit eder.
|
|
11
|
-
* - `blocks: []` ve `heightType: 'fill'`: çizer blokları yok sayar (D3), kök ekranı doldurur.
|
|
12
|
-
*
|
|
13
|
-
* Mizanpaj sözleşmesi (plans/mobil-mizanpaj-dili 02): kök `size: shrink` (S-16: `grow` çocukları ekran
|
|
14
|
-
* boyunca eşit paya böler) ve `horizontalAlign: stretch` (Studio şablonu). Şablon kabuklu sayfanındır:
|
|
15
|
-
* üst güvenli alan ve 16 dp yatay kenar boşluğu kabuğun işidir (`safe-area top` + slot margin'i). Kabuksuz
|
|
16
|
-
* sayfayı `shellLessPageSkeleton` tamamlar — sayfanın layout'u ancak yazımda kesinleşir.
|
|
17
|
-
*/
|
|
18
|
-
export function mobileRootContainer() {
|
|
19
|
-
return {
|
|
20
|
-
elementId: PAGE_CONTAINER_ELEMENT_ID,
|
|
21
|
-
kind: 'component',
|
|
22
|
-
name: PAGE_CONTAINER_ELEMENT_ID,
|
|
23
|
-
type: 'container',
|
|
24
|
-
props: {
|
|
25
|
-
title: { type: 'static', data: '' },
|
|
26
|
-
direction: { type: 'static', data: 'column' },
|
|
27
|
-
horizontalAlign: { type: 'static', data: 'stretch' },
|
|
28
|
-
verticalAlign: { type: 'static', data: 'top' },
|
|
29
|
-
size: { type: 'static', data: 'shrink' },
|
|
30
|
-
gap: { type: 'static', data: 'medium' },
|
|
31
|
-
wrap: { type: 'static', data: false }
|
|
32
|
-
},
|
|
33
|
-
blocks: [],
|
|
34
|
-
states: { isSticky: false, isDroppable: true, isScrollable: true, isWrapper: true },
|
|
35
|
-
appearance: { heightType: 'fill', marginValue: 0, marginType: 'none', paddingType: 'none', paddingValue: 0 },
|
|
36
|
-
events: [],
|
|
37
|
-
validations: [],
|
|
38
|
-
stateBindings: [],
|
|
39
|
-
children: []
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Kabuksuz sayfanın iskeleti (S1/S4): kök yatay 16 padding taşır — `isWrapper` yalnız margin'i sıfırlar,
|
|
44
|
-
* kökün padding'i çizilir (01'de ölçüldü) — ve ilk çizilen öğe üst güvenli alan değilse tepeye
|
|
45
|
-
* `safe_area_top` konur. Gateway'in kurduğu köke uygulanır; notu döner.
|
|
46
|
-
*/
|
|
47
|
-
export function shellLessPageSkeleton(root) {
|
|
48
|
-
const children = Array.isArray(root.children) ? root.children : [];
|
|
49
|
-
const addsSafeArea = topSafeAreaMissing(children);
|
|
50
|
-
root.appearance = { ...root.appearance, paddingType: 'custom', paddingValues: { top: 0, right: 16, bottom: 0, left: 16 } };
|
|
51
|
-
root.children = addsSafeArea ? [topSafeArea(), ...children] : children;
|
|
52
|
-
return [`mobil kabuksuz sayfa: köke yatay 16 padding yazıldı${addsSafeArea ? " ve tepeye üst güvenli alan (safe_area_top, edges:'top') eklendi" : ''} — ekran kenarı boşluğu ve durum çubuğu payı (mizanpaj S1/S4). Kabuklu sayfada ikisi kabuğun işidir: layoutId verin`];
|
|
53
|
-
}
|
|
54
|
-
/** Ekrana sabit katmanlar akışta değildir; üst payı onlar vermez (`topSafeAreaMissing`, Studio kanvası). */
|
|
55
|
-
export const OVERLAY_TYPES = ['drawer', 'fab', 'floating-container', 'modal', 'toast'];
|
|
56
|
-
/** `safe-area`'nın `edges`'i; yazılmamışsa `all` (Studio `KNOWN_SAFE_EDGES`). */
|
|
57
|
-
export function safeAreaEdges(node) {
|
|
58
|
-
const edges = node.props?.edges;
|
|
59
|
-
const value = edges && typeof edges === 'object' ? edges.data : edges;
|
|
60
|
-
return typeof value === 'string' && value !== '' ? value : 'all';
|
|
61
|
-
}
|
|
62
|
-
/** Akışta çizilen ilk öğe (kaplama katmanları ve yüzen düğümler atlanır). */
|
|
63
|
-
export function firstDrawn(nodes) {
|
|
64
|
-
return nodes.find(node => !OVERLAY_TYPES.includes(String(node.type ?? '')) && node.states?.isFloating !== true);
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* İlk çizilen öğe üst payı veriyor mu — Studio'nun `topSafeAreaMissing`'iyle aynı anlam
|
|
68
|
-
* (`apps/studio/src/features/mobile/mobile-native-canvas.functions.ts`): `edges` yazılmamışsa `all`dır,
|
|
69
|
-
* yalnız `bottom` üst payı vermez; ilk öğe çocuklu bir kapsa onun ilk öğesine bakılır. Boş sayfa da pay alır.
|
|
70
|
-
*/
|
|
71
|
-
export function topSafeAreaMissing(nodes, mode = 'skeleton') {
|
|
72
|
-
const first = firstDrawn(nodes);
|
|
73
|
-
if (!first)
|
|
74
|
-
return true;
|
|
75
|
-
if (first.type === 'safe-area')
|
|
76
|
-
return safeAreaEdges(first) === 'bottom';
|
|
77
|
-
const children = Array.isArray(first.children) ? first.children : [];
|
|
78
|
-
if (children.length > 0)
|
|
79
|
-
return topSafeAreaMissing(children, mode);
|
|
80
|
-
// Studio: boş kap hiçbir şey çizmez, korunacak içerik de yoktur — uyarı yok. İskelet ise payı yine kurar.
|
|
81
|
-
return mode === 'studio' ? first.states?.isDroppable !== true : true;
|
|
82
|
-
}
|
|
83
|
-
/** Studio'nun yeni sayfa şablonundaki payla aynı (`new-page-schema.functions.ts`). */
|
|
84
|
-
function topSafeArea() {
|
|
85
|
-
return {
|
|
86
|
-
elementId: 'safe_area_top',
|
|
87
|
-
kind: 'component',
|
|
88
|
-
name: 'safe_area_top',
|
|
89
|
-
type: 'safe-area',
|
|
90
|
-
props: { edges: { type: 'static', data: 'top' } },
|
|
91
|
-
blocks: [],
|
|
92
|
-
states: { isSticky: false, isDroppable: true, isScrollable: false, isWrapper: false },
|
|
93
|
-
appearance: { heightType: 'auto', widthType: 'fill' },
|
|
94
|
-
events: [],
|
|
95
|
-
validations: [],
|
|
96
|
-
stateBindings: [],
|
|
97
|
-
children: []
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Mobil profil = taşınabilir alt küme (`plans/mobil-uygulama-expo/01-mimari-tasarim.md` §5, §7).
|
|
102
|
-
* Yazım platforma YİNE gider (D3: şema aynı); alt kümenin dışı `MOB-*` bulgusu olarak uyarılır.
|
|
103
|
-
*/
|
|
104
|
-
export const mobileProfile = Object.freeze({
|
|
105
|
-
platform: 'mobile',
|
|
106
|
-
catalogType: 'mobile',
|
|
107
|
-
layout: {
|
|
108
|
-
modes: new Set(['flex']),
|
|
109
|
-
blocksMeaningful: false,
|
|
110
|
-
rootContainer: mobileRootContainer,
|
|
111
|
-
orderBy: 'array-index'
|
|
112
|
-
},
|
|
113
|
-
appearance: {
|
|
114
|
-
ignoredKeys: new Set(['className', 'tooltip', 'tooltipPosition', 'zIndex']),
|
|
115
|
-
sizeUnits: new Set(['px', '%'])
|
|
116
|
-
},
|
|
117
|
-
style: { freeStyle: false, hoverStyle: false, resolver: 'native' },
|
|
118
|
-
steps: {
|
|
119
|
-
registered: new Set([...ENGINE_STEP_TYPES, 'alert', 'confirm', 'redirect', 'navigate', 'storage', 'setcookie', 'download', 'share', 'haptic', 'location', 'notification-permission', 'notification-schedule', 'notification-cancel']),
|
|
120
|
-
aliases: STEP_ALIASES,
|
|
121
|
-
infoOnly: new Set(['download', 'setcookie'])
|
|
122
|
-
},
|
|
123
|
-
shell: {
|
|
124
|
-
components: new Set(['app-bar']),
|
|
125
|
-
suggestions: {
|
|
126
|
-
navigations: "sekmeler: application_update ile properties.mobile.tabs (Ayarlar › Sekmeler); üst çubuk için 'app-bar'",
|
|
127
|
-
sidebar: "çekmece: 'drawer' widget'ı + app-bar.leading:'menu' ve menuTarget",
|
|
128
|
-
header: "'app-bar' (başlık, leading: auto | none | back | menu, sağ eylemler)"
|
|
129
|
-
},
|
|
130
|
-
legacyShellNodes: new Set(['tab-bar', 'drawer-menu'])
|
|
131
|
-
},
|
|
132
|
-
externalComponents: false,
|
|
133
|
-
state: { storageKinds: new Set(STATE_STORAGE_KINDS) },
|
|
134
|
-
capture: {
|
|
135
|
-
target: 'studio-mobile',
|
|
136
|
-
devices: {
|
|
137
|
-
phone: { width: 390, height: 844, dpr: 2 },
|
|
138
|
-
tablet: { width: 820, height: 1180, dpr: 2 }
|
|
139
|
-
},
|
|
140
|
-
defaultDevice: 'phone'
|
|
141
|
-
}
|
|
142
|
-
});
|
|
@@ -1,324 +0,0 @@
|
|
|
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
|
-
}
|