@appaflytech/zeyrek-ai-mcp 0.0.37 → 0.0.41
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/dist/knowledge/componentCatalog.js +457 -156
- package/dist/knowledge/componentEvents.js +49 -8
- package/dist/knowledge/connectDataBind.js +10 -2
- package/dist/knowledge/expressionPrelude.js +1 -0
- package/dist/knowledge/mobileComponentCatalog.js +21 -9
- package/dist/knowledge/mobileComponentEvents.js +21 -1
- package/dist/knowledge/mobileStore.js +75 -0
- package/dist/knowledge/normalize.js +52 -17
- package/dist/knowledge/platform/mobile.js +1 -1
- package/dist/knowledge/platform/mobileRules.js +1 -1
- package/dist/knowledge/platform/web.js +1 -1
- package/dist/knowledge/rules.js +244 -25
- package/dist/knowledge/styleResolve.js +27 -8
- package/dist/knowledge/styleResolveNative.js +4 -2
- package/dist/knowledge/themeResolve.js +1 -1
- package/dist/knowledge/themeSchema.js +2 -1
- package/dist/observability/trace.js +7 -2
- package/dist/platform/capture/actions.js +24 -1
- package/dist/platform/capture/summary.js +4 -1
- package/dist/platform/secrets.js +50 -2
- package/dist/resources/examples/mobile-app-shell-drawer.json +1 -0
- package/dist/resources/examples/mobile-list-detail-form.json +3 -0
- package/dist/resources/guides/component.md +1 -1
- package/dist/resources/guides/entity.md +16 -0
- package/dist/resources/guides/expressions.md +7 -6
- package/dist/resources/guides/merge.md +5 -4
- package/dist/resources/guides/microservice.md +2 -2
- package/dist/resources/guides/middleware.md +3 -1
- package/dist/resources/guides/mobile-publish.md +24 -0
- package/dist/resources/guides/mobile.md +50 -25
- package/dist/resources/guides/page.md +61 -23
- package/dist/resources/guides/theme.md +1 -0
- package/dist/resources/guides/translation.md +41 -1
- package/dist/resources/guides/workflow.md +31 -1
- package/dist/resources/schema-fingerprint.json +3 -3
- package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +2 -1
- package/dist/resources/schemas/OrganizationProjectDto.json +8 -0
- package/dist/resources/schemas/UpsertMobileStoreSettingsCommand.json +59 -0
- package/dist/tools/applications.js +6 -3
- package/dist/tools/common.js +9 -5
- package/dist/tools/entities.js +41 -4
- package/dist/tools/environments.js +1 -1
- package/dist/tools/flows.js +5 -1
- package/dist/tools/layouts.js +22 -7
- package/dist/tools/mobile.js +15 -1
- package/dist/tools/mobileStore.js +21 -5
- package/dist/tools/pages.js +31 -45
- package/dist/tools/queries.js +1 -1
- package/dist/tools/rows.js +14 -3
- package/dist/tools/screenWrite.js +2 -2
- package/dist/tools/screens.js +39 -0
- package/dist/tools/translations.js +148 -6
- package/package.json +1 -1
package/dist/knowledge/rules.js
CHANGED
|
@@ -5,6 +5,7 @@ import { headersAccessNames, isHeadersAccessGuarded } from './flowGuards.js';
|
|
|
5
5
|
import { callsItself, functionCallRefs, isValidParameterName, mentionsAwait, nameProblem, sourceOf, callPrefix } from './functionGuards.js';
|
|
6
6
|
import { backendPolicyRules } from './backendConstraints.js';
|
|
7
7
|
import { accessPolicyRules } from './access.js';
|
|
8
|
+
import { isGuid } from '../platform/refs.js';
|
|
8
9
|
import { themePolicyRules } from './themeRules.js';
|
|
9
10
|
import { processPolicyRules } from './process.js';
|
|
10
11
|
import { mobileStorePolicyRules } from './mobileStore.js';
|
|
@@ -153,11 +154,13 @@ export function readsBeforeGuard(source, root) {
|
|
|
153
154
|
* yaşatılır (microservice.ts:174 `isHasSuccess`). 'error' dalı 2026-09-26'dan beri motorun
|
|
154
155
|
* KENDİSİ tanır (ADR 0004, ZM-07/g-02): bağlıysa hata anında `always` hiç koşmaz, sürer
|
|
155
156
|
* (microservice.ts:182 `hasBranch`) — eski motor tanımadığı dalı hiç aramadığı için bu ekleme
|
|
156
|
-
* geriye dönük zararsızdır.
|
|
157
|
+
* geriye dönük zararsızdır. `validation` tüm kurallar geçince 'success', biri düşünce 'error' dalına,
|
|
158
|
+
* ardından her durumda 'always'e gider (validation.ts; kök ADR 0014, g-18).
|
|
157
159
|
*/
|
|
158
160
|
const flowBranchNames = {
|
|
159
161
|
start: ['always'],
|
|
160
162
|
microservice: ['always', 'success', 'error'],
|
|
163
|
+
validation: ['always', 'success', 'error'],
|
|
161
164
|
confirm: ['yes', 'no', 'always'],
|
|
162
165
|
function: ['always'],
|
|
163
166
|
haptic: ['always'],
|
|
@@ -168,6 +171,8 @@ const flowBranchNames = {
|
|
|
168
171
|
bpm: ['always', 'success', 'fail'],
|
|
169
172
|
permission: ['always'],
|
|
170
173
|
'open-settings': ['always'],
|
|
174
|
+
'ad-show': ['always', 'error'],
|
|
175
|
+
back: ['always'],
|
|
171
176
|
end: []
|
|
172
177
|
};
|
|
173
178
|
/**
|
|
@@ -180,7 +185,7 @@ export const knownFlowStepTypes = new Set([
|
|
|
180
185
|
'start', 'end', 'query', 'setter', 'download', 'condition', 'redirect', 'setcookie', 'alert', 'confirm',
|
|
181
186
|
'microservice', 'validation', 'flow-trigger', 'storage', 'navigate', 'code', 'function', 'share',
|
|
182
187
|
'haptic', 'location', 'notification-permission', 'notification-schedule', 'notification-cancel', 'bpm',
|
|
183
|
-
'permission', 'open-settings'
|
|
188
|
+
'permission', 'open-settings', 'ad-show', 'back'
|
|
184
189
|
]);
|
|
185
190
|
/**
|
|
186
191
|
* Cihaz yeteneği adımlarının kapalı kümeleri — `@ng-apps/flow` `tasks/device/device.functions.ts`
|
|
@@ -189,8 +194,19 @@ export const knownFlowStepTypes = new Set([
|
|
|
189
194
|
*/
|
|
190
195
|
export const hapticStyles = new Set(['success', 'warning', 'error', 'light', 'medium', 'heavy', 'selection']);
|
|
191
196
|
export const locationAccuracies = new Set(['low', 'balanced', 'high']);
|
|
197
|
+
/**
|
|
198
|
+
* Reklam yerleşiminin adı — backend `MobileStoreSettingsValidator.AdUnitNamePattern`'ın aynası (kök ADR 0012); `ad-show.value`
|
|
199
|
+
* ve `adBanner.unit` bu adla mağaza ayarındaki yerleşimi anar.
|
|
200
|
+
*/
|
|
201
|
+
export const adPlacementNamePattern = /^[a-z][A-Za-z0-9]{0,39}$/;
|
|
192
202
|
/** `permission` adımının kipleri — `@ng-apps/flow` `PERMISSION_MODES`'un aynası (kök ADR 0010); tür adını gateway bilmez, backend doğrular. */
|
|
193
203
|
export const permissionStepModes = new Set(['request', 'check']);
|
|
204
|
+
/**
|
|
205
|
+
* `validation` adımının kural tipleri — `@ng-apps/flow` `VALIDATION_RULE_TYPES`'ın aynası (`test/flow-step-parity.test.ts`
|
|
206
|
+
* bağlar). Backend kümeyi doğrulamaz (kök ADR 0014); tanınmayan tip runtime'da `validate()` default dalında GEÇER sayılır.
|
|
207
|
+
*/
|
|
208
|
+
export const validationRuleTypes = new Set(['required', 'email', 'regex', 'min-length', 'max-length', 'min-value', 'max-value']);
|
|
209
|
+
const validationParameterlessTypes = new Set(['required', 'email']);
|
|
194
210
|
const flowFreeBranchTypes = new Set(['condition']);
|
|
195
211
|
/** `condition.types.ts` ConditionArgumentRuleType — motorun tanıdığı üçlü. */
|
|
196
212
|
const conditionRuleTypes = new Set(['if', 'else-if', 'else']);
|
|
@@ -198,16 +214,22 @@ const conditionRuleTypes = new Set(['if', 'else-if', 'else']);
|
|
|
198
214
|
// backend'de bir Guid'dir (StepManagementSchema.cs `public Guid Id { get; set; }`), yani
|
|
199
215
|
// okunabilir bir ad iki uçta birden tutmaz: istek System.Guid çevrimiyle reddedilir.
|
|
200
216
|
const conditionRuleIdPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
201
|
-
//
|
|
202
|
-
//
|
|
203
|
-
|
|
217
|
+
// Metin şablonu alanları (kök ADR 0003 §Metin şablonu, K4 2026-10-08): iki barındırıcı (web alert.ts/confirm.ts,
|
|
218
|
+
// cihaz runtime-tasks.ts + device.functions.ts) metni @ng-apps/flow `resolveStepText` → `runPropertyCode`'dan
|
|
219
|
+
// geçirir — yalnız `{{ }}` içi çözülür, dışındaki çıplak ifade ekranda harfi harfine görünür.
|
|
220
|
+
const flowTemplateTextFields = new Map([
|
|
204
221
|
['alert', ['message', 'description']],
|
|
205
222
|
['confirm', ['message', 'title', 'confirmText', 'cancelText']],
|
|
206
|
-
['
|
|
207
|
-
['notification-schedule', ['name', 'title', 'path']],
|
|
223
|
+
['notification-schedule', ['name', 'title']],
|
|
208
224
|
['notification-cancel', ['name']]
|
|
209
225
|
]);
|
|
226
|
+
// Düz metin alanları: ne ifade ne şablon çözülür (`share.title` resolveShareContent'te, bildirim `path`'i K4 kapsamı dışında).
|
|
227
|
+
const flowLiteralTextFields = new Map([
|
|
228
|
+
['share', ['title']],
|
|
229
|
+
['notification-schedule', ['path']]
|
|
230
|
+
]);
|
|
210
231
|
const flowExpressionLooking = /\$\{|\b(?:Steps|Page|Row|Widget|LocalStorage|state|self|item|event|error|cookie)\s*(?:\?\.|\.|\[)/;
|
|
232
|
+
const flowTemplateSegments = /\{\{[\s\S]*?\}\}/g;
|
|
211
233
|
// Barındırıcıya özel tipler BURADA kural üretmez. `navigate`i gateway normalizasyonu zaten
|
|
212
234
|
// `redirect`e çeviriyor (normalize.ts `normalizeFlowHostGaps`) — `redirect` iki barındırıcıda da
|
|
213
235
|
// koşar, yani vaka uyarılmaz, ORTADAN KALKAR. Gateway uygulamanın tipini platform profilinden bilir
|
|
@@ -1837,7 +1859,7 @@ export const policyRules = [
|
|
|
1837
1859
|
id: 'ENT-006',
|
|
1838
1860
|
module: 'entity',
|
|
1839
1861
|
appliesTo: ['create', 'update'],
|
|
1840
|
-
sourceRef: 'sms oturumu: kullanıcının intprimarykey/guidprimarykey referans tabloları — PK identity + properties.generationType:0 (Always) ile otomatik üretilir; dbType↔commonType eşleşmesi zorunlu',
|
|
1862
|
+
sourceRef: 'sms oturumu: kullanıcının intprimarykey/guidprimarykey referans tabloları — PK identity + properties.generationType:0 (Always) ile otomatik üretilir; dbType↔commonType eşleşmesi zorunlu. 38 S-1: PostgreSqlEntityConnector.ValidateEntityAsync identity alanında GenerationType 0/1 değilse InvalidGenerationTypeException (400) atar — create ve update',
|
|
1841
1863
|
check: definition => {
|
|
1842
1864
|
const primaryKeys = asArray(definition.fields)
|
|
1843
1865
|
.filter((field) => Boolean(field) && typeof field === 'object')
|
|
@@ -1847,7 +1869,15 @@ export const policyRules = [
|
|
|
1847
1869
|
const properties = (key.properties ?? {});
|
|
1848
1870
|
const dbType = String(key.dbType ?? '');
|
|
1849
1871
|
const expected = dbType === 'UUID' ? 'guid' : ['INT2', 'INT4', 'INT8'].includes(dbType) ? 'number' : null;
|
|
1850
|
-
if (key.identity
|
|
1872
|
+
if (key.identity === true && properties.generationType !== 0 && properties.generationType !== 1) {
|
|
1873
|
+
findings.push({
|
|
1874
|
+
ruleId: 'ENT-006',
|
|
1875
|
+
severity: 'block',
|
|
1876
|
+
message: `'${key.slug ?? key.name}' primary key'i identity ama properties.generationType ${properties.generationType === undefined ? 'yok' : `'${String(properties.generationType)}'`} — platform 400 "Please select a valid generation type for '${key.slug ?? key.name}'" döner. Alana properties: { generationType: 0 } (Always) ya da 1 (ByDefault) ekleyin.`,
|
|
1877
|
+
guide: 'ngapps://guides/entity'
|
|
1878
|
+
});
|
|
1879
|
+
}
|
|
1880
|
+
else if (key.identity !== true) {
|
|
1851
1881
|
findings.push({
|
|
1852
1882
|
ruleId: 'ENT-006',
|
|
1853
1883
|
severity: 'warn',
|
|
@@ -2501,7 +2531,7 @@ export const policyRules = [
|
|
|
2501
2531
|
: [{
|
|
2502
2532
|
ruleId: 'FLOW-021',
|
|
2503
2533
|
severity: 'warn',
|
|
2504
|
-
message: "errorSteps yalnız 'start' adımı taşıyor — motor bunu hata yolu SAYMAZ (flow-engine.ts hasErrorFlow: start dışında en az bir adım arar). Hata yolu kurulmuş görünür ama uç 4xx dönünce onError false döner, akış 'always' dalından hata gövdesiyle devam eder ve kullanıcıya hiçbir mesaj ulaşmaz. start'ın ardına en az bir adım bağlayın:
|
|
2534
|
+
message: "errorSteps yalnız 'start' adımı taşıyor — motor bunu hata yolu SAYMAZ (flow-engine.ts hasErrorFlow: start dışında en az bir adım arar). Hata yolu kurulmuş görünür ama uç 4xx dönünce onError false döner, akış 'always' dalından hata gövdesiyle devam eder ve kullanıcıya hiçbir mesaj ulaşmaz. start'ın ardına en az bir adım bağlayın: alert yeter — 'message' alanı metin şablonudur (FLOW-022), ucun kendi mesajı '{{ Steps.error.message }}' ile gösterilir; {{ }} dışında yazılan ifadeyi kullanıcı harfi harfine görür. Mesajı bir bileşene yazacaksan setter (value.type:'code' → Steps.error.message).",
|
|
2505
2535
|
guide: 'ngapps://guides/page'
|
|
2506
2536
|
}];
|
|
2507
2537
|
}
|
|
@@ -2510,26 +2540,32 @@ export const policyRules = [
|
|
|
2510
2540
|
id: 'FLOW-022',
|
|
2511
2541
|
module: 'flow',
|
|
2512
2542
|
appliesTo: ['create', 'update'],
|
|
2513
|
-
sourceRef: "
|
|
2543
|
+
sourceRef: "Metin şablonu (kök docs/adr/0003-cihaz-yetenegi-adimlari.md §Metin şablonu, plans/mobil-acik-isler K4, 2026-10-08): alert.ts/confirm.ts (web) ve ui-native runtime-tasks.ts + @ng-apps/flow device.functions.ts (cihaz, önizleme) `alert.message/description`, `confirm.message/title/confirmText/cancelText`, `notification-schedule.name/title`, `notification-cancel.name`'i @ng-apps/flow `resolveStepText` → `runPropertyCode`'dan geçirir: yalnız `{{ }}` içi çözülür (çeviri `t('anahtar')`, `state`, `Steps`, `item`), dışındaki çıplak ifade ekranda harfi harfine görünür (2026-09-06·2 koşusu S-13'ün ikinci turu; 38 S-12, 37 S-11). Düz metin kalanlar: share `title` (resolveShareContent olduğu gibi verir; `message` ve `path` ifadedir) ve notification-schedule `path` (K4 kapsamı dışı) — orada `{{ }}` de çözülmez",
|
|
2514
2544
|
check: definition => flowAllSteps(definition)
|
|
2515
2545
|
.flatMap((step) => {
|
|
2516
2546
|
const type = String(step.type ?? '');
|
|
2517
|
-
const fields = flowLiteralTextFields.get(type);
|
|
2518
|
-
if (!fields)
|
|
2519
|
-
return [];
|
|
2520
2547
|
const args = (step.args ?? {});
|
|
2521
|
-
|
|
2522
|
-
|
|
2548
|
+
const label = flowStepLabel(step);
|
|
2549
|
+
const text = (field) => (typeof args[field] === 'string' ? args[field] : '');
|
|
2550
|
+
const templateFindings = (flowTemplateTextFields.get(type) ?? [])
|
|
2551
|
+
.filter(field => flowExpressionLooking.test(text(field).replace(flowTemplateSegments, '')))
|
|
2552
|
+
.map(field => ({
|
|
2553
|
+
ruleId: 'FLOW-022',
|
|
2554
|
+
severity: 'warn',
|
|
2555
|
+
message: `'${label}' ${type} adımının '${field}' alanı {{ }} dışında ifade içeriyor: bu alan METİN ŞABLONUDUR (kök ADR 0003 §Metin şablonu) — yalnız {{ }} içi çözülür, dışındaki ifade kullanıcıya harfi harfine görünür. İfadeyi sarın: '{{ Steps.error.message }}', 'ilac-{{ item.id }}', '{{ t('anahtar') }}' (çeviri).`,
|
|
2556
|
+
guide: 'ngapps://guides/page'
|
|
2557
|
+
}));
|
|
2558
|
+
const literalFindings = (flowLiteralTextFields.get(type) ?? [])
|
|
2559
|
+
.filter(field => flowExpressionLooking.test(text(field)) || text(field).includes('{{'))
|
|
2523
2560
|
.map(field => ({
|
|
2524
2561
|
ruleId: 'FLOW-022',
|
|
2525
2562
|
severity: 'warn',
|
|
2526
|
-
message: type
|
|
2527
|
-
? `'${
|
|
2528
|
-
: type
|
|
2529
|
-
? `'${flowStepLabel(step)}' share adımının '${field}' alanı ifade içeriyor ama bu alan DÜZ METİNDİR: başlık paylaşım penceresine olduğu gibi gider (iOS'ta e-posta konusu, Android'de seçici başlığı), ifade değerlendirilmez. Değişen içeriği ifade olan 'message' alanına yazın — ör. message: "'Şuna bak: ' + Page.properties.name"; bağlantı 'path' alanındadır.`
|
|
2530
|
-
: `'${flowStepLabel(step)}' ${type} adımının '${field}' alanı ifade içeriyor ama bu alan DÜZ METİNDİR: ${type}.ts task.argument.${field}'i doğrudan ekrana geçirir ve args hiçbir yerde değerlendirilmez (Runtime.svelte \`argument: step.args\`). Kullanıcı ifadenin sonucunu değil ifadenin KENDİSİNİ görür. Ucun mesajını göstermek için bir setter (ya da code) adımı kullanın — value { type: 'code', ref: 'Steps.error?.message' }; hata metninin nerede durduğu page.md §hata yolu'nda.`,
|
|
2563
|
+
message: type === 'share'
|
|
2564
|
+
? `'${label}' share adımının '${field}' alanı ifade ya da {{ }} içeriyor ama bu alan DÜZ METİNDİR: başlık paylaşım penceresine olduğu gibi gider (iOS'ta e-posta konusu, Android'de seçici başlığı), ne ifade ne şablon çözülür. Değişen içeriği ifade olan 'message' alanına yazın — ör. message: "'Şuna bak: ' + Page.properties.name"; bağlantı 'path' alanındadır.`
|
|
2565
|
+
: `'${label}' ${type} adımının '${field}' alanı ifade ya da {{ }} içeriyor ama bu alan DÜZ METİNDİR (kök ADR 0003): açılacak sayfanın slug'ı olduğu gibi kullanılır, ne ifade ne şablon çözülür. Değişen kısmı ifade olan 'query' (sayfanın sorgusu) alanına yazın.`,
|
|
2531
2566
|
guide: 'ngapps://guides/page'
|
|
2532
2567
|
}));
|
|
2568
|
+
return [...templateFindings, ...literalFindings];
|
|
2533
2569
|
})
|
|
2534
2570
|
},
|
|
2535
2571
|
{
|
|
@@ -2626,6 +2662,123 @@ export const policyRules = [
|
|
|
2626
2662
|
return findings;
|
|
2627
2663
|
})
|
|
2628
2664
|
},
|
|
2665
|
+
{
|
|
2666
|
+
id: 'FLOW-039',
|
|
2667
|
+
module: 'flow',
|
|
2668
|
+
appliesTo: ['create', 'update'],
|
|
2669
|
+
sourceRef: "Tam ekran reklam adımının argümanı StepManagementArgument.value'dur (kök docs/adr/0012-reklam-yerlesimleri-magaza-ayari-ve-kosullu-sdk.md): mağaza ayarındaki reklam yerleşiminin ADI, DÜZ METİN — AdMob birim kimliği değil (kimlik derlemenin platformuna göre manifestten çözülür). Backend FlowDtoValidator HasAdPlacement adı ^[a-z][A-Za-z0-9]{0,39}$ deseniyle denetler, uymayan 422 alır; unit/placement/adUnitId gibi anahtarlar sözleşmede YOKTUR ve 200 ile sessizce düşer. Yerleşimin mağaza ayarında tanımlı olduğu burada denetlenemez: mobile_store_get",
|
|
2670
|
+
check: definition => flowAllSteps(definition)
|
|
2671
|
+
.filter(step => step.type === 'ad-show')
|
|
2672
|
+
.flatMap((step) => {
|
|
2673
|
+
const args = (step.args ?? {});
|
|
2674
|
+
const unit = typeof args.value === 'string' ? args.value : '';
|
|
2675
|
+
const unquoted = unit.replace(/^['"`](.*)['"`]$/, '$1');
|
|
2676
|
+
const stray = ['unit', 'placement', 'adUnitId', 'unitId', 'format'].filter(key => args[key] !== undefined && args[key] !== null && args[key] !== '');
|
|
2677
|
+
const findings = [];
|
|
2678
|
+
if (!unit.trim()) {
|
|
2679
|
+
findings.push({
|
|
2680
|
+
ruleId: 'FLOW-039',
|
|
2681
|
+
severity: 'block',
|
|
2682
|
+
message: `'${flowStepLabel(step)}' ad-show adımının yerleşimi yok: args.value mağaza ayarındaki tam ekran yerleşimin ADINI ister (platform 422 döner). Yerleşimler: mobile_store_get — ads.units[] (format interstitial, rewarded ya da rewardedInterstitial).`,
|
|
2683
|
+
guide: 'ngapps://guides/mobile'
|
|
2684
|
+
});
|
|
2685
|
+
}
|
|
2686
|
+
else if (unquoted !== unit) {
|
|
2687
|
+
findings.push({
|
|
2688
|
+
ruleId: 'FLOW-039',
|
|
2689
|
+
severity: 'block',
|
|
2690
|
+
message: `'${flowStepLabel(step)}' ad-show adımının yerleşimi tırnaklı yazılmış: ${unit}. Bu alan DÜZ METİNDİR, ifade değil — args.value: '${unquoted}' yazın.`,
|
|
2691
|
+
guide: 'ngapps://guides/mobile'
|
|
2692
|
+
});
|
|
2693
|
+
}
|
|
2694
|
+
else if (!adPlacementNamePattern.test(unit)) {
|
|
2695
|
+
findings.push({
|
|
2696
|
+
ruleId: 'FLOW-039',
|
|
2697
|
+
severity: 'block',
|
|
2698
|
+
message: `'${flowStepLabel(step)}' ad-show adımının yerleşimi bir yerleşim adı değil: '${unit}'. Değer ifade ya da AdMob birim kimliği olamaz; mağaza ayarındaki yerleşimin adıdır (${adPlacementNamePattern.source}). Platform 422 döner. Yerleşimler: mobile_store_get — ads.units[].name.`,
|
|
2699
|
+
guide: 'ngapps://guides/mobile'
|
|
2700
|
+
});
|
|
2701
|
+
}
|
|
2702
|
+
if (stray.length > 0) {
|
|
2703
|
+
findings.push({
|
|
2704
|
+
ruleId: 'FLOW-039',
|
|
2705
|
+
severity: unit.trim() ? 'warn' : 'block',
|
|
2706
|
+
message: `'${flowStepLabel(step)}' ad-show adımında args.${stray.join('/args.')} alanı var: bu anahtar StepManagementArgument sözleşmesinde YOK, platform onu 200 ile sessizce düşürür. Yerleşimin adı args.value'ya düz metin olarak yazılır — ör. args: { value: '<yerleşim adı>' }.`,
|
|
2707
|
+
guide: 'ngapps://guides/mobile'
|
|
2708
|
+
});
|
|
2709
|
+
}
|
|
2710
|
+
return findings;
|
|
2711
|
+
})
|
|
2712
|
+
},
|
|
2713
|
+
{
|
|
2714
|
+
id: 'FLOW-041',
|
|
2715
|
+
module: 'flow',
|
|
2716
|
+
appliesTo: ['create', 'update'],
|
|
2717
|
+
sourceRef: "validation adımının kuralları StepManagementArgument.validations listesindedir (kök docs/adr/0014-validation-kurallari-cocuk-tabloda.md §5, 37 S-13): { id?, component (tasarım bileşeninin elementId'si; boş = null, sayfada olmayan 404), property, type, errorMessage (düz metin ya da t: anahtarı), settings (tek parametre, runtime Object.values(settings)[0]'ı okur) }. Backend type kümesini doğrulamaz; @ng-apps/flow validation.ts validate() tanımadığı tipte true döner, yani yazım hatalı kural sessizce GEÇER. Liste boşsa her çalıştırma success dalına gider. Komponent doğrulamasının { message, value } şekli bu sözleşmede YOKTUR ve 200 ile sessizce düşer",
|
|
2718
|
+
check: definition => flowAllSteps(definition)
|
|
2719
|
+
.filter(step => step.type === 'validation')
|
|
2720
|
+
.flatMap((step) => {
|
|
2721
|
+
const label = flowStepLabel(step);
|
|
2722
|
+
const validations = (step.args ?? {}).validations;
|
|
2723
|
+
if (validations !== undefined && validations !== null && !Array.isArray(validations)) {
|
|
2724
|
+
return [{
|
|
2725
|
+
ruleId: 'FLOW-041',
|
|
2726
|
+
severity: 'block',
|
|
2727
|
+
message: `'${label}' validation adımının args.validations alanı bir DİZİ olmalı: [{ component, property, type, errorMessage, settings }] (kök ADR 0014). Ör. [{ component: 'email', property: 'value', type: 'email', errorMessage: 't:validation.email', settings: {} }].`,
|
|
2728
|
+
guide: 'ngapps://guides/page'
|
|
2729
|
+
}];
|
|
2730
|
+
}
|
|
2731
|
+
const rules = (Array.isArray(validations) ? validations : []);
|
|
2732
|
+
if (rules.length === 0) {
|
|
2733
|
+
return [{
|
|
2734
|
+
ruleId: 'FLOW-041',
|
|
2735
|
+
severity: 'warn',
|
|
2736
|
+
message: `'${label}' validation adımında kural yok: args.validations boş olduğu için adım her çalıştırmada 'success' dalına gider, hiçbir alan denetlenmez. Kuralları args.validations'a yazın (kök ADR 0014).`,
|
|
2737
|
+
guide: 'ngapps://guides/page'
|
|
2738
|
+
}];
|
|
2739
|
+
}
|
|
2740
|
+
return rules.flatMap((rule, index) => {
|
|
2741
|
+
const type = typeof rule?.type === 'string' ? rule.type : '';
|
|
2742
|
+
const settings = rule?.settings && typeof rule.settings === 'object' ? Object.values(rule.settings) : [];
|
|
2743
|
+
const stray = ['message', 'value', 'name', 'isActive'].filter(key => rule?.[key] !== undefined);
|
|
2744
|
+
const at = `args.validations[${index}]`;
|
|
2745
|
+
const findings = [];
|
|
2746
|
+
if (!validationRuleTypes.has(type)) {
|
|
2747
|
+
findings.push({
|
|
2748
|
+
ruleId: 'FLOW-041',
|
|
2749
|
+
severity: 'block',
|
|
2750
|
+
message: `'${label}' validation adımında ${at}.type '${type}' tanınmıyor — beklenen: '${[...validationRuleTypes].join("' | '")}'. Platform tipi doğrulamadan kaydeder ve runtime tanımadığı tipi GEÇTİ sayar: kural hiçbir şeyi denetlemez.`,
|
|
2751
|
+
guide: 'ngapps://guides/page'
|
|
2752
|
+
});
|
|
2753
|
+
}
|
|
2754
|
+
else if (!validationParameterlessTypes.has(type) && (settings.length === 0 || settings[0] === null || settings[0] === '')) {
|
|
2755
|
+
findings.push({
|
|
2756
|
+
ruleId: 'FLOW-041',
|
|
2757
|
+
severity: 'block',
|
|
2758
|
+
message: `'${label}' validation adımında ${at} ('${type}') parametresiz: runtime eşiği settings'in İLK değerinden okur (${type === 'regex' ? "{ pattern: '^\\\\d+$' }" : '{ value: 8 }'}); değer yoksa karşılaştırma undefined ile yapılır ve kural her çalıştırmada düşer.`,
|
|
2759
|
+
guide: 'ngapps://guides/page'
|
|
2760
|
+
});
|
|
2761
|
+
}
|
|
2762
|
+
if (typeof rule?.property !== 'string' || rule.property.trim() === '') {
|
|
2763
|
+
findings.push({
|
|
2764
|
+
ruleId: 'FLOW-041',
|
|
2765
|
+
severity: 'block',
|
|
2766
|
+
message: `'${label}' validation adımında ${at}.property yok: runtime bileşenin hangi prop'unu okuyacağını bu alandan bilir (çoğunlukla 'value'). Yoksa okunan değer undefined olur.`,
|
|
2767
|
+
guide: 'ngapps://guides/page'
|
|
2768
|
+
});
|
|
2769
|
+
}
|
|
2770
|
+
if (stray.length > 0) {
|
|
2771
|
+
findings.push({
|
|
2772
|
+
ruleId: 'FLOW-041',
|
|
2773
|
+
severity: 'warn',
|
|
2774
|
+
message: `'${label}' validation adımında ${at}.${stray.join('/')} alanı var: bu komponent doğrulamasının şeklidir, adım kuralının sözleşmesinde YOK ve platform onu 200 ile sessizce düşürür. Mesaj errorMessage'a, eşik settings'e yazılır — ör. { component: 'sifre', property: 'value', type: 'min-length', errorMessage: 'En az 8 karakter', settings: { value: 8 } }.`,
|
|
2775
|
+
guide: 'ngapps://guides/page'
|
|
2776
|
+
});
|
|
2777
|
+
}
|
|
2778
|
+
return findings;
|
|
2779
|
+
});
|
|
2780
|
+
})
|
|
2781
|
+
},
|
|
2629
2782
|
{
|
|
2630
2783
|
id: 'FLOW-038',
|
|
2631
2784
|
module: 'flow',
|
|
@@ -2687,7 +2840,7 @@ export const policyRules = [
|
|
|
2687
2840
|
id: 'FLOW-030',
|
|
2688
2841
|
module: 'flow',
|
|
2689
2842
|
appliesTo: ['create', 'update'],
|
|
2690
|
-
sourceRef: "Yerel bildirim adımlarının argümanı StepManagementArgument'ın mevcut alanlarındadır (kök docs/adr/0003-cihaz-yetenegi-adimlari.md): notification-schedule name = kimlik (
|
|
2843
|
+
sourceRef: "Yerel bildirim adımlarının argümanı StepManagementArgument'ın mevcut alanlarındadır (kök docs/adr/0003-cihaz-yetenegi-adimlari.md): notification-schedule name = kimlik ve title = başlık METİN ŞABLONU (yalnız {{ }} içi çözülür — 2026-10-08, FLOW-022; aynı kimlik eskisinin yerini alır), message ve value İFADE, path sayfa (düz metin); notification-cancel name = kimlik, metin şablonu ('*' hepsi). Zamanın türü value'nun sonucunun şeklinden okunur (@ng-apps/flow notificationTrigger): 'HH:mm' her gün, { weekday: 1-7, time: 'HH:mm' } her hafta (1 = Pazartesi), ISO tarih-saat bir kez. Eksik name/value backend FlowDtoValidator'da 400 alır; id/at/daily/weekly/body gibi anahtarlar sözleşmede YOKTUR ve platform onları 200 ile sessizce düşürür. GUID değerli args.id platformun kendi StepArgument kimliğidir (okunan akıştan geri yazım) ve işaretlenmez — 37 S-21",
|
|
2691
2844
|
check: definition => flowAllSteps(definition)
|
|
2692
2845
|
.filter(step => step.type === 'notification-schedule' || step.type === 'notification-cancel')
|
|
2693
2846
|
.flatMap((step) => {
|
|
@@ -2695,13 +2848,16 @@ export const policyRules = [
|
|
|
2695
2848
|
const text = (key) => typeof args[key] === 'string' ? String(args[key]).trim() : '';
|
|
2696
2849
|
const findings = [];
|
|
2697
2850
|
const schedule = step.type === 'notification-schedule';
|
|
2851
|
+
// 37 S-21: StepArgument bir AggregateRoot<Guid>'dir; okunan akış args.id'yi GUID olarak
|
|
2852
|
+
// taşır ve geri yazımda o platformun kimliğidir. Yalnız GUID olmayan id ajanın uydurmasıdır.
|
|
2698
2853
|
const stray = ['id', 'identifier', 'at', 'daily', 'weekly', 'time', 'body', 'text']
|
|
2699
|
-
.filter(key => args[key] !== undefined && args[key] !== null && args[key] !== '')
|
|
2854
|
+
.filter(key => args[key] !== undefined && args[key] !== null && args[key] !== '')
|
|
2855
|
+
.filter(key => key !== 'id' || !isGuid(String(args.id)));
|
|
2700
2856
|
if (!text('name')) {
|
|
2701
2857
|
findings.push({
|
|
2702
2858
|
ruleId: 'FLOW-030',
|
|
2703
2859
|
severity: 'block',
|
|
2704
|
-
message: `'${flowStepLabel(step)}' ${step.type} adımının kimliği yok: args.name
|
|
2860
|
+
message: `'${flowStepLabel(step)}' ${step.type} adımının kimliği yok: args.name metin olarak yazılmalı (ör. 'sulama'; kayıt başına 'ilac-{{ item.id }}')${schedule ? ' — aynı kimlikle yeniden planlama eskisinin yerini alır' : " — '*' bütün planlı bildirimleri siler"}. Platform eksik kimliği 400 ile reddeder.`,
|
|
2705
2861
|
guide: 'ngapps://guides/mobile'
|
|
2706
2862
|
});
|
|
2707
2863
|
}
|
|
@@ -3086,6 +3242,29 @@ export const policyRules = [
|
|
|
3086
3242
|
}];
|
|
3087
3243
|
})
|
|
3088
3244
|
},
|
|
3245
|
+
{
|
|
3246
|
+
id: 'FLOW-027',
|
|
3247
|
+
module: 'flow',
|
|
3248
|
+
appliesTo: ['create', 'update'],
|
|
3249
|
+
sourceRef: "2026-10-01 kanban ölçüm turu: steps ve errorSteps'te AYNI stepId ('start') flow_create'te 500 `An item with the same key has already been added. Key: start [ArgumentException]` verdi — platform adımları tek sözlüğe stepId anahtarıyla koyuyor, hata mesajı çakışmanın stepId olduğunu söylemiyor",
|
|
3250
|
+
check: definition => {
|
|
3251
|
+
const counts = new Map();
|
|
3252
|
+
for (const step of flowAllSteps(definition)) {
|
|
3253
|
+
if (!step.stepId)
|
|
3254
|
+
continue;
|
|
3255
|
+
counts.set(step.stepId, (counts.get(step.stepId) ?? 0) + 1);
|
|
3256
|
+
}
|
|
3257
|
+
const repeated = [...counts].filter(([, count]) => count > 1).map(([stepId]) => stepId);
|
|
3258
|
+
return repeated.length === 0
|
|
3259
|
+
? []
|
|
3260
|
+
: [{
|
|
3261
|
+
ruleId: 'FLOW-027',
|
|
3262
|
+
severity: 'block',
|
|
3263
|
+
message: `stepId'ler akışta (steps + errorSteps birlikte) benzersiz olmalı; tekrar edenler: ${repeated.map(id => `'${id}'`).join(', ')}. Platform bunları tek sözlüğe anahtar olarak koyar ve "An item with the same key has already been added" ile 500 verir (mesaj stepId'den söz etmez). errorSteps'te de kendi 'start' adımı gerekiyorsa ona farklı bir stepId verin (örn. 'err_start').`,
|
|
3264
|
+
guide: 'ngapps://guides/page'
|
|
3265
|
+
}];
|
|
3266
|
+
}
|
|
3267
|
+
},
|
|
3089
3268
|
{
|
|
3090
3269
|
id: 'MS-013',
|
|
3091
3270
|
module: 'microservice',
|
|
@@ -3185,6 +3364,46 @@ export const policyRules = [
|
|
|
3185
3364
|
return findings;
|
|
3186
3365
|
}
|
|
3187
3366
|
},
|
|
3367
|
+
/**
|
|
3368
|
+
* WF-035 — 38 S-10: `query.fail` gateway'in `gatewayOnError` adımına bağlanınca bozuk id'li
|
|
3369
|
+
* istek 500 `JavaScriptException: error is not defined` döndü. Hedef adım global-error dalının
|
|
3370
|
+
* da hedefi olabilir; o yoldan gelince `error` kurulur, bağlı fail yolundan gelince kurulmaz —
|
|
3371
|
+
* bu yüzden hedefin global-error hedefi olması uyarıyı susturmaz.
|
|
3372
|
+
*/
|
|
3373
|
+
{
|
|
3374
|
+
id: 'WF-035',
|
|
3375
|
+
module: 'workflow',
|
|
3376
|
+
appliesTo: ['create', 'update', 'preview'],
|
|
3377
|
+
sourceRef: "IStep.cs FailedOutput: global-error dalına yalnız fail dalı BAĞLANMAMIŞSA geçilir ve `error` değişkenini yalnız ActivityContext.EnterGlobalErrorHandling kurar. Bağlı fail dalında sonraki adımlar patlayan adımın hatasını kendi değişkeninden okur: ActivityContext.CreateJavascriptEngine her adımı adıyla { data, meta, error: { code, message, detail, type, cause } } olarak koyar. Ölçüm 38 S-10 (2026-09-30): query.fail → gatewayOnError, bozuk id → 500 'error is not defined'",
|
|
3378
|
+
check: (definition, context) => {
|
|
3379
|
+
const steps = workflowSteps(workflowBody(definition, context.workflowKind));
|
|
3380
|
+
const byId = new Map(steps.filter(step => step.id).map(step => [String(step.id), step]));
|
|
3381
|
+
const readsGlobalError = /(?<![\w$.])error\s*(?:\?\.|\.(?=[A-Za-z_$])|\[)/;
|
|
3382
|
+
const strings = (value) => {
|
|
3383
|
+
if (typeof value === 'string')
|
|
3384
|
+
return [value];
|
|
3385
|
+
if (Array.isArray(value))
|
|
3386
|
+
return value.flatMap(strings);
|
|
3387
|
+
if (value && typeof value === 'object')
|
|
3388
|
+
return Object.values(value).flatMap(strings);
|
|
3389
|
+
return [];
|
|
3390
|
+
};
|
|
3391
|
+
return steps.flatMap((step) => asArray(step.branches)
|
|
3392
|
+
.map(branch => branch)
|
|
3393
|
+
.filter(branch => branch?.type === 'fail' && Boolean(branch.nextStepId))
|
|
3394
|
+
.map(branch => byId.get(String(branch.nextStepId)))
|
|
3395
|
+
.filter((target) => Boolean(target) && strings(target.rule).some(text => readsGlobalError.test(text)))
|
|
3396
|
+
.map(target => {
|
|
3397
|
+
const name = stepLabel(step);
|
|
3398
|
+
return {
|
|
3399
|
+
ruleId: 'WF-035',
|
|
3400
|
+
severity: 'warn',
|
|
3401
|
+
message: `'${name}' adımının fail dalı '${stepLabel(target)}' adımına bağlı ve o adım 'error.*' okuyor. 'error' YALNIZ global-error dalında (fail dalı bağlanmamışken) kurulur; bağlı fail dalından gelindiğinde tanımsızdır ve adım 500 'JavaScriptException: error is not defined' ile düşer. Patlayan adımın hatasını kendi değişkeninden okuyun: ${name}.error.message · ${name}.error.code (ayrıca detail, type, cause; çıktı ${name}.data). Ya da fail dalını bağlamayın: hata global-error dalına düşer ve 'error' orada kurulur. Global-error hedefiyle aynı adımı paylaşmak yerine fail dalına ayrı bir response adımı bağlayın (ör. 404).`,
|
|
3402
|
+
guide: 'ngapps://guides/workflow'
|
|
3403
|
+
};
|
|
3404
|
+
}));
|
|
3405
|
+
}
|
|
3406
|
+
},
|
|
3188
3407
|
{
|
|
3189
3408
|
id: 'WF-021',
|
|
3190
3409
|
module: 'workflow',
|
|
@@ -3309,7 +3528,7 @@ export const policyRules = [
|
|
|
3309
3528
|
id: 'MW-003',
|
|
3310
3529
|
module: 'workflow',
|
|
3311
3530
|
appliesTo: ['create', 'update', 'preview'],
|
|
3312
|
-
sourceRef: "set-user değişkeni istek kapsamındaki WorkflowContext'e yazılır (SetUserStep → AddVariable('user')) ve sonraki middleware'in motoru onu görür (WorkflowRunner her koşuda
|
|
3531
|
+
sourceRef: "set-user değişkeni istek kapsamındaki WorkflowContext'e yazılır (SetUserStep → AddVariable('user')) ve sonraki middleware'in motoru onu görür (WorkflowRunner her koşuda CreateJavascriptEngine() ile motoru _variables'tan kurar; sıra ExecuteMicroserviceBehavior:96 / ExecutePageBehavior:121 → OrderBy(m => m.Name)). Ama kuran middleware set-user'a ULAŞMADAN biterse (çerez yok, koşul dalı) değişken hiç doğmaz ve okuyan middleware ham 'user is not defined' ile 500 verir — 401 değil (crm koşumu B-3)",
|
|
3313
3532
|
check: (definition, context) => {
|
|
3314
3533
|
if (context.workflowKind !== 'middleware')
|
|
3315
3534
|
return [];
|
|
@@ -164,6 +164,8 @@ export const TRANSFORM_RANGES = {
|
|
|
164
164
|
translateX: [-1000, 1000],
|
|
165
165
|
translateY: [-1000, 1000]
|
|
166
166
|
};
|
|
167
|
+
/** Frontend `TRANSFORM_FIELDS`: transform'lu ata `position: fixed` torununu hapsettiği için katmanlar ve kabuklar bu alanları `surfaceStyleGroup({ omit })` ile dışlar. */
|
|
168
|
+
const TRANSFORM_FIELDS = Object.keys(TRANSFORM_RANGES);
|
|
167
169
|
/** Sayı ya da sayısal metin, aralığın içinde; aksi `undefined` — frontend `transformValue`. */
|
|
168
170
|
export const transformValue = (value, field) => {
|
|
169
171
|
if (typeof value !== 'number' && (typeof value !== 'string' || value.trim() === ''))
|
|
@@ -434,7 +436,9 @@ const STATCARD_FLEX_ALIGN = {
|
|
|
434
436
|
left: 'flex-start',
|
|
435
437
|
center: 'center',
|
|
436
438
|
right: 'flex-end',
|
|
437
|
-
justify: 'stretch'
|
|
439
|
+
justify: 'stretch',
|
|
440
|
+
start: 'flex-start',
|
|
441
|
+
end: 'flex-end'
|
|
438
442
|
};
|
|
439
443
|
function composeStatCardParts(input, root) {
|
|
440
444
|
const value = input.valueStyle;
|
|
@@ -507,22 +511,26 @@ export const TYPED_STYLE_COMPONENTS = {
|
|
|
507
511
|
// onun altında kalıyordu (LAYOUT-006, 2026-09-29). Yüzey ve taşma köke.
|
|
508
512
|
// navigationStyle menüye gider, resolvedStyle'a girmez.
|
|
509
513
|
groups: ['surfaceStyle'],
|
|
510
|
-
ext: { overflowTarget: ROOT_TARGET }
|
|
514
|
+
ext: { overflowTarget: ROOT_TARGET },
|
|
515
|
+
omit: { surfaceStyle: TRANSFORM_FIELDS }
|
|
511
516
|
},
|
|
512
517
|
'layout-shell': {
|
|
513
518
|
// Aynı kök: zemin `.ng-layout-shell` KÖKÜNDE (SHELL-006, 2026-09-29).
|
|
514
519
|
groups: ['surfaceStyle'],
|
|
515
|
-
ext: { overflowTarget: ROOT_TARGET }
|
|
520
|
+
ext: { overflowTarget: ROOT_TARGET },
|
|
521
|
+
omit: { surfaceStyle: TRANSFORM_FIELDS }
|
|
516
522
|
},
|
|
517
523
|
'layout-region': {
|
|
518
524
|
// Zemin `.ng-layout-region` KÖKÜNDE, taşma kaydırılan gövdede (REG-002, 2026-09-29).
|
|
519
525
|
groups: ['surfaceStyle'],
|
|
520
|
-
ext: { overflowTarget: '&__body' }
|
|
526
|
+
ext: { overflowTarget: '&__body' },
|
|
527
|
+
omit: { surfaceStyle: TRANSFORM_FIELDS }
|
|
521
528
|
},
|
|
522
529
|
'auth-layout': {
|
|
523
530
|
// Zemin `.ng-auth` KÖKÜNDE (AUTH-006, 2026-09-29); yüzey ve taşma köke.
|
|
524
531
|
groups: ['surfaceStyle'],
|
|
525
|
-
ext: { overflowTarget: ROOT_TARGET }
|
|
532
|
+
ext: { overflowTarget: ROOT_TARGET },
|
|
533
|
+
omit: { surfaceStyle: TRANSFORM_FIELDS }
|
|
526
534
|
},
|
|
527
535
|
header: {
|
|
528
536
|
// Şeridin zemini ve alt çizgisi kökte (HDR-007, 2026-09-29).
|
|
@@ -535,6 +543,20 @@ export const TYPED_STYLE_COMPONENTS = {
|
|
|
535
543
|
groups: ['surfaceStyle'],
|
|
536
544
|
ext: { overflowTarget: ROOT_TARGET }
|
|
537
545
|
},
|
|
546
|
+
modal: {
|
|
547
|
+
// Panel portal ile düğüm kutusunun DIŞINA çizilir; yüzey panelin KÖKÜNDE
|
|
548
|
+
// derlenir, taşma da panelin köküne iner (modal ölçümü, 2026-10-05).
|
|
549
|
+
groups: ['surfaceStyle'],
|
|
550
|
+
ext: { overflowTarget: ROOT_TARGET },
|
|
551
|
+
omit: { surfaceStyle: TRANSFORM_FIELDS }
|
|
552
|
+
},
|
|
553
|
+
drawer: {
|
|
554
|
+
// Panel portal ile düğüm kutusunun DIŞINA çizilir; yüzey panelin KÖKÜNDE
|
|
555
|
+
// derlenir, taşma da panelin köküne iner (drawer ölçümü, 2026-10-05).
|
|
556
|
+
groups: ['surfaceStyle'],
|
|
557
|
+
ext: { overflowTarget: ROOT_TARGET },
|
|
558
|
+
omit: { surfaceStyle: TRANSFORM_FIELDS }
|
|
559
|
+
},
|
|
538
560
|
icon: {
|
|
539
561
|
// Yalnız hover: surface çipin (chipShape/chipColor), typography da boyutun
|
|
540
562
|
// (size) ve rengin (color) çift kanalı olurdu — görsel kararlar semantik
|
|
@@ -553,11 +575,8 @@ export const TYPED_STYLE_COMPONENTS = {
|
|
|
553
575
|
* eski birleşik kaskadla (tema+preset+own) çözülmeye devam eder.
|
|
554
576
|
*/
|
|
555
577
|
export const NODE_SURFACE_TYPES = new Set([
|
|
556
|
-
'table',
|
|
557
578
|
'datatable',
|
|
558
579
|
'collapsible-container',
|
|
559
|
-
'drawer',
|
|
560
|
-
'modal',
|
|
561
580
|
'popover',
|
|
562
581
|
'tabs',
|
|
563
582
|
'stepper',
|
|
@@ -456,8 +456,10 @@ const composeTypography = (typography, text, theme, context) => {
|
|
|
456
456
|
text.fontStyle = String(typography.fontStyle);
|
|
457
457
|
if (isSet(typography.textDecoration))
|
|
458
458
|
text.textDecorationLine = String(typography.textDecoration);
|
|
459
|
-
if (isSet(typography.textAlign))
|
|
460
|
-
|
|
459
|
+
if (isSet(typography.textAlign)) {
|
|
460
|
+
const align = String(typography.textAlign);
|
|
461
|
+
text.textAlign = align === 'start' ? 'left' : align === 'end' ? 'right' : align;
|
|
462
|
+
}
|
|
461
463
|
const letterSpacing = parsePanelLength(typography.letterSpacing, context);
|
|
462
464
|
if (typeof letterSpacing === 'number')
|
|
463
465
|
text.letterSpacing = letterSpacing;
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* `theme_get` bunları `unknownKeys` olarak gösterir, `theme_update` şeması reddeder.
|
|
30
30
|
*/
|
|
31
31
|
export const THEME_TOP_LEVEL_KEYS = [
|
|
32
|
-
'mode', 'palette', 'paletteDark', 'fontFamily', 'headingFontFamily', 'typeScale',
|
|
32
|
+
'mode', 'direction', 'palette', 'paletteDark', 'fontFamily', 'headingFontFamily', 'typeScale',
|
|
33
33
|
'fontSize', 'fontWeight', 'borderRadius', 'radiusScale', 'spacing', 'spacingScale',
|
|
34
34
|
'shadow', 'defaultInputSize', 'colors', 'actions', 'presets', 'componentStyles',
|
|
35
35
|
'componentPresets'
|
|
@@ -30,7 +30,7 @@ export function summarizeTheme(theme) {
|
|
|
30
30
|
if (entries.length > 0)
|
|
31
31
|
overrides[key] = entries;
|
|
32
32
|
}
|
|
33
|
-
for (const key of ['fontFamily', 'headingFontFamily', 'fontSize', 'fontWeight', 'spacing', 'borderRadius', 'defaultInputSize']) {
|
|
33
|
+
for (const key of ['fontFamily', 'headingFontFamily', 'fontSize', 'fontWeight', 'spacing', 'borderRadius', 'defaultInputSize', 'direction']) {
|
|
34
34
|
if (record[key] !== undefined)
|
|
35
35
|
overrides[key] = record[key];
|
|
36
36
|
}
|
|
@@ -91,6 +91,7 @@ const ratioScale = (levels) => z.object(Object.fromEntries(levels.map(level => [
|
|
|
91
91
|
*/
|
|
92
92
|
export const themeInput = z.object({
|
|
93
93
|
mode: z.enum(['light', 'dark']),
|
|
94
|
+
direction: z.enum(['ltr', 'rtl', 'auto']),
|
|
94
95
|
palette: paletteSchema,
|
|
95
96
|
paletteDark: paletteSchema,
|
|
96
97
|
fontFamily: z.string().min(1),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
2
|
import { appendFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
|
-
import { secretKeyPattern } from '../platform/secrets.js';
|
|
4
|
+
import { maskSecretLeaf, secretKeyPattern, secretPairName } from '../platform/secrets.js';
|
|
5
5
|
const defaultDirectory = join(process.cwd(), 'logs');
|
|
6
6
|
const activeTrace = new AsyncLocalStorage();
|
|
7
7
|
const activeRequest = new AsyncLocalStorage();
|
|
@@ -37,11 +37,16 @@ function clip(text, limit) {
|
|
|
37
37
|
function redact(value, depth = 0) {
|
|
38
38
|
if (depth > 8)
|
|
39
39
|
return '…';
|
|
40
|
+
// Günlük, yanıt maskesinin gördüğü ad/değer çiftini ve yaprak jetonu da görmeli: debug gövdesi ve
|
|
41
|
+
// `asUser` argümanı buraya ham gelir (g-01, 37 S-24).
|
|
42
|
+
if (typeof value === 'string')
|
|
43
|
+
return maskSecretLeaf(value);
|
|
40
44
|
if (Array.isArray(value))
|
|
41
45
|
return value.map(entry => redact(entry, depth + 1));
|
|
42
46
|
if (value && typeof value === 'object') {
|
|
47
|
+
const pairName = secretPairName(value);
|
|
43
48
|
return Object.fromEntries(Object.entries(value).map(([key, entry]) => {
|
|
44
|
-
if (secretKeyPattern.test(key))
|
|
49
|
+
if (secretKeyPattern.test(key === 'value' && pairName !== undefined ? pairName : key))
|
|
45
50
|
return [key, '***'];
|
|
46
51
|
if (/^stack(Trace)?$/i.test(key) && typeof entry === 'string') {
|
|
47
52
|
return [key, `${clip(entry.split(/\r?\n/)[0] ?? '', 240)} … (kalan stack kırpıldı)`];
|
|
@@ -24,7 +24,13 @@ export function actionScript(action, captureTarget = studioWebTarget) {
|
|
|
24
24
|
var count = performance.getEntriesByType('resource').length;
|
|
25
25
|
var node = document.querySelector(${selector});
|
|
26
26
|
if (!node) return { found: false, applied: false, requests: count };
|
|
27
|
-
|
|
27
|
+
// g-04 sırası: düğümün kendisi basılabilirse o → <id>__press → içteki ilk basılabilir öğe.
|
|
28
|
+
// Mobil çoklu seçim tetikleyicisi kökte role="combobox" taşır ve içindeki ilk role="button"
|
|
29
|
+
// çipin "kaldır" düğmesidir — eskiden ona basılıp seçim siliniyordu (37 S-18). Tek basışlı
|
|
30
|
+
// denetimin yüzeyi <id>__press'tir (ui-native press-role-guard), anahtarınki role="switch".
|
|
31
|
+
var press = document.querySelector(${JSON.stringify(`[data-element-id="${String(action.target ?? '')}${INTERNAL_ROLE_SEPARATOR}press"]`)});
|
|
32
|
+
var target = node.matches('button, a, [role="button"], [role="switch"], [role="combobox"]') ? node
|
|
33
|
+
: (press || node.querySelector('button, a, [role="button"], [role="switch"]') || node);
|
|
28
34
|
// S-2: sekme grubunun KÖKÜNE click, DOM sırasındaki ilk sekmeye gider ve "uygulandı" der —
|
|
29
35
|
// ajan ikinci sekmeyi hiç seçemiyordu (2026-09-16 raporu madde 6). Sekme seçmek 'tab'in işi.
|
|
30
36
|
if (target.closest && target.closest('[role="tablist"]') && !node.matches('[role="tab"]')) {
|
|
@@ -354,15 +360,32 @@ export function actionScript(action, captureTarget = studioWebTarget) {
|
|
|
354
360
|
if (!field) return finish({ found: true, applied: false, note: 'düğümde input/select/textarea yok — bu komponent native alan kullanmıyor olabilir, click ile deneyin', requests: count });
|
|
355
361
|
var proto = field instanceof HTMLSelectElement ? HTMLSelectElement.prototype
|
|
356
362
|
: (field instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype);
|
|
363
|
+
// K1 (g-04): gerçek kullanıcı alana odaklanır, yazar ve çıkar. Blur'suz yazımda odak kaybına
|
|
364
|
+
// bağlı akış (ui-native girişlerin 'changed'i) hiç koşmuyordu (37 S-19). focus() odağı alamazsa
|
|
365
|
+
// (görünmez alan, odaksız belge) olaylar elle atılır: Svelte 'blur'u, React 'focusout'u dinler.
|
|
366
|
+
var focusField = function () {
|
|
367
|
+
field.focus();
|
|
368
|
+
if (document.activeElement === field) return;
|
|
369
|
+
field.dispatchEvent(new FocusEvent('focus'));
|
|
370
|
+
field.dispatchEvent(new FocusEvent('focusin', { bubbles: true }));
|
|
371
|
+
};
|
|
372
|
+
var blurField = function () {
|
|
373
|
+
if (document.activeElement === field) { field.blur(); return; }
|
|
374
|
+
field.dispatchEvent(new FocusEvent('blur'));
|
|
375
|
+
field.dispatchEvent(new FocusEvent('focusout', { bubbles: true }));
|
|
376
|
+
};
|
|
377
|
+
focusField();
|
|
357
378
|
if (field instanceof HTMLInputElement && (field.type === 'checkbox' || field.type === 'radio')) {
|
|
358
379
|
var shouldCheck = wanted === 'true' || wanted === '1' || wanted === 'on';
|
|
359
380
|
if (field.checked !== shouldCheck) field.click();
|
|
381
|
+
blurField();
|
|
360
382
|
return finish({ found: true, applied: true, requests: count });
|
|
361
383
|
}
|
|
362
384
|
var setter = Object.getOwnPropertyDescriptor(proto, 'value').set;
|
|
363
385
|
setter.call(field, ${value});
|
|
364
386
|
field.dispatchEvent(new Event('input', { bubbles: true }));
|
|
365
387
|
field.dispatchEvent(new Event('change', { bubbles: true }));
|
|
388
|
+
blurField();
|
|
366
389
|
return finish({ found: true, applied: true, requests: count });
|
|
367
390
|
})()`;
|
|
368
391
|
}
|