@appaflytech/zeyrek-ai-mcp 0.0.4 → 0.0.6
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 +8 -3
- package/.env.production.example +2 -0
- package/README.md +229 -193
- package/dist/config.js +20 -2
- package/dist/knowledge/backendConstraints.js +897 -0
- package/dist/knowledge/componentCatalog.js +196 -42
- package/dist/knowledge/coverage.js +43 -0
- package/dist/knowledge/flowGuards.js +30 -0
- package/dist/knowledge/guideSections.js +49 -0
- package/dist/knowledge/hydrate.js +97 -0
- package/dist/knowledge/languageCodes.js +36 -0
- package/dist/knowledge/languages.js +105 -0
- package/dist/knowledge/layoutSummary.js +10 -0
- package/dist/knowledge/localization.js +51 -0
- package/dist/knowledge/normalize.js +449 -16
- package/dist/knowledge/outlines.js +136 -12
- package/dist/knowledge/propTypes.js +80 -0
- package/dist/knowledge/rules.js +618 -43
- package/dist/knowledge/schemaGuard.js +199 -0
- package/dist/knowledge/styleResolve.js +63 -4
- package/dist/observability/trace.js +27 -3
- package/dist/platform/errors.js +46 -4
- package/dist/platform/http.js +33 -4
- package/dist/platform/patch.js +27 -0
- package/dist/platform/refs.js +104 -0
- package/dist/platform/responses.js +9 -2
- package/dist/platform/screenshot.js +527 -16
- package/dist/platform/secrets.js +40 -0
- package/dist/platform/tokenVault.js +89 -0
- package/dist/platform/writeDiff.js +281 -0
- package/dist/resources/examples/layout-app-shell.json +1 -1
- package/dist/resources/examples/microservice-table-list.json +2 -4
- package/dist/resources/examples/page-dynamic-form.json +2 -2
- package/dist/resources/examples/page-flex-sections.json +99 -0
- package/dist/resources/examples/page-sign-in.json +3 -2
- package/dist/resources/guides/component.md +69 -8
- package/dist/resources/guides/entity.md +404 -16
- package/dist/resources/guides/expressions.md +44 -2
- package/dist/resources/guides/external-component.md +9 -0
- package/dist/resources/guides/getting-started.md +77 -7
- package/dist/resources/guides/layout.md +53 -10
- package/dist/resources/guides/microservice.md +226 -13
- package/dist/resources/guides/middleware.md +89 -1
- package/dist/resources/guides/page.md +383 -46
- package/dist/resources/guides/translation.md +126 -8
- package/dist/resources/guides/view.md +16 -1
- package/dist/resources/guides/workflow.md +198 -27
- package/dist/resources/knowledge.js +5 -0
- package/dist/resources/rules.js +27 -0
- package/dist/resources/schema-fingerprint.json +8 -0
- package/dist/resources/schemas/CreateBulkRowCommand.json +33 -0
- package/dist/resources/schemas/CreateDatasourceCommand.json +15 -6
- package/dist/resources/schemas/CreateDiscoveredEntitiesCommand.json +19 -8
- package/dist/resources/schemas/CreateEntityCommand.json +52 -12
- package/dist/resources/schemas/CreateMicroserviceCommand.json +26 -2
- package/dist/resources/schemas/CreateQueryCommand.json +56 -3
- package/dist/resources/schemas/CreateRowCommand.json +38 -0
- package/dist/resources/schemas/CreateViewCommand.json +13 -10
- package/dist/resources/schemas/CreateWorkflowCommand.json +95 -14
- package/dist/resources/schemas/GetRowsQuery.json +3 -0
- package/dist/resources/schemas/PreviewMicroserviceCommand.json +16 -0
- package/dist/resources/schemas/PreviewQueryCommand.json +5 -2
- package/dist/resources/schemas/PreviewWorkflowCommand.json +34 -9
- package/dist/resources/schemas/StepDto.json +23 -3
- package/dist/resources/schemas/UpdateBulkRowCommand.json +33 -0
- package/dist/resources/schemas/UpdateDatasourceCommand.json +15 -6
- package/dist/resources/schemas/UpdateEntityCommand.json +49 -10
- package/dist/resources/schemas/UpdateMicroserviceCommand.json +28 -3
- package/dist/resources/schemas/UpdateQueryCommand.json +13 -3
- package/dist/resources/schemas/UpdateRowCommand.json +40 -0
- package/dist/resources/schemas/UpdateViewCommand.json +15 -10
- package/dist/resources/schemas/UpdateWorkflowCommand.json +95 -14
- package/dist/server.js +4 -0
- package/dist/tools/access.js +3 -3
- package/dist/tools/applications.js +1 -1
- package/dist/tools/automations.js +63 -30
- package/dist/tools/common.js +39 -0
- package/dist/tools/components.js +16 -7
- package/dist/tools/datasources.js +83 -21
- package/dist/tools/deployments.js +34 -2
- package/dist/tools/dtos.js +25 -12
- package/dist/tools/entities.js +113 -28
- package/dist/tools/externalComponents.js +9 -3
- package/dist/tools/files.js +20 -6
- package/dist/tools/flows.js +110 -11
- package/dist/tools/folders.js +7 -2
- package/dist/tools/globalFunctions.js +63 -0
- package/dist/tools/layouts.js +103 -29
- package/dist/tools/microservices.js +229 -38
- package/dist/tools/organizations.js +5 -6
- package/dist/tools/pages.js +365 -100
- package/dist/tools/platformInfo.js +152 -11
- package/dist/tools/projects.js +183 -33
- package/dist/tools/queries.js +32 -10
- package/dist/tools/rows.js +159 -45
- package/dist/tools/screenWrite.js +59 -0
- package/dist/tools/screens.js +28 -0
- package/dist/tools/translations.js +24 -8
- package/dist/tools/views.js +20 -10
- package/dist/tools/workflows.js +33 -16
- package/package.json +6 -3
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { logStep } from '../observability/trace.js';
|
|
2
|
+
import { PlatformApiError } from '../platform/errors.js';
|
|
2
3
|
import { ensureWorkflowErrorPath } from './errorPath.js';
|
|
3
4
|
const SORT_DEFAULT = { name: 'sortField', type: 'sortType', source: 'query-string' };
|
|
4
5
|
const LIMIT_DEFAULT = { name: 'limit', offset: 'offset', source: 'query-string' };
|
|
@@ -194,9 +195,70 @@ export function normalizeWorkflowDefinition(definition) {
|
|
|
194
195
|
* '<adım adı>.data'dır; verilmemiş isRaw'ı buna eşitliyoruz ki editörde bir kez açılan akış
|
|
195
196
|
* sessizce başka bir şeye dönüşmesin. Kalan tutarsızlığı FLOW-009 yakalar.
|
|
196
197
|
*/
|
|
197
|
-
|
|
198
|
+
/**
|
|
199
|
+
* Adım tipi sözlüğü backend'in, KOŞAN tipler barındırıcının: motor yedi tip register eder, web
|
|
200
|
+
* `Runtime.svelte` altı, mobil `ScreenRuntime.tsx` beş tip ekler ve aynı sayfa kaydı ikisinde de
|
|
201
|
+
* koşar. Register edilmemiş bir tipe gelen akış sessizce ölür (`flow-engine.ts` `process()`
|
|
202
|
+
* callback bulamazsa next/completed/stopped hiç çağrılmaz), üstelik `run()` promise'i
|
|
203
|
+
* çözülmediği için `__ngAppsFlowActivity.running` sıfıra inmez. İki hâl tamamen türetilebilir
|
|
204
|
+
* olduğu için lint değil NORMALİZASYON — kalanı (`confirm`/`download` mobilde yok) yalnız
|
|
205
|
+
* uygulama tarafında kapanabilir, `FLOW-023` onları uyarır.
|
|
206
|
+
*/
|
|
207
|
+
function normalizeFlowHostGaps(steps, list) {
|
|
198
208
|
const fixes = [];
|
|
199
|
-
|
|
209
|
+
const label = (step) => String(step.stepId ?? step.name ?? '?');
|
|
210
|
+
// 'end' adımını hiçbir barındırıcı register etmez ve Studio yeni akışı YALNIZ bir start adımıyla
|
|
211
|
+
// tohumlar (`event-actions.context.ts` initFlow) — yani End düğümü hiçbir yerde gerekmiyor.
|
|
212
|
+
// Ona işaret eden dal `nextStepId: null` olur (motor completed/stopped'ı düzgün çağırır) ve ölü
|
|
213
|
+
// adım gövdeden çıkar; bırakılsaydı FLOW-005 onu haklı olarak erişilemez adım diye bloklardı.
|
|
214
|
+
const endStepIds = new Set(list
|
|
215
|
+
.filter(step => step.type === 'end')
|
|
216
|
+
.map(step => String(step.stepId ?? ''))
|
|
217
|
+
.filter(id => id.length > 0));
|
|
218
|
+
for (const step of list) {
|
|
219
|
+
// navigate ile redirect'in argümanı AYNI ({ path, pageId, query, targetBlank }) ama navigate'i
|
|
220
|
+
// yalnız mobil register eder; redirect ise iki barındırıcıda da koşar (mobilde aynı navigate
|
|
221
|
+
// uygulamasına eşlenir), yani redirect kesin üstün.
|
|
222
|
+
if (step.type === 'navigate') {
|
|
223
|
+
step.type = 'redirect';
|
|
224
|
+
fixes.push(`'${label(step)}': type navigate → redirect yazıldı — argüman şekli aynı ({ path, pageId, query, targetBlank }) ama navigate'i web barındırıcısı register etmez, redirect ikisinde de koşar`);
|
|
225
|
+
}
|
|
226
|
+
if (endStepIds.size === 0)
|
|
227
|
+
continue;
|
|
228
|
+
const branches = step.branches;
|
|
229
|
+
if (Array.isArray(branches)) {
|
|
230
|
+
for (const branch of asObjects(branches)) {
|
|
231
|
+
if (!endStepIds.has(String(branch.nextStepId ?? '')))
|
|
232
|
+
continue;
|
|
233
|
+
branch.nextStepId = null;
|
|
234
|
+
fixes.push(`'${label(step)}': '${String(branch.name ?? branch.id ?? '?')}' dalının nextStepId'si null yazıldı — hedef bir 'end' adımıydı, o tipi hiçbir barındırıcı register etmez ve akış oraya girince çıkamaz`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
else if (branches && typeof branches === 'object') {
|
|
238
|
+
const dictionary = branches;
|
|
239
|
+
for (const name of Object.keys(dictionary)) {
|
|
240
|
+
if (!endStepIds.has(String(dictionary[name] ?? '')))
|
|
241
|
+
continue;
|
|
242
|
+
dictionary[name] = null;
|
|
243
|
+
fixes.push(`'${label(step)}': '${name}' dalının nextStepId'si null yazıldı — hedef bir 'end' adımıydı, o tipi hiçbir barındırıcı register etmez ve akış oraya girince çıkamaz`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (Array.isArray(steps) && endStepIds.size > 0) {
|
|
248
|
+
for (let index = steps.length - 1; index >= 0; index--) {
|
|
249
|
+
const step = steps[index];
|
|
250
|
+
if (!step || typeof step !== 'object' || step.type !== 'end')
|
|
251
|
+
continue;
|
|
252
|
+
steps.splice(index, 1);
|
|
253
|
+
fixes.push(`'${label(step)}': 'end' adımı gövdeden çıkarıldı — motor bu tipi register etmez, Studio da yeni akışı yalnız start ile tohumlar; akış son adımın dalındaki nextStepId: null ile biter`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return fixes;
|
|
257
|
+
}
|
|
258
|
+
export function normalizeFlowSteps(steps) {
|
|
259
|
+
const list = asObjects(steps);
|
|
260
|
+
const fixes = normalizeFlowHostGaps(steps, list);
|
|
261
|
+
for (const step of list) {
|
|
200
262
|
if (step.type !== 'microservice')
|
|
201
263
|
continue;
|
|
202
264
|
const args = (step.args ?? {});
|
|
@@ -223,7 +285,10 @@ const REQUIRED_COLLECTIONS = ['events', 'validations', 'stateBindings', 'childre
|
|
|
223
285
|
* girmez) — doğrulayıcı "valid" der, yazma patlar. Tamamen türetilebilir olduğu için lint
|
|
224
286
|
* değil normalizasyon.
|
|
225
287
|
*/
|
|
226
|
-
|
|
288
|
+
// PropValueType (DesignComponentProperty.cs:421) — 'translation' listeden düşünce çeviriye bağlı
|
|
289
|
+
// prop'un KENDİSİ veri sanılıp static Prop'a sarılıyordu: istek 200 dönüyor, prop bozuk kuruluyordu
|
|
290
|
+
// (09-04 Y-22). Enum burada eksiksiz tutulur.
|
|
291
|
+
const PROP_VALUE_TYPES = new Set(['static', 'file', 'flow', 'widget', 'element', 'page', 'code', 'translation']);
|
|
227
292
|
function isPropShaped(value) {
|
|
228
293
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value) && PROP_VALUE_TYPES.has(String(value.type));
|
|
229
294
|
}
|
|
@@ -387,6 +452,52 @@ export function schemaOpValidationErrors(ops) {
|
|
|
387
452
|
}
|
|
388
453
|
return errors;
|
|
389
454
|
}
|
|
455
|
+
/**
|
|
456
|
+
* F-06 — `style` prop'una kebab-case CSS anahtarı yazmak sessizce etkisiz kalıyor: platform
|
|
457
|
+
* değeri kabul ediyor, `page_schema_get` aynen geri veriyor ve runtime hiç uygulamıyor. Tek işaret
|
|
458
|
+
* tarayıcı konsolundaki tek satır ("Using kebab-case for css properties in objects is not
|
|
459
|
+
* supported. Did you mean minHeight?") ve `page_screenshot` konsolu döndürmediği için yalnız MCP
|
|
460
|
+
* ile çalışan bir ajan bunu HİÇ göremiyor. 09-05 koşusunun en pahalı yanlış teşhisi buydu (~15 dk)
|
|
461
|
+
* ve yanlış teşhis düğümleri silip yeniden kurmaya, o da 20 setter'ın kopmasına yol açtı.
|
|
462
|
+
*
|
|
463
|
+
* Seçici anahtarlara (`&__body`, `:hover`, `--token`) DOKUNULMAZ; yalnız `a-b` biçimli düz CSS
|
|
464
|
+
* özellik adları camelCase'e çevrilir ve çevrim `gatewayNormalized` ile bildirilir.
|
|
465
|
+
*/
|
|
466
|
+
const styleKeyPattern = /^[a-z][a-z0-9]*(-[a-z0-9]+)+$/;
|
|
467
|
+
export function isStylePropName(name) {
|
|
468
|
+
return name === 'style' || /Style$/.test(name);
|
|
469
|
+
}
|
|
470
|
+
export function normalizeStyleKeys(value, label, fixed) {
|
|
471
|
+
if (Array.isArray(value)) {
|
|
472
|
+
for (const entry of value)
|
|
473
|
+
normalizeStyleKeys(entry, label, fixed);
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
if (!value || typeof value !== 'object')
|
|
477
|
+
return;
|
|
478
|
+
const record = value;
|
|
479
|
+
for (const key of Object.keys(record)) {
|
|
480
|
+
normalizeStyleKeys(record[key], label, fixed);
|
|
481
|
+
if (!styleKeyPattern.test(key))
|
|
482
|
+
continue;
|
|
483
|
+
const camel = key.replace(/-([a-z0-9])/g, (_, character) => character.toUpperCase());
|
|
484
|
+
if (Object.hasOwn(record, camel)) {
|
|
485
|
+
delete record[key];
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
record[camel] = record[key];
|
|
489
|
+
delete record[key];
|
|
490
|
+
}
|
|
491
|
+
fixed.push(`${label}: '${key}' → '${camel}'`);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
export function styleKeyNote(fixed) {
|
|
495
|
+
if (fixed.length === 0)
|
|
496
|
+
return [];
|
|
497
|
+
return [`stil anahtarları camelCase'e çevrildi (${fixed.join(' · ')}) — runtime kebab-case anahtarı OKUMAZ, `
|
|
498
|
+
+ 'değer kaydedilir ama hiç uygulanmaz ve tek işareti tarayıcı konsoludur (F-06). '
|
|
499
|
+
+ "Seçici anahtarlara ('&__body', ':hover', '--token') dokunulmadı."];
|
|
500
|
+
}
|
|
390
501
|
export function normalizeComponentDefaults(nodes) {
|
|
391
502
|
const fixed = [];
|
|
392
503
|
for (const node of asObjects(nodes)) {
|
|
@@ -402,7 +513,14 @@ export function normalizeComponentDefaults(nodes) {
|
|
|
402
513
|
missing.push(key);
|
|
403
514
|
}
|
|
404
515
|
if (node.props && typeof node.props === 'object' && !Array.isArray(node.props)) {
|
|
405
|
-
const
|
|
516
|
+
const props = node.props;
|
|
517
|
+
const styleKeys = [];
|
|
518
|
+
for (const name of Object.keys(props).filter(isStylePropName)) {
|
|
519
|
+
normalizeStyleKeys(props[name], `${String(node.elementId ?? node.name ?? '?')}.${name}`, styleKeys);
|
|
520
|
+
}
|
|
521
|
+
if (styleKeys.length > 0)
|
|
522
|
+
missing.push(`stil anahtarları camelCase'e çevrildi: ${styleKeys.join(' · ')}`);
|
|
523
|
+
const { wrapped, deepened } = wrapNodeProps(props);
|
|
406
524
|
if (wrapped.length > 0)
|
|
407
525
|
missing.push(`prop'lar Prop sarmalına alındı: ${wrapped.join('+')}`);
|
|
408
526
|
if (deepened.length > 0)
|
|
@@ -459,7 +577,9 @@ function normalizeReplacePropValue(op) {
|
|
|
459
577
|
op.expression = expression;
|
|
460
578
|
return [`${label}: replace_prop code prop'u 'expression' alanına taşındı — kod prop'u value ile değil expression ile verilir`];
|
|
461
579
|
}
|
|
462
|
-
|
|
580
|
+
// Applier her value'yu Static (ifade verilmişse Code) Prop'a çevirir; diğer tipler bu op'la
|
|
581
|
+
// ifade EDİLEMEZ. Sessiz bozuk kayıt yerine çağrı burada durur (09-04 Y-22).
|
|
582
|
+
throw new PlatformApiError(`${label}: '${propType}' tipli Prop replace_prop ile kurulamaz — bu op her değeri static (ifade verilmişse code) Prop'a çevirir, gönderdiğiniz sarmal Data'nın içine düşer ve prop sessizce bozulur. Bu prop'u add_component ile (komponenti yeniden ekleyerek) ya da page_update(schema) ile tam şema yazarak kurun; çeviri bağlama şekli için platform_guide('translation').`, 400);
|
|
463
583
|
}
|
|
464
584
|
export function normalizeSchemaOps(ops) {
|
|
465
585
|
const components = [];
|
|
@@ -467,6 +587,9 @@ export function normalizeSchemaOps(ops) {
|
|
|
467
587
|
const validations = [];
|
|
468
588
|
for (const op of asObjects(ops)) {
|
|
469
589
|
if (op.op === 'replace_prop') {
|
|
590
|
+
if (isStylePropName(String(op.prop ?? ''))) {
|
|
591
|
+
normalizeStyleKeys(op.value, `'${String(op.target ?? '?')}.${String(op.prop)}'`, props);
|
|
592
|
+
}
|
|
470
593
|
props.push(...normalizeReplacePropValue(op));
|
|
471
594
|
continue;
|
|
472
595
|
}
|
|
@@ -501,6 +624,8 @@ export const PAGE_CONTAINER_ELEMENT_ID = 'page_container';
|
|
|
501
624
|
* `CreatePageWhenApplicationCreatedEventHandler` + `SeedData`). Renderer varsayılanı
|
|
502
625
|
* BİLMEZ — `isFlexLayout` yalnız düğümün kendi prop'una bakar — bu yüzden gateway'in kurduğu
|
|
503
626
|
* kapsayıcı da seti açıkça taşır; yoksa Studio'dan açılan sayfa flex, MCP'den açılan ızgara olurdu.
|
|
627
|
+
* 2026-09-01: `wrap` varsayılanı `true` oldu (`DEFAULT_CONTAINER_LAYOUT_PROPS`) ve sete eklendi —
|
|
628
|
+
* `row` + `wrap:true` altında w:12 çocuklar alt satıra sararak dikey akar (eski tek-satır sıkışması bitti).
|
|
504
629
|
*/
|
|
505
630
|
export function pageContainerNode() {
|
|
506
631
|
return {
|
|
@@ -515,7 +640,8 @@ export function pageContainerNode() {
|
|
|
515
640
|
horizontalAlign: { type: 'static', data: 'left' },
|
|
516
641
|
verticalAlign: { type: 'static', data: 'top' },
|
|
517
642
|
size: { type: 'static', data: 'shrink' },
|
|
518
|
-
gap: { type: 'static', data: 'medium' }
|
|
643
|
+
gap: { type: 'static', data: 'medium' },
|
|
644
|
+
wrap: { type: 'static', data: true }
|
|
519
645
|
},
|
|
520
646
|
blocks: [{ breakpoint: 1024, size: { h: 24, w: 12 }, position: { x: 0, y: 0 } }],
|
|
521
647
|
states: { isSticky: false, isDroppable: true, isScrollable: true, isWrapper: true },
|
|
@@ -632,6 +758,109 @@ export function heightTypeNote(stamped) {
|
|
|
632
758
|
: stamped.join(', ');
|
|
633
759
|
return [`appearance.heightType yazıldı (${detail}): platform boş bırakılan appearance'ı 'fixed'e sabitliyor, Studio ise katalogdaki isDefaultAutoHeight'a bakıyor. 'fixed' düğüm tuvalde tam size.h*10px çizilip taşan içeriği kırptığı için katalog varsayılanı gövdeye açıkça yazıldı; bilerek sınırlamak istediğin düğüme appearance.heightType:'fixed' ver`];
|
|
634
760
|
}
|
|
761
|
+
/**
|
|
762
|
+
* Flex çocuğunun boyut modeli (2026-09-01, `AddSizeStringsToComponentAppearance`):
|
|
763
|
+
* `appearance.widthType` beş değer alır — `cover`/`contain` medya sığdırma, `auto`/`fixed`/`fill`
|
|
764
|
+
* flex-çocuk boyut modu. `fixed` iken serbest CSS uzunluğu `widthString`/`heightString`'den okunur;
|
|
765
|
+
* `fill`/`auto` string taşımaz (Studio paneli mod değişiminde string'i SİLER —
|
|
766
|
+
* `appearance-size.functions.ts` `buildFlexWidthMode`). Öncelik: fill > fixed(+string) > auto >
|
|
767
|
+
* eski 12 kolon span (`widthType` hiç yoksa). Eski backend bilinmeyen `widthType`'ı sessizce
|
|
768
|
+
* düşürür (`ComponentWidthType` enum'una `Fixed`/`Fill` 2026-09-01'de eklendi).
|
|
769
|
+
*/
|
|
770
|
+
const WIDTH_TYPES = new Set(['cover', 'contain', 'auto', 'fixed', 'fill']);
|
|
771
|
+
/**
|
|
772
|
+
* `packages/ng-apps-screen-builder/src/flex-layout.ts` `normalizeCssSize`'ın birebir portu:
|
|
773
|
+
* çıplak sayı → px; birimler px/%/vw/vh/vmin/vmax/rem/em/ch; `calc(...)` aynen geçer; gerisi
|
|
774
|
+
* (negatif, `pt`, `fr`, `auto`…) undefined — platform kaydeder ama panel/runtime değeri düşürür.
|
|
775
|
+
*/
|
|
776
|
+
export function normalizeCssSize(raw) {
|
|
777
|
+
if (typeof raw === 'number')
|
|
778
|
+
return Number.isFinite(raw) && raw > 0 ? `${raw}px` : undefined;
|
|
779
|
+
const value = String(raw ?? '').trim();
|
|
780
|
+
if (!value)
|
|
781
|
+
return undefined;
|
|
782
|
+
if (/^\d+(\.\d+)?$/.test(value))
|
|
783
|
+
return `${value}px`;
|
|
784
|
+
if (/^\d+(\.\d+)?(px|%|vw|vh|vmin|vmax|rem|em|ch)$/.test(value))
|
|
785
|
+
return value;
|
|
786
|
+
if (/^calc\(.+\)$/.test(value))
|
|
787
|
+
return value;
|
|
788
|
+
return undefined;
|
|
789
|
+
}
|
|
790
|
+
function appearanceOf(node) {
|
|
791
|
+
return (node.appearance && typeof node.appearance === 'object' && !Array.isArray(node.appearance)
|
|
792
|
+
? node.appearance
|
|
793
|
+
: {});
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Studio paritesi: panel çıplak sayıyı `px`e çevirerek yazar (`buildFlexSizeValue`), gateway de
|
|
797
|
+
* aynısını yapar ki iki yoldan kurulan şema aynı görünsün. Yalnız türetilebilir olan çevrilir;
|
|
798
|
+
* geçersiz değerler burada DEĞİL `flexSizeWarnings`'te raporlanır (uyarı, silme değil — platform
|
|
799
|
+
* da silmiyor, yalnız okumuyor).
|
|
800
|
+
*/
|
|
801
|
+
export function normalizeSizeStrings(nodes) {
|
|
802
|
+
const fixed = [];
|
|
803
|
+
for (const node of asObjects(nodes)) {
|
|
804
|
+
const appearance = appearanceOf(node);
|
|
805
|
+
for (const field of ['widthString', 'heightString']) {
|
|
806
|
+
const raw = appearance[field];
|
|
807
|
+
if (raw === undefined || raw === null || raw === '')
|
|
808
|
+
continue;
|
|
809
|
+
const normalized = normalizeCssSize(raw);
|
|
810
|
+
if (normalized !== undefined && normalized !== raw) {
|
|
811
|
+
appearance[field] = normalized;
|
|
812
|
+
node.appearance = appearance;
|
|
813
|
+
fixed.push(`${String(node.elementId ?? node.name ?? node.type ?? '?')}.${field}: ${String(raw)} → ${normalized}`);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
fixed.push(...normalizeSizeStrings(node.children));
|
|
817
|
+
}
|
|
818
|
+
return fixed;
|
|
819
|
+
}
|
|
820
|
+
export function sizeStringNote(fixed) {
|
|
821
|
+
if (fixed.length === 0)
|
|
822
|
+
return [];
|
|
823
|
+
return [`Boyut string'i normalize edildi (${fixed.join(', ')}): Studio paneli çıplak sayıyı px'e çevirerek yazar (appearance-size.functions.ts buildFlexSizeValue), gateway aynı biçimi üretir`];
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* İki uyarı tek yürüyüşte:
|
|
827
|
+
*
|
|
828
|
+
* - **mod uyumsuzluğu** (`mismatchCode`): `widthString`/`heightString` var ama ilgili
|
|
829
|
+
* `*Type` `'fixed'` değil — string yalnız `fixed` modda okunur (`getFlexChildStyle`;
|
|
830
|
+
* `fill` her şeyi ezer), Studio paneli mod değişiminde string'i siler. Yazan ajan
|
|
831
|
+
* büyük olasılıkla modu yazmayı unutmuştur.
|
|
832
|
+
* - **geçersiz değer / bilinmeyen mod** (`invalidCode`): `normalizeCssSize`'dan geçmeyen
|
|
833
|
+
* string ya da `WIDTH_TYPES` dışı `widthType` — platform 200 döner ama değer sessizce
|
|
834
|
+
* düşer (eski backend bilinmeyen enum'u da düşürür).
|
|
835
|
+
*/
|
|
836
|
+
export function flexSizeWarnings(nodes, mismatchCode, invalidCode) {
|
|
837
|
+
const warnings = [];
|
|
838
|
+
const walk = (list) => {
|
|
839
|
+
for (const node of list) {
|
|
840
|
+
const appearance = appearanceOf(node);
|
|
841
|
+
const label = String(node.elementId ?? node.name ?? node.type ?? '?');
|
|
842
|
+
const widthType = appearance.widthType === undefined ? undefined : String(appearance.widthType);
|
|
843
|
+
const heightType = appearance.heightType === undefined ? undefined : String(appearance.heightType);
|
|
844
|
+
if (widthType !== undefined && !WIDTH_TYPES.has(widthType)) {
|
|
845
|
+
warnings.push(`${invalidCode}: '${label}' düğümünde bilinmeyen widthType '${widthType}' — geçerli küme: cover · contain · auto · fixed · fill. Platform kabul eder ama renderer okumaz, eski backend enum'u sessizce düşürür.`);
|
|
846
|
+
}
|
|
847
|
+
for (const [field, type, mode] of [['widthString', widthType, 'widthType'], ['heightString', heightType, 'heightType']]) {
|
|
848
|
+
const raw = appearance[field];
|
|
849
|
+
if (raw === undefined || raw === null || raw === '')
|
|
850
|
+
continue;
|
|
851
|
+
if (normalizeCssSize(raw) === undefined) {
|
|
852
|
+
warnings.push(`${invalidCode}: '${label}' düğümünde ${field} ('${String(raw)}') geçerli bir CSS uzunluğu değil — kabul edilenler: çıplak sayı (px sayılır), px · % · vw · vh · vmin · vmax · rem · em · ch birimli değer, calc(...). Negatif/pt/fr/auto GEÇMEZ; platform kaydeder ama panel ve runtime değeri sessizce düşürür.`);
|
|
853
|
+
}
|
|
854
|
+
if (type !== 'fixed') {
|
|
855
|
+
warnings.push(`${mismatchCode}: '${label}' düğümünde ${field} var ama ${mode} 'fixed' değil (${type ?? 'yok'}) — string yalnız ${mode}:'fixed' iken okunur; 'fill' her şeyi ezer, 'auto'/mod yokken 12 kolon span geçerlidir. Sabit ölçü istiyorsan aynı düğüme ${mode}:'fixed' da yaz — platform_guide('component') §appearance.`);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
walk(asObjects(node.children));
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
walk(asObjects(nodes));
|
|
862
|
+
return warnings;
|
|
863
|
+
}
|
|
635
864
|
const GRID_COLUMNS = 12;
|
|
636
865
|
const DESKTOP_BREAKPOINT = 1024;
|
|
637
866
|
const DEFAULT_RECT = { x: 0, y: 0, w: 12, h: 6 };
|
|
@@ -732,7 +961,7 @@ function moveActive(items, active) {
|
|
|
732
961
|
? item
|
|
733
962
|
: moved.find(entry => entry.elementId === item.elementId) ?? item));
|
|
734
963
|
}
|
|
735
|
-
function reorderSiblings(nodes) {
|
|
964
|
+
function reorderSiblings(nodes, causes) {
|
|
736
965
|
const laidOut = nodes
|
|
737
966
|
.map((node, index) => ({ node, elementId: typeof node.elementId === 'string' && node.elementId !== '' ? node.elementId : `#${index}` }))
|
|
738
967
|
.filter(entry => !ROOT_LAYER_TYPES.has(String(entry.node.type ?? '')));
|
|
@@ -741,8 +970,17 @@ function reorderSiblings(nodes) {
|
|
|
741
970
|
let items = laidOut.map(entry => ({ elementId: entry.elementId, rect: rectOf(desktopBlock(entry.node)) }));
|
|
742
971
|
for (const entry of laidOut) {
|
|
743
972
|
const active = items.find(item => item.elementId === entry.elementId);
|
|
744
|
-
if (active)
|
|
745
|
-
|
|
973
|
+
if (!active)
|
|
974
|
+
continue;
|
|
975
|
+
// Kimin ittiğini burada yakalıyoruz: bildirim "y değişti" demekle kalmayıp hangi kardeşle
|
|
976
|
+
// hücre paylaşıldığını da söylesin (G-31). Aynı düğüm birden çok turda itilirse SON iten
|
|
977
|
+
// kalır — düzeni açıklayan da odur.
|
|
978
|
+
const beforeMove = new Map(items.map(item => [item.elementId, item.rect.y]));
|
|
979
|
+
items = moveActive(items, active);
|
|
980
|
+
for (const item of items) {
|
|
981
|
+
if (beforeMove.get(item.elementId) !== item.rect.y)
|
|
982
|
+
causes.set(item.elementId, entry.elementId);
|
|
983
|
+
}
|
|
746
984
|
}
|
|
747
985
|
let moved = 0;
|
|
748
986
|
for (const [index, entry] of laidOut.entries()) {
|
|
@@ -764,11 +1002,11 @@ function reorderSiblings(nodes) {
|
|
|
764
1002
|
}
|
|
765
1003
|
return moved;
|
|
766
1004
|
}
|
|
767
|
-
function reorderPass(nodes) {
|
|
1005
|
+
function reorderPass(nodes, causes) {
|
|
768
1006
|
const siblings = asObjects(nodes);
|
|
769
|
-
let moved = reorderSiblings(siblings);
|
|
1007
|
+
let moved = reorderSiblings(siblings, causes);
|
|
770
1008
|
for (const node of siblings) {
|
|
771
|
-
moved += reorderPass(node.children);
|
|
1009
|
+
moved += reorderPass(node.children, causes);
|
|
772
1010
|
}
|
|
773
1011
|
return moved;
|
|
774
1012
|
}
|
|
@@ -811,12 +1049,17 @@ const MAX_REORDER_PASSES = 8;
|
|
|
811
1049
|
*/
|
|
812
1050
|
export function reorderSchemaBlocks(nodes) {
|
|
813
1051
|
const before = [];
|
|
1052
|
+
const causes = new Map();
|
|
814
1053
|
collectLayout(nodes, before);
|
|
815
|
-
for (let pass = 0; pass < MAX_REORDER_PASSES && reorderPass(nodes) > 0; pass++)
|
|
1054
|
+
for (let pass = 0; pass < MAX_REORDER_PASSES && reorderPass(nodes, causes) > 0; pass++)
|
|
816
1055
|
continue;
|
|
817
1056
|
return before
|
|
818
1057
|
.filter(entry => rectOf(desktopBlock(entry.node)).y !== entry.y)
|
|
819
|
-
.map(entry =>
|
|
1058
|
+
.map(entry => {
|
|
1059
|
+
const cause = causes.get(entry.label);
|
|
1060
|
+
const reason = cause && cause !== entry.label ? ` — '${cause}' ile aynı hücreleri paylaşıyordu` : '';
|
|
1061
|
+
return `${entry.label} y:${entry.y}→${rectOf(desktopBlock(entry.node)).y}${entry.hasBlock ? '' : " (desktop block'u yazıldı)"}${reason}`;
|
|
1062
|
+
});
|
|
820
1063
|
}
|
|
821
1064
|
export function reorderNote(moved) {
|
|
822
1065
|
if (moved.length === 0)
|
|
@@ -1177,6 +1420,9 @@ export function stringStyleWarning(ids) {
|
|
|
1177
1420
|
* kapsayıcıya `direction: 'column'` yazmak SESSİZCE hiçbir şey yapmaz — doğrulama duvarı da
|
|
1178
1421
|
* susar, çünkü prop katalogda VARDIR (2026-08-25 ölçümü: `page_schema_validate` yedi prop'a da
|
|
1179
1422
|
* `valid: true` döndü). Katalog varsayılanına eşit değerler elenir; onlar niyet taşımaz.
|
|
1423
|
+
* `wrap` varsayılanı 2026-09-01'de beş kapsayıcı şemasında ve Studio
|
|
1424
|
+
* `DEFAULT_CONTAINER_LAYOUT_PROPS`'ta `false` → `true` oldu; buradaki küme onu izler,
|
|
1425
|
+
* yoksa varsayılanla kurulmuş her düğüm yanlış pozitif üretir.
|
|
1180
1426
|
*/
|
|
1181
1427
|
const FLEX_LAYOUT_DEFAULTS = {
|
|
1182
1428
|
direction: 'row',
|
|
@@ -1184,16 +1430,34 @@ const FLEX_LAYOUT_DEFAULTS = {
|
|
|
1184
1430
|
verticalAlign: 'top',
|
|
1185
1431
|
size: 'shrink',
|
|
1186
1432
|
gap: 'medium',
|
|
1187
|
-
wrap:
|
|
1433
|
+
wrap: true
|
|
1188
1434
|
};
|
|
1189
1435
|
/** Kapsayıcı düzenini kuran prop kümesi (`ContainerComponent` şeması): `layout` + ona bağlı altı ayar. */
|
|
1190
1436
|
export const FLEX_LAYOUT_PROP_NAMES = new Set(['layout', ...Object.keys(FLEX_LAYOUT_DEFAULTS)]);
|
|
1437
|
+
/**
|
|
1438
|
+
* Düzen prop grubunu taşıyan komponent tipleri: `packages/ng-apps-ui/src/components` altında
|
|
1439
|
+
* kendi `schema.ts` dosyasında `horizontalAlign` tanımlayanlar. Kural yalnız bunlara uygulanır — `button` · `avatar` ·
|
|
1440
|
+
* `icon` gibi komponentlerin KENDİ `size` prop'u flex `size`'ı değildir ve dokuz komponent için
|
|
1441
|
+
* yanlış uyarı üretiyordu (09-04 Y-08).
|
|
1442
|
+
*/
|
|
1443
|
+
const FLEX_LAYOUT_HOST_TYPES = new Set(['aibox', 'box', 'box-section', 'container', 'page-container', 'form', 'layout', 'widget-area']);
|
|
1444
|
+
/**
|
|
1445
|
+
* Eski `cssgrid` (box/aibox'ın akan gerçek `display:grid` kipi; 2026-09-02'de seçenek olmaktan
|
|
1446
|
+
* çıktı, kayıtlı düğümlerde çalışmaya devam ediyor) düzen prop'larının HEPSİNİ ölü yapmaz:
|
|
1447
|
+
* `getGridChildrenStyle` boşluğu (`gap`/`gapCustom` → `resolveFlexGap`) ve `verticalAlign`'ı
|
|
1448
|
+
* ('stretch' ise `align-items`) okur. Bu ikisini ölü diye bildirmek, çalışan bir ayarı
|
|
1449
|
+
* sildirirdi. Kaynak: `packages/ng-apps-screen-builder/src/grid-layout.ts`.
|
|
1450
|
+
*/
|
|
1451
|
+
const CSS_GRID_READ_PROPS = new Set(['gap', 'verticalAlign']);
|
|
1191
1452
|
export function inertFlexPropNodes(nodes) {
|
|
1192
1453
|
const found = [];
|
|
1193
1454
|
const walk = (list) => {
|
|
1194
1455
|
for (const node of list) {
|
|
1195
|
-
if (!isFlexContainer(node)) {
|
|
1456
|
+
if (FLEX_LAYOUT_HOST_TYPES.has(String(node.type ?? '')) && !isFlexContainer(node)) {
|
|
1457
|
+
const isCssGrid = staticPropValue(node, 'layout') === 'cssgrid';
|
|
1196
1458
|
const inert = Object.keys(FLEX_LAYOUT_DEFAULTS).filter(name => {
|
|
1459
|
+
if (isCssGrid && CSS_GRID_READ_PROPS.has(name))
|
|
1460
|
+
return false;
|
|
1197
1461
|
const value = staticPropValue(node, name);
|
|
1198
1462
|
return value !== undefined && value !== null && value !== FLEX_LAYOUT_DEFAULTS[name];
|
|
1199
1463
|
});
|
|
@@ -1214,6 +1478,69 @@ export function inertFlexPropWarning(entries) {
|
|
|
1214
1478
|
+ "sessizce yok sayılır (istek reddedilmez, prop katalogda vardır). Akış düzeni istiyorsan aynı düğüme "
|
|
1215
1479
|
+ "layout:'flex' de yaz, istemiyorsan bu prop'ları kaldır — platform_guide('page') Kural 1f-2."];
|
|
1216
1480
|
}
|
|
1481
|
+
/**
|
|
1482
|
+
* Bir yamanın SİLECEĞİ akışlar. Platform referanssız kalan akışı KALICI siler; uyarı yamadan sonra
|
|
1483
|
+
* geldiğinde "önce yedekle" tavsiyesi uygulanamaz hâle geliyordu (09-05 koşusu: sekiz adımlı giriş
|
|
1484
|
+
* akışı sıfırdan yeniden yazıldı). Bu yüzden plan yamadan ÖNCE hesaplanır — `remove_component`
|
|
1485
|
+
* kadar `set_event`/`remove_event` de akış öldürür ve o yolda hiç uyarı yoktu.
|
|
1486
|
+
*/
|
|
1487
|
+
export function orphanedFlowPlan(schema, pageEvents, ops) {
|
|
1488
|
+
const nodes = asObjects(schema);
|
|
1489
|
+
const opList = asObjects(ops);
|
|
1490
|
+
const plan = [];
|
|
1491
|
+
const survivingRefs = (skipElementId, skipEvent) => {
|
|
1492
|
+
const surviving = new Set();
|
|
1493
|
+
for (const event of asObjects(pageEvents)) {
|
|
1494
|
+
if (typeof event.value === 'string')
|
|
1495
|
+
surviving.add(event.value);
|
|
1496
|
+
}
|
|
1497
|
+
collectFlowRefsExcept(nodes, skipElementId, surviving);
|
|
1498
|
+
if (skipEvent !== undefined) {
|
|
1499
|
+
const node = findNodeByElementId(nodes, skipElementId);
|
|
1500
|
+
for (const event of asObjects(node?.events)) {
|
|
1501
|
+
if (String(event.name ?? '') !== skipEvent && typeof event.value === 'string')
|
|
1502
|
+
surviving.add(event.value);
|
|
1503
|
+
}
|
|
1504
|
+
for (const flowId of collectFlowRefs({ ...(node ?? {}), events: [] }))
|
|
1505
|
+
surviving.add(flowId);
|
|
1506
|
+
}
|
|
1507
|
+
return surviving;
|
|
1508
|
+
};
|
|
1509
|
+
for (const op of opList) {
|
|
1510
|
+
const target = String(op.target ?? '');
|
|
1511
|
+
if (target === '')
|
|
1512
|
+
continue;
|
|
1513
|
+
if (op.op === 'remove_component') {
|
|
1514
|
+
const doomed = findNodeByElementId(nodes, target);
|
|
1515
|
+
if (!doomed)
|
|
1516
|
+
continue;
|
|
1517
|
+
const losing = collectFlowRefs(doomed);
|
|
1518
|
+
const surviving = survivingRefs(target);
|
|
1519
|
+
const orphaned = [...losing].filter(flowId => !surviving.has(flowId));
|
|
1520
|
+
if (orphaned.length > 0)
|
|
1521
|
+
plan.push({ target, op: 'remove_component', flowIds: orphaned });
|
|
1522
|
+
continue;
|
|
1523
|
+
}
|
|
1524
|
+
if (op.op !== 'set_event' && op.op !== 'remove_event')
|
|
1525
|
+
continue;
|
|
1526
|
+
const eventName = String(op.event ?? '');
|
|
1527
|
+
const node = findNodeByElementId(nodes, target);
|
|
1528
|
+
const current = asObjects(node?.events).find(event => String(event.name ?? '') === eventName);
|
|
1529
|
+
const currentValue = typeof current?.value === 'string' ? current.value : '';
|
|
1530
|
+
if (currentValue === '' || currentValue === String(op.eventValue ?? ''))
|
|
1531
|
+
continue;
|
|
1532
|
+
const surviving = survivingRefs(target, eventName);
|
|
1533
|
+
if (!surviving.has(currentValue))
|
|
1534
|
+
plan.push({ target, op: String(op.op), flowIds: [currentValue] });
|
|
1535
|
+
}
|
|
1536
|
+
return plan;
|
|
1537
|
+
}
|
|
1538
|
+
export function orphanedFlowMessage(plan) {
|
|
1539
|
+
const lines = plan.map(entry => `'${entry.target}' (${entry.op}) → ${entry.flowIds.join(', ')}`);
|
|
1540
|
+
return `İstek gönderilmedi: bu yama ${plan.reduce((total, entry) => total + entry.flowIds.length, 0)} akışı sayfada referanssız bırakıyor ve platform referanssız akışı KALICI siler — ${lines.join(' · ')}. `
|
|
1541
|
+
+ 'Akışı korumak için önce flow_get ile gövdesini yedekleyin ya da set_event ile başka bir komponente bağlayın. '
|
|
1542
|
+
+ 'Silinmesini bilerek istiyorsanız aynı çağrıyı allowFlowDeletion: true ile tekrarlayın; yanıt hangi akışların silindiğini listeler.';
|
|
1543
|
+
}
|
|
1217
1544
|
export function orphanedFlowWarnings(schema, pageEvents, ops) {
|
|
1218
1545
|
const removals = asObjects(ops).filter(op => op.op === 'remove_component');
|
|
1219
1546
|
if (removals.length === 0)
|
|
@@ -1251,6 +1578,112 @@ function findNodeByElementId(nodes, elementId) {
|
|
|
1251
1578
|
}
|
|
1252
1579
|
return undefined;
|
|
1253
1580
|
}
|
|
1581
|
+
/**
|
|
1582
|
+
* `orphanedFlowWarnings` silinen düğümün REFERANS VERDİĞİ akışları bulur; burası ters yönü kapsar:
|
|
1583
|
+
* silinen düğümü HEDEFLEYEN setter adımlarını. Aynı `elementId` ile hemen yeniden eklense bile
|
|
1584
|
+
* platform komponent kimliğini yeniden üretir ve setter sessizce ölür — akış koşar, istek 200
|
|
1585
|
+
* döner, komponente hiçbir şey yazılmaz ve tek belirti boş komponenttir. ÖLÇÜLDÜ 2026-09-04
|
|
1586
|
+
* (benchmark 4. koşu, `S-9`): teşhis ~8 dakika sürdü, uç doğru veri döndürdüğü ve şema geçerli
|
|
1587
|
+
* olduğu için hatayı yalnız ekran görüntüsü gösterdi. Kural `platform_guide('page')` Kural 1f'de
|
|
1588
|
+
* yazılı ama düğüm silinmeden ÖNCE okunması gerekiyor; bu uyarı tek erken sinyaldir.
|
|
1589
|
+
*/
|
|
1590
|
+
export function staleSetterWarnings(flows, ops, schema) {
|
|
1591
|
+
const opList = asObjects(ops);
|
|
1592
|
+
const removed = new Set(opList
|
|
1593
|
+
.filter(op => op.op === 'remove_component')
|
|
1594
|
+
.map(op => String(op.target ?? ''))
|
|
1595
|
+
.filter(target => target.length > 0));
|
|
1596
|
+
if (removed.size === 0)
|
|
1597
|
+
return [];
|
|
1598
|
+
// Silinen KAPSAYICININ altındaki düğümler de gider. Eskiden yalnız op'un kendi target'ına
|
|
1599
|
+
// bakılıyordu; 09-05 koşusunda `sign_in_form` silinince ona değil ÇOCUĞU `btn_sign_in`'e yazan
|
|
1600
|
+
// setter sessizce koptu ve uyarı hiç çıkmadı (G-21). Komşu işlev `orphanedFlowWarnings` alt ağacı
|
|
1601
|
+
// zaten geziyordu — iki işlev aynı olaya bakıp farklı derinlik kullanıyordu.
|
|
1602
|
+
for (const target of [...removed]) {
|
|
1603
|
+
const doomed = findNodeByElementId(asObjects(schema), target);
|
|
1604
|
+
if (doomed)
|
|
1605
|
+
collectElementIds(doomed.children, removed);
|
|
1606
|
+
}
|
|
1607
|
+
const readded = new Set();
|
|
1608
|
+
for (const op of opList.filter(op => op.op === 'add_component'))
|
|
1609
|
+
collectElementIds(op.component, readded);
|
|
1610
|
+
const warnings = [];
|
|
1611
|
+
const affected = [];
|
|
1612
|
+
const seen = new Set();
|
|
1613
|
+
for (const flow of asObjects(flows)) {
|
|
1614
|
+
const flowName = String(flow.name ?? flow.slug ?? flow.id ?? 'adsız akış');
|
|
1615
|
+
for (const step of [...asObjects(flow.steps), ...asObjects(flow.errorSteps)].filter(step => step.type === 'setter')) {
|
|
1616
|
+
const args = (step.args ?? {});
|
|
1617
|
+
for (const iteratee of asObjects(args.iteratees)) {
|
|
1618
|
+
const element = typeof iteratee.element === 'string' ? iteratee.element : '';
|
|
1619
|
+
if (!removed.has(element))
|
|
1620
|
+
continue;
|
|
1621
|
+
const key = `${flow.id ?? flowName}|${step.name ?? step.stepId}|${element}`;
|
|
1622
|
+
if (seen.has(key))
|
|
1623
|
+
continue;
|
|
1624
|
+
seen.add(key);
|
|
1625
|
+
const stepName = String(step.name ?? step.stepId ?? 'adsız adım');
|
|
1626
|
+
warnings.push(readded.has(element)
|
|
1627
|
+
? `FLOW-018 (uyarı): '${element}' bu yamada silinip yeniden kuruldu ama '${flowName}' akışının '${stepName}' setter adımı hâlâ ona yazıyor. Platform komponent kimliğini yeniden üretir: akış koşar, istek 200 döner ve komponente HİÇBİR ŞEY yazılmaz — tek belirti boş komponenttir. flow_update ile akışı aynı gövdeyle yeniden kaydedin.`
|
|
1628
|
+
: `FLOW-018 (uyarı): '${element}' siliniyor ama '${flowName}' akışının '${stepName}' setter adımının hedefi o — adımın hedefi artık yok, akış sessizce hiçbir şeye yazmaz. Adımı kaldırın ya da flow_update ile başka bir komponente bağlayın.`);
|
|
1629
|
+
affected.push(`${flowName} · ${stepName} → ${element}`);
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
// Özet satırı BAŞA gelir: yama işlenmeden önce kaç iteratee'nin ve hangi akışların etkileneceği
|
|
1634
|
+
// tek bakışta görünsün (2026-09-06 · S-5, P0-2 Step 7). Tek tek satırlar altında kalır.
|
|
1635
|
+
if (affected.length > 0) {
|
|
1636
|
+
const flowNames = [...new Set(affected.map(entry => entry.split(' · ')[0]))];
|
|
1637
|
+
warnings.unshift(`FLOW-018 (özet): bu yama ${affected.length} setter iteratee'sini hedefsiz bırakıyor — ${flowNames.length} akış etkileniyor (${flowNames.join(' · ')}). Etkilenenler: ${affected.join(' | ')}. Silmeden önce flow_get ile akışları yedekleyin.`);
|
|
1638
|
+
}
|
|
1639
|
+
return warnings;
|
|
1640
|
+
}
|
|
1641
|
+
/**
|
|
1642
|
+
* FLOW-017'nin kör noktası: o kural yalnız BOŞ `element`/`property` görür. 2026-09-09 koşusunda
|
|
1643
|
+
* (S-14) setter dolu ama HENÜZ VAR OLMAYAN bir elementId'ye bağlandı — platform alanı doğrulamaz,
|
|
1644
|
+
* 200 döner ve `element`i null kaydeder; adım koşar, hata vermez, hiçbir şeye yazmaz. Tek belirti
|
|
1645
|
+
* boş komponent. Yakalayan şey yazma SONRASI `silentlyDropped` diff'iydi; bu işlev aynı şeyi
|
|
1646
|
+
* yazmadan önce söyler. Barındırıcı kaydını okumak gerektiği için `policyRules` içine giremez
|
|
1647
|
+
* (check senkron ve yalnız gövdeyi görür) — yeri FLOW-003/FLOW-013 ile aynı, `tools/flows.ts`.
|
|
1648
|
+
*/
|
|
1649
|
+
export function unknownSetterTargetWarnings(steps, errorSteps, hostNodes) {
|
|
1650
|
+
const known = new Set();
|
|
1651
|
+
collectElementIds(hostNodes, known);
|
|
1652
|
+
// Küme boşsa barındırıcı okunamamış ya da şema henüz kurulmamıştır: yanlış pozitif üretmektense
|
|
1653
|
+
// sessiz kalırız — uyarının değeri "şemada VAR olanı da söylüyor" olmasından geliyor.
|
|
1654
|
+
if (known.size === 0)
|
|
1655
|
+
return [];
|
|
1656
|
+
const warnings = [];
|
|
1657
|
+
const seen = new Set();
|
|
1658
|
+
for (const step of [...asObjects(steps), ...asObjects(errorSteps)].filter(step => step.type === 'setter')) {
|
|
1659
|
+
const args = (step.args ?? {});
|
|
1660
|
+
for (const iteratee of asObjects(args.iteratees)) {
|
|
1661
|
+
const element = typeof iteratee.element === 'string' ? iteratee.element.trim() : '';
|
|
1662
|
+
if (element.length === 0 || known.has(element))
|
|
1663
|
+
continue;
|
|
1664
|
+
const stepName = String(step.name ?? step.stepId ?? 'adsız adım');
|
|
1665
|
+
const key = `${stepName}|${element}`;
|
|
1666
|
+
if (seen.has(key))
|
|
1667
|
+
continue;
|
|
1668
|
+
seen.add(key);
|
|
1669
|
+
warnings.push(`FLOW-024 (uyarı): '${stepName}' setter adımının hedefi '${element}' barındırıcının şemasında YOK. Platform bu alanı doğrulamaz: istek 200 döner, element null kaydedilir ve adım koşunca HİÇBİR ŞEY yazmaz — tek belirti boş komponenttir (FLOW-017 yalnız BOŞ element'i görür, dolu ama var olmayan id'yi değil). Şemadaki elementId'ler: ${[...known].join(', ')}. Düğümü henüz kurmadıysanız sıra şudur: page_schema_patch add_component ile düğümü kurun, sonra akışı flow_update ile AYNI gövdeyle yeniden kaydedin — kaydedilen element null olduğu için flow_patch'in [element=…] seçicisi onu bulamaz.`);
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
return warnings;
|
|
1673
|
+
}
|
|
1674
|
+
function collectElementIds(node, into) {
|
|
1675
|
+
if (Array.isArray(node)) {
|
|
1676
|
+
for (const entry of node)
|
|
1677
|
+
collectElementIds(entry, into);
|
|
1678
|
+
return;
|
|
1679
|
+
}
|
|
1680
|
+
if (!node || typeof node !== 'object')
|
|
1681
|
+
return;
|
|
1682
|
+
const record = node;
|
|
1683
|
+
if (typeof record.elementId === 'string')
|
|
1684
|
+
into.add(record.elementId);
|
|
1685
|
+
collectElementIds(record.children, into);
|
|
1686
|
+
}
|
|
1254
1687
|
function collectFlowRefs(node) {
|
|
1255
1688
|
const found = new Set();
|
|
1256
1689
|
const visitProps = (value) => {
|