@appaflytech/zeyrek-ai-mcp 0.0.40 → 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 +68 -7
- package/dist/knowledge/componentEvents.js +33 -0
- package/dist/knowledge/mobileComponentCatalog.js +5 -5
- package/dist/knowledge/normalize.js +30 -12
- package/dist/knowledge/platform/mobile.js +1 -1
- package/dist/knowledge/platform/web.js +1 -1
- package/dist/knowledge/rules.js +166 -24
- package/dist/knowledge/styleResolve.js +0 -1
- package/dist/platform/capture/actions.js +24 -1
- package/dist/platform/capture/summary.js +4 -1
- 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/entity.md +16 -0
- package/dist/resources/guides/expressions.md +7 -6
- package/dist/resources/guides/microservice.md +1 -1
- package/dist/resources/guides/middleware.md +2 -0
- package/dist/resources/guides/mobile.md +28 -19
- package/dist/resources/guides/page.md +47 -16
- package/dist/resources/guides/translation.md +41 -1
- package/dist/resources/guides/workflow.md +31 -1
- package/dist/resources/schema-fingerprint.json +2 -2
- package/dist/tools/entities.js +41 -4
- package/dist/tools/layouts.js +22 -7
- package/dist/tools/mobile.js +15 -1
- package/dist/tools/pages.js +28 -42
- 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'],
|
|
@@ -169,6 +172,7 @@ const flowBranchNames = {
|
|
|
169
172
|
permission: ['always'],
|
|
170
173
|
'open-settings': ['always'],
|
|
171
174
|
'ad-show': ['always', 'error'],
|
|
175
|
+
back: ['always'],
|
|
172
176
|
end: []
|
|
173
177
|
};
|
|
174
178
|
/**
|
|
@@ -181,7 +185,7 @@ export const knownFlowStepTypes = new Set([
|
|
|
181
185
|
'start', 'end', 'query', 'setter', 'download', 'condition', 'redirect', 'setcookie', 'alert', 'confirm',
|
|
182
186
|
'microservice', 'validation', 'flow-trigger', 'storage', 'navigate', 'code', 'function', 'share',
|
|
183
187
|
'haptic', 'location', 'notification-permission', 'notification-schedule', 'notification-cancel', 'bpm',
|
|
184
|
-
'permission', 'open-settings', 'ad-show'
|
|
188
|
+
'permission', 'open-settings', 'ad-show', 'back'
|
|
185
189
|
]);
|
|
186
190
|
/**
|
|
187
191
|
* Cihaz yeteneği adımlarının kapalı kümeleri — `@ng-apps/flow` `tasks/device/device.functions.ts`
|
|
@@ -197,6 +201,12 @@ export const locationAccuracies = new Set(['low', 'balanced', 'high']);
|
|
|
197
201
|
export const adPlacementNamePattern = /^[a-z][A-Za-z0-9]{0,39}$/;
|
|
198
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. */
|
|
199
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']);
|
|
200
210
|
const flowFreeBranchTypes = new Set(['condition']);
|
|
201
211
|
/** `condition.types.ts` ConditionArgumentRuleType — motorun tanıdığı üçlü. */
|
|
202
212
|
const conditionRuleTypes = new Set(['if', 'else-if', 'else']);
|
|
@@ -204,16 +214,22 @@ const conditionRuleTypes = new Set(['if', 'else-if', 'else']);
|
|
|
204
214
|
// backend'de bir Guid'dir (StepManagementSchema.cs `public Guid Id { get; set; }`), yani
|
|
205
215
|
// okunabilir bir ad iki uçta birden tutmaz: istek System.Guid çevrimiyle reddedilir.
|
|
206
216
|
const conditionRuleIdPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
|
|
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([
|
|
210
221
|
['alert', ['message', 'description']],
|
|
211
222
|
['confirm', ['message', 'title', 'confirmText', 'cancelText']],
|
|
212
|
-
['
|
|
213
|
-
['notification-schedule', ['name', 'title', 'path']],
|
|
223
|
+
['notification-schedule', ['name', 'title']],
|
|
214
224
|
['notification-cancel', ['name']]
|
|
215
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
|
+
]);
|
|
216
231
|
const flowExpressionLooking = /\$\{|\b(?:Steps|Page|Row|Widget|LocalStorage|state|self|item|event|error|cookie)\s*(?:\?\.|\.|\[)/;
|
|
232
|
+
const flowTemplateSegments = /\{\{[\s\S]*?\}\}/g;
|
|
217
233
|
// Barındırıcıya özel tipler BURADA kural üretmez. `navigate`i gateway normalizasyonu zaten
|
|
218
234
|
// `redirect`e çeviriyor (normalize.ts `normalizeFlowHostGaps`) — `redirect` iki barındırıcıda da
|
|
219
235
|
// koşar, yani vaka uyarılmaz, ORTADAN KALKAR. Gateway uygulamanın tipini platform profilinden bilir
|
|
@@ -1843,7 +1859,7 @@ export const policyRules = [
|
|
|
1843
1859
|
id: 'ENT-006',
|
|
1844
1860
|
module: 'entity',
|
|
1845
1861
|
appliesTo: ['create', 'update'],
|
|
1846
|
-
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',
|
|
1847
1863
|
check: definition => {
|
|
1848
1864
|
const primaryKeys = asArray(definition.fields)
|
|
1849
1865
|
.filter((field) => Boolean(field) && typeof field === 'object')
|
|
@@ -1853,7 +1869,15 @@ export const policyRules = [
|
|
|
1853
1869
|
const properties = (key.properties ?? {});
|
|
1854
1870
|
const dbType = String(key.dbType ?? '');
|
|
1855
1871
|
const expected = dbType === 'UUID' ? 'guid' : ['INT2', 'INT4', 'INT8'].includes(dbType) ? 'number' : null;
|
|
1856
|
-
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) {
|
|
1857
1881
|
findings.push({
|
|
1858
1882
|
ruleId: 'ENT-006',
|
|
1859
1883
|
severity: 'warn',
|
|
@@ -2507,7 +2531,7 @@ export const policyRules = [
|
|
|
2507
2531
|
: [{
|
|
2508
2532
|
ruleId: 'FLOW-021',
|
|
2509
2533
|
severity: 'warn',
|
|
2510
|
-
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).",
|
|
2511
2535
|
guide: 'ngapps://guides/page'
|
|
2512
2536
|
}];
|
|
2513
2537
|
}
|
|
@@ -2516,26 +2540,32 @@ export const policyRules = [
|
|
|
2516
2540
|
id: 'FLOW-022',
|
|
2517
2541
|
module: 'flow',
|
|
2518
2542
|
appliesTo: ['create', 'update'],
|
|
2519
|
-
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",
|
|
2520
2544
|
check: definition => flowAllSteps(definition)
|
|
2521
2545
|
.flatMap((step) => {
|
|
2522
2546
|
const type = String(step.type ?? '');
|
|
2523
|
-
const fields = flowLiteralTextFields.get(type);
|
|
2524
|
-
if (!fields)
|
|
2525
|
-
return [];
|
|
2526
2547
|
const args = (step.args ?? {});
|
|
2527
|
-
|
|
2528
|
-
|
|
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('{{'))
|
|
2529
2560
|
.map(field => ({
|
|
2530
2561
|
ruleId: 'FLOW-022',
|
|
2531
2562
|
severity: 'warn',
|
|
2532
|
-
message: type
|
|
2533
|
-
? `'${
|
|
2534
|
-
: type
|
|
2535
|
-
? `'${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.`
|
|
2536
|
-
: `'${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.`,
|
|
2537
2566
|
guide: 'ngapps://guides/page'
|
|
2538
2567
|
}));
|
|
2568
|
+
return [...templateFindings, ...literalFindings];
|
|
2539
2569
|
})
|
|
2540
2570
|
},
|
|
2541
2571
|
{
|
|
@@ -2680,6 +2710,75 @@ export const policyRules = [
|
|
|
2680
2710
|
return findings;
|
|
2681
2711
|
})
|
|
2682
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
|
+
},
|
|
2683
2782
|
{
|
|
2684
2783
|
id: 'FLOW-038',
|
|
2685
2784
|
module: 'flow',
|
|
@@ -2741,7 +2840,7 @@ export const policyRules = [
|
|
|
2741
2840
|
id: 'FLOW-030',
|
|
2742
2841
|
module: 'flow',
|
|
2743
2842
|
appliesTo: ['create', 'update'],
|
|
2744
|
-
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",
|
|
2745
2844
|
check: definition => flowAllSteps(definition)
|
|
2746
2845
|
.filter(step => step.type === 'notification-schedule' || step.type === 'notification-cancel')
|
|
2747
2846
|
.flatMap((step) => {
|
|
@@ -2749,13 +2848,16 @@ export const policyRules = [
|
|
|
2749
2848
|
const text = (key) => typeof args[key] === 'string' ? String(args[key]).trim() : '';
|
|
2750
2849
|
const findings = [];
|
|
2751
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.
|
|
2752
2853
|
const stray = ['id', 'identifier', 'at', 'daily', 'weekly', 'time', 'body', 'text']
|
|
2753
|
-
.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)));
|
|
2754
2856
|
if (!text('name')) {
|
|
2755
2857
|
findings.push({
|
|
2756
2858
|
ruleId: 'FLOW-030',
|
|
2757
2859
|
severity: 'block',
|
|
2758
|
-
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.`,
|
|
2759
2861
|
guide: 'ngapps://guides/mobile'
|
|
2760
2862
|
});
|
|
2761
2863
|
}
|
|
@@ -3262,6 +3364,46 @@ export const policyRules = [
|
|
|
3262
3364
|
return findings;
|
|
3263
3365
|
}
|
|
3264
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
|
+
},
|
|
3265
3407
|
{
|
|
3266
3408
|
id: 'WF-021',
|
|
3267
3409
|
module: 'workflow',
|
|
@@ -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
|
}
|
|
@@ -45,7 +45,10 @@ export function domSummaryScript(viewportHeight) {
|
|
|
45
45
|
var field = node.querySelector('input, select, textarea');
|
|
46
46
|
var chosen = node.querySelector('.selected-item');
|
|
47
47
|
var holder = field || chosen;
|
|
48
|
-
|
|
48
|
+
// 37 S-23: OTP'nin gizli girdisi type=text'tir ve kod özet satırında açık yazıyordu; sır sinyali
|
|
49
|
+
// tarayıcının otomatik tamamlama ipucudur (one-time-code · current-password · new-password).
|
|
50
|
+
var secret = !!field && (field.type === 'password' || /(^|\\s)(one-time-code|[\\w-]*-password)(\\s|$)/i.test(String(field.getAttribute('autocomplete') || '')));
|
|
51
|
+
var value = !owned(holder) ? '' : (field ? (secret ? '***' : clean(field.value)) : clean(chosen.textContent));
|
|
49
52
|
var media = node.querySelector('img, svg, canvas, video, iframe');
|
|
50
53
|
// İç içe <li> hem grubu hem alt mesajları sayıyordu: tek grup + 2 alt hata 3 görünüyordu (B-7).
|
|
51
54
|
var errorItems = Array.prototype.slice.call(node.querySelectorAll('.form-validation-errors li:not(:has(li))'))
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"catalog": "app-bar, slot ve drawer mobil kataloğun Dalga 1 bileşenleridir. component_list mobil uygulamada bunları döndürmüyorsa katalog henüz yazılmamıştır ve layout_create 422 unknown-type alır (MOB-003).",
|
|
13
13
|
"verify": "Kabuğu layout'a bağlı bir sayfada page_screenshot ile doğrula (mobilde varsayılan device: 'phone').",
|
|
14
14
|
"menuTarget": "app-bar.menuTarget çekmecenin elementId'sidir (nav_drawer); iç rol app_bar__menu düğmesidir — page_screenshot actions:[{ action:'click', target:'app_bar__menu' }] çekmeceyi açar.",
|
|
15
|
+
"drawerButtons": "Çekmecedeki düğmeler ŞABLONDUR: mobile_app_create her sayfa için bir düğme üretir — elementId menu_<slug> (slug'daki tire ve diğer işaretler alt çizgiye döner: ana-sayfa → menu_ana_sayfa), metin pageName. Buradaki iki düğme bu örneğin kendi pages listesine (gorevler, profil) karşılıktır; başka uygulamaya kopyalanmaz (37 S-9).",
|
|
15
16
|
"drawerFlows": "Menü düğmelerine akış ayrı adımda bağlanır: flow_create (host: 'layout', adım redirect { path: '<sayfa slug'ı>' }) → layout_schema_patch set_event { target: 'menu_gorevler', event: 'click' }.",
|
|
16
17
|
"drawerNode": "Çekmece mobil kataloğun `drawer` bileşenidir (kind 'component', çocuk alır): layout'a doğrudan yazılır, prop'ları katalogdakilerdir (`position`, `size`, `title`, `isDismissDisabled`); kapalı doğar, `app-bar` menüsü `isOpen: true` yazarak açar. Katalogda olmayan prop (ör. `width`, `overlay`) ya da seçenek dışı değer (düğmede `variant: 'ghost'` — geçerli olanlar `solid`/`text`/`outline`) layout_create'i 422 ile düşürür.",
|
|
17
18
|
"layoutContract": "Mizanpaj sözleşmesi: üst güvenli alan tek yerde, kabukta (app-bar 'safe-area' edges:'top' içinde; sayfa ikinci pay vermez). Ekran kenarı boşluğu tek değer (16) ve tek yerde: slot'un yatay ve üst margin'i — içerik app-bar'ın 16 dp altından başlar, kabuksuz sayfadaki gibi. shell_root 'shrink' ve padding'siz — düz container olduğu için tip yazılmazsa runtime 10 dp padding verir ve app-bar kenardan kopar.",
|
|
@@ -85,6 +85,7 @@
|
|
|
85
85
|
"type": "view",
|
|
86
86
|
"kind": "component",
|
|
87
87
|
"props": {},
|
|
88
|
+
"blocks": [],
|
|
88
89
|
"children": [
|
|
89
90
|
{
|
|
90
91
|
"elementId": "row_card",
|
|
@@ -118,6 +119,7 @@
|
|
|
118
119
|
"type": "view",
|
|
119
120
|
"kind": "component",
|
|
120
121
|
"props": {},
|
|
122
|
+
"blocks": [],
|
|
121
123
|
"children": [
|
|
122
124
|
{
|
|
123
125
|
"elementId": "lst_tasks_bos",
|
|
@@ -130,6 +132,7 @@
|
|
|
130
132
|
"data": "Kayıt yok"
|
|
131
133
|
}
|
|
132
134
|
},
|
|
135
|
+
"blocks": [],
|
|
133
136
|
"children": []
|
|
134
137
|
}
|
|
135
138
|
]
|
|
@@ -453,6 +453,8 @@ Companion'ı adından değil **ana entity'nin `localizationEntityId` alanından*
|
|
|
453
453
|
UUIDv7) anahtar doğru kurulmuştur.
|
|
454
454
|
|
|
455
455
|
Gateway bu iki kuralı ENT-006 (PK identity/tip uyumu) ve ENT-007 (enum şekli) olarak denetler.
|
|
456
|
+
`identity: true` olup `properties.generationType` 0/1 taşımayan anahtar ENT-006 **block**'tur: platform
|
|
457
|
+
create'te de update'te de 400 "Please select a valid generation type" döner (38 S-1).
|
|
456
458
|
|
|
457
459
|
> **Çok dilli bir entity TEK adımda silinir.** `entity_delete` ana entity'ye uygulanır, companion
|
|
458
460
|
> da onunla düşer. (Aynı gün daha erken bir ölçümdeki "önce companion'ı sil" adımı artık geçersiz;
|
|
@@ -486,6 +488,20 @@ boş bırakmak geçerli bir tercihtir** (ENT-002 warn'ı bunu hatırlatır, zoru
|
|
|
486
488
|
> yolunda bu çeviriyi **platform** yapar; ham PUT atarken **siz yapmazsanız her güncelleme bağı
|
|
487
489
|
> sessizce siler.**
|
|
488
490
|
|
|
491
|
+
Var olan entity'yi `entity_patch` ile uygulamaya bağlamanın çalışan şekli — yamalanan belge
|
|
492
|
+
`entity_get`'in okuma şeklidir, yol `/applications`, öğe `{ applicationId }`:
|
|
493
|
+
|
|
494
|
+
```jsonc
|
|
495
|
+
// Bağ listesini yazar (var olan bağlar da listede olmalı — replace tam listedir)
|
|
496
|
+
{ "op": "replace", "path": "/applications", "value": [{ "applicationId": "<application id>" }] }
|
|
497
|
+
// Tek bağ ekler
|
|
498
|
+
{ "op": "add", "path": "/applications/-", "value": { "applicationId": "<application id>" } }
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
`/applicationIds` yolu belgede yoktur; çıplak guid öğesi platformda 500 `JsonException`, `{ id }`
|
|
502
|
+
öğesi boş `applicationId` ile yanıltıcı "This record is in use. Related table: 'Applications'" verir
|
|
503
|
+
(37 S-1). Gateway üçünü de yukarıdaki şekle çevirir ve `gatewayNormalized` ile söyler.
|
|
504
|
+
|
|
489
505
|
## İlişkiler (doğru şekil — 57 alanla doğrulandı)
|
|
490
506
|
|
|
491
507
|
```jsonc
|
|
@@ -98,10 +98,10 @@ dalından devam eder.
|
|
|
98
98
|
| Global | İçerik |
|
|
99
99
|
|---|---|
|
|
100
100
|
| `Page` | `Page.user`, `Page.variables`, `Page.components`, `Page.properties`, `Page.queryParams`, `Page.authorized(roles)` |
|
|
101
|
-
| **Komponent adları** | Her komponent adıyla doğrudan değişkendir: `specialties_table.rows = data`
|
|
101
|
+
| **Komponent adları** | Her komponent adıyla doğrudan değişkendir: `specialties_table.rows = data` prop'u yazar ve UI'ı günceller; `setter` adımı gibi bileşenin `change` akışını **koşturmaz** — `change` akışı yalnız kullanıcı etkileşiminde koşar (2026-10-08; öncesindeki runtime her kod yazımında `change` akışını koştururdu). Addaki `-` silinir: `tickets-table` → `ticketstable` |
|
|
102
102
|
| `state.page` / `state.app` | Sayfa/uygulama state'i (oku/yaz): `state.page.selectedId = row.id`. **Bellekte yaşar** — sayfa her açıldığında tanımlı `value`'dan başlar; mobil uygulamada kalıcı olması için değişkene `storage` verilir (`platform_guide('mobile')` §Kalıcı state). **Tanımlı değişkenler de buradadır:** `variable_create` ile yazılan her `key` sayfa düzeyinde `state.page.<key>`, uygulama düzeyinde `state.app.<key>` olur; `value` nesneyse alanlarına inilir (`state.page.MyTest.isActive`) |
|
|
103
|
-
| `Steps` | Adım çıktıları: `Steps.load_specialties` — anahtar adımın **ADIdır**, `stepId` değil (`setStorage(task.name, …)`); `Steps.<adım>.error` o adımın
|
|
104
|
-
| `self` · `item` · `Row` / `RowIndex` | Tetikleyen komponent (yazılabilir) · repeater/tablo satırı (yazmak YALNIZ o satırı günceller). Satır AKIŞINDA (satırdaki düğümün olayı) ARC-121 düzeltmesini (2026-09-27) taşıyan runtime gerekir, eskisinde boştu — sürümden bağımsız yol `<liste>.selectedItem` / `.selectedIndex`. Tekrarlayıcının KENDİ `change`/`select` akışında satır bağlamı yoktur. `item.x = …` yazımı
|
|
103
|
+
| `Steps` | Adım çıktıları: `Steps.load_specialties` — anahtar adımın **ADIdır**, `stepId` değil (`setStorage(task.name, …)`); `Steps.<adım>.error` o adımın hatası: `{ status, message, body }` — `message` ucun kendi mesajı (`err.reason`, boşsa HTTP durum metni), `status` HTTP kodu, `body` ucun hata gövdesi (`microservice.ts` `.catch`; tablo `platform_guide('page')`). `Steps.error` ise hata akışı için doldurulur (`onError` koşmadan önce `setStorage('error', …)` yapar) ve **yalnız orada okunur**: storage sayfa başına tek `FlowMachine`'de yaşar, ama dışarıdan başlayan her akış başlarken `'error'`'u ve kendi adımlarının önceki değerlerini temizler (`flow-engine.ts` `run`, 2026-09-25) — normal akışta `Steps.error` boştur; hata akışı ve onun tetiklediği akış (`nested`) onu görür ve şekli `{ message, code, step: { id, name, type, data } }`'dir: kullanıcıya gösterilecek metin `Steps.error.message`'tır (`err.response.data.message`, boşsa durum metni). **Başarılı bir adımda `Steps.<adım>.data` gerçek bir `Array`'dir** — `.map` · `.filter` · `.concat` · `.length` çalışır. Adım HATA dönerse `Steps.<adım>.data` `null`'dır, gövde `.error.body`'dedir; aşağıdaki kutuya bakın |
|
|
104
|
+
| `self` · `item` · `Row` / `RowIndex` | Tetikleyen komponent (yazılabilir) · repeater/tablo satırı (yazmak YALNIZ o satırı günceller). Satır AKIŞINDA (satırdaki düğümün olayı) ARC-121 düzeltmesini (2026-09-27) taşıyan runtime gerekir, eskisinde boştu — sürümden bağımsız yol `<liste>.selectedItem` / `.selectedIndex`. Tekrarlayıcının KENDİ `change`/`select` akışında satır bağlamı yoktur. `item.x = …` yazımı da aynı kuraldadır: o satırı günceller, tekrarlayıcının `change` akışını koşturmaz |
|
|
105
105
|
| `event` | `{name, data, source: {id, props}}` |
|
|
106
106
|
| `cookie` | `cookie.get(name)` / `cookie.set(name, value, {expiresInHours, path})` / `cookie.remove(name)` — tarayıcı cookie'si |
|
|
107
107
|
| `setComponentProperty(name, key, value)` / `getComponentProperty(name, key)` | fonksiyon alternatifi |
|
|
@@ -116,9 +116,10 @@ dalından devam eder.
|
|
|
116
116
|
> **`.map is not a function` bir dizi sözleşmesi sorunu DEĞİL, düşmüş bir adımdır.**
|
|
117
117
|
> Ölçüldü 2026-09-05 (`f09-repro/probe`, yerel kurulum): başarılı bir microservice adımında
|
|
118
118
|
> `Steps.<adım>.data` `[object Array]`'dir, `Array.isArray` true döner ve `.map`/`.filter`/
|
|
119
|
-
> `.concat` çalışır. Adım hata döndüğünde (401, 500, geçersiz parametre)
|
|
120
|
-
> **hata gövdesini**
|
|
121
|
-
> `(Steps.x.data || []).map is not a function` tam olarak burada
|
|
119
|
+
> `.concat` çalışır. Adım hata döndüğünde (401, 500, geçersiz parametre) eski runtime aynı alana ucun
|
|
120
|
+
> **hata gövdesini** yazıyordu — o bir nesnedir, dizi metodları yoktur ve
|
|
121
|
+
> `(Steps.x.data || []).map is not a function` tam olarak burada çıkıyordu. Bugünkü runtime `data`'yı
|
|
122
|
+
> `null` yazar ve gövdeyi `Steps.<adım>.error.body`'ye taşır (`|| []` koruması artık yeter). 2026-09-05 koşusunda
|
|
122
123
|
> boş kalan şirket filtresinin sebebi buydu: adım korumalı bir uca 401 ile düşmüştü.
|
|
123
124
|
> **Teşhis sırası:** önce `Steps.<adım>.error`'a bak, sonra ifadeyi suçla. Bir dizi bekliyorsan
|
|
124
125
|
> `Array.isArray(Steps.<adım>.data)` ile koru; boş listeye düşmek, hata gövdesi üzerinde
|
|
@@ -25,7 +25,7 @@ söyler. Hiçbir kural zorlamaz — ucun gerçek bir tuzağı yoksa alanı hiç
|
|
|
25
25
|
- **Sessiz boş dönüş** — "filtre eşleşmezse 404 değil boş dizi döner"
|
|
26
26
|
|
|
27
27
|
```jsonc
|
|
28
|
-
{ "name": "urun-listesi", "httpMethod": "
|
|
28
|
+
{ "name": "urun-listesi", "httpMethod": "get",
|
|
29
29
|
"description": "Aktif ürünleri ada göre sıralı döner.",
|
|
30
30
|
"caution": "Sayfalama yok; katalog büyüdükçe yanıt boyutu doğrusal büyür." }
|
|
31
31
|
```
|
|
@@ -297,6 +297,8 @@ değişkenlerini `Page.variables` olarak taşır; tarayıcıda ve cihazda okunur
|
|
|
297
297
|
**`rule.isPrivate: true`** ver (Studio: "Sunucuda tut"). Aynı istekteki sonraki middleware'ler
|
|
298
298
|
onu okumaya devam eder; istemciye gitmez. Sayfanın akışları istemcide koşar ve `Page.variables`'ı
|
|
299
299
|
okur — işaretli değişken orada `undefined`'dır; akışın ihtiyaç duyduğu alanı işaretleme.
|
|
300
|
+
`IsPrivate` değişken Studio otomatik tamamlamasında önerilmez ve `workflow_middleware_variables`
|
|
301
|
+
çıktısında da yer almaz (sonraki middleware'in editörü dahil).
|
|
300
302
|
- İstemcinin gerçekten ihtiyaç duyduğu alanları ayrı, işaretsiz bir değişkende **izin listesiyle**
|
|
301
303
|
kur (yukarıdaki `settings`). Aynı ad sonra işaretsiz bir adımla yeniden yazılırsa istemciye açılır.
|
|
302
304
|
- Yalnız microservice uçlarına bağlı middleware'de değişken yanıtla dönmez; işaret yine de zararsızdır.
|