@appaflytech/zeyrek-ai-mcp 0.0.6 → 0.0.8
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 +2 -2
- package/.env.production.example +8 -0
- package/README.md +9 -8
- package/dist/config.js +18 -3
- package/dist/knowledge/backendConstraints.js +88 -0
- package/dist/knowledge/componentCatalog.js +415 -68
- package/dist/knowledge/componentEvents.js +120 -25
- package/dist/knowledge/functionGuards.js +134 -0
- package/dist/knowledge/normalize.js +512 -25
- package/dist/knowledge/outlines.js +31 -0
- package/dist/knowledge/policy.js +14 -5
- package/dist/knowledge/propTypes.js +5 -4
- package/dist/knowledge/rules.js +453 -6
- package/dist/knowledge/styleResolve.js +6 -1
- package/dist/platform/csv.js +123 -0
- package/dist/platform/errors.js +62 -3
- package/dist/platform/http.js +11 -2
- package/dist/platform/responses.js +16 -4
- package/dist/platform/screenshot.js +98 -9
- package/dist/platform/strictArguments.js +32 -0
- package/dist/platform/writeDiff.js +6 -3
- package/dist/resources/examples/flow-call-function.json +66 -0
- package/dist/resources/examples/function-total.json +70 -0
- package/dist/resources/examples/microfunction-node-sum.json +56 -0
- package/dist/resources/examples/page-dynamic-form.json +1 -1
- package/dist/resources/examples/page-sign-in.json +2 -2
- package/dist/resources/guides/component.md +14 -2
- package/dist/resources/guides/entity.md +321 -17
- package/dist/resources/guides/expressions.md +9 -0
- package/dist/resources/guides/functions.md +262 -0
- package/dist/resources/guides/getting-started.md +7 -0
- package/dist/resources/guides/layout.md +6 -0
- package/dist/resources/guides/merge.md +231 -0
- package/dist/resources/guides/microfunction.md +166 -0
- package/dist/resources/guides/microservice.md +166 -7
- package/dist/resources/guides/modify-existing.md +23 -0
- package/dist/resources/guides/page.md +101 -22
- package/dist/resources/guides/translation.md +35 -10
- package/dist/resources/guides/workflow.md +51 -2
- package/dist/resources/schema-fingerprint.json +4 -4
- package/dist/resources/schemas/AnalyzeImportQuery.json +51 -0
- package/dist/resources/schemas/ApplyMergePlanCommand.json +19 -0
- package/dist/resources/schemas/CreateBulkRowCommand.json +3 -0
- package/dist/resources/schemas/CreateEnvironmentCommand.json +31 -0
- package/dist/resources/schemas/CreateGlobalFunctionCommand.json +95 -0
- package/dist/resources/schemas/CreateImportPlanCommand.json +278 -0
- package/dist/resources/schemas/CreateMergePlanCommand.json +34 -0
- package/dist/resources/schemas/CreateMicrofunctionCommand.json +117 -0
- package/dist/resources/schemas/CreateMicrofunctionRuntimeCommand.json +51 -0
- package/dist/resources/schemas/CreateMicroserviceCommand.json +123 -2
- package/dist/resources/schemas/CreateWorkflowCommand.json +17 -3
- package/dist/resources/schemas/DataJobDto.json +267 -0
- package/dist/resources/schemas/ExportRowsCommand.json +172 -0
- package/dist/resources/schemas/Filter.json +3 -1
- package/dist/resources/schemas/GetRowsQuery.json +3 -1
- package/dist/resources/schemas/GlobalFunctionDto.json +89 -0
- package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +249 -0
- package/dist/resources/schemas/ImportAnalysisDto.json +401 -0
- package/dist/resources/schemas/ImportPlanDto.json +260 -0
- package/dist/resources/schemas/ImportSampleDto.json +90 -0
- package/dist/resources/schemas/InvokeMicrofunctionCommand.json +25 -0
- package/dist/resources/schemas/PreviewMicroserviceCommand.json +123 -2
- package/dist/resources/schemas/PreviewWorkflowCommand.json +17 -3
- package/dist/resources/schemas/ResolveMergeConflictsCommand.json +58 -0
- package/dist/resources/schemas/RevertMergePlanCommand.json +19 -0
- package/dist/resources/schemas/RunImportCommand.json +62 -0
- package/dist/resources/schemas/StepDto.json +17 -3
- package/dist/resources/schemas/UpdateBulkRowCommand.json +3 -0
- package/dist/resources/schemas/UpdateEntityCommand.json +3 -1
- package/dist/resources/schemas/UpdateGlobalFunctionCommand.json +66 -0
- package/dist/resources/schemas/UpdateMicrofunctionCommand.json +121 -0
- package/dist/resources/schemas/UpdateMicrofunctionRuntimeCommand.json +54 -0
- package/dist/resources/schemas/UpdateMicroserviceCommand.json +123 -2
- package/dist/resources/schemas/UpdateWorkflowCommand.json +17 -3
- package/dist/server.js +17 -4
- package/dist/tools/activities.js +40 -0
- package/dist/tools/automations.js +14 -5
- package/dist/tools/common.js +16 -0
- package/dist/tools/dependencies.js +1 -1
- package/dist/tools/environments.js +52 -0
- package/dist/tools/files.js +51 -50
- package/dist/tools/flows.js +104 -5
- package/dist/tools/functions.js +331 -0
- package/dist/tools/importSchemas.js +79 -0
- package/dist/tools/imports.js +241 -0
- package/dist/tools/layouts.js +4 -4
- package/dist/tools/merges.js +113 -0
- package/dist/tools/microfunctions.js +155 -0
- package/dist/tools/microservices.js +112 -9
- package/dist/tools/pages.js +188 -66
- package/dist/tools/platformInfo.js +4 -1
- package/dist/tools/queries.js +72 -6
- package/dist/tools/revisions.js +94 -0
- package/dist/tools/rows.js +342 -22
- package/dist/tools/screenWrite.js +11 -2
- package/dist/tools/templates.js +32 -5
- package/dist/tools/workflows.js +28 -6
- package/package.json +3 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { logStep } from '../observability/trace.js';
|
|
2
2
|
import { PlatformApiError } from '../platform/errors.js';
|
|
3
3
|
import { ensureWorkflowErrorPath } from './errorPath.js';
|
|
4
|
+
import { TYPED_STYLE_COMPONENTS } from './styleResolve.js';
|
|
4
5
|
const SORT_DEFAULT = { name: 'sortField', type: 'sortType', source: 'query-string' };
|
|
5
6
|
const LIMIT_DEFAULT = { name: 'limit', offset: 'offset', source: 'query-string' };
|
|
6
7
|
function asArray(value) {
|
|
@@ -80,6 +81,33 @@ function fillRootAlias(table, fixes) {
|
|
|
80
81
|
table.alias = alias;
|
|
81
82
|
fixes.push(`kök alias '${alias}' olarak dolduruldu — join'li table'da kök alias boşsa SQL'e '.<alan>' gider ve Postgres 42601 (zero-length delimited identifier) döner`);
|
|
82
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* S-06 (2026-09-09 koşusu): yazma alanlarında `alias` sözleşmede YOKTUR. Okuma/dönüş seçimleri
|
|
86
|
+
* `TableSelect` ile modellenir ve `Alias`/`Path`/`FieldSlug` taşır; yazılacak alanlar ise
|
|
87
|
+
* `TableField` ile — o sınıfta yalnız `FieldId`, `Source`, `Value`, `FolderId`, `Order`,
|
|
88
|
+
* `WriteOn` vardır (`MicroserviceTableGetParameter.cs:326`; aynı tip `WorkflowDto.cs:252`
|
|
89
|
+
* table step kuralında da kullanılıyor). Gönderilen `alias` model bağlamada düşer, yanıt onu
|
|
90
|
+
* geri getirmez ve `silentlyDropped` her yazma çağrısında sahte bir kayıp satırı basardı.
|
|
91
|
+
* Alan gövdeden **istek gitmeden** çıkarılır ve `gatewayNormalized` ile bildirilir — kayıp değil,
|
|
92
|
+
* sözleşme dışı bir alan.
|
|
93
|
+
*
|
|
94
|
+
* Sıra önemlidir: `fillRootAlias` kök alias'ı türetirken `fields[].alias`'ı da aday sayar, bu
|
|
95
|
+
* yüzden ayıklama ondan SONRA çalışır.
|
|
96
|
+
*/
|
|
97
|
+
function dropWriteFieldAliases(table, fixes) {
|
|
98
|
+
const dropped = [];
|
|
99
|
+
for (const field of asObjects(table.fields)) {
|
|
100
|
+
const alias = String(field.alias ?? '').trim();
|
|
101
|
+
if (!Object.hasOwn(field, 'alias'))
|
|
102
|
+
continue;
|
|
103
|
+
delete field.alias;
|
|
104
|
+
if (alias)
|
|
105
|
+
dropped.push(`${alias}.${String(field.fieldSlug ?? field.fieldId ?? '?')}`);
|
|
106
|
+
}
|
|
107
|
+
if (dropped.length === 0)
|
|
108
|
+
return;
|
|
109
|
+
fixes.push(`fields[].alias gövdeden çıkarıldı (${dropped.join(', ')}): yazma alanları TableField ile modelleniyor ve o sözleşmede alias YOK — alias yalnız selectFields / returnFields / filters / joins için geçerlidir (platform_guide('microservice') §Alias nerede geçerli)`);
|
|
110
|
+
}
|
|
83
111
|
/**
|
|
84
112
|
* Platform sort/limit'i null bırakır ve path'i türetmez; Studio ikisini de guard'sız okur.
|
|
85
113
|
* Eksik olanları doldurur, dolu olanlara DOKUNMAZ (yanlış değerleri MS-007 block'u yakalar).
|
|
@@ -104,6 +132,7 @@ export function normalizeTableParameters(parameters, fallbackEntityId) {
|
|
|
104
132
|
fillJoinPaths(rootEntityId, table.joins, [], aliasChains, fixes);
|
|
105
133
|
fillSelectionPaths(table, aliasChains, fixes);
|
|
106
134
|
}
|
|
135
|
+
dropWriteFieldAliases(table, fixes);
|
|
107
136
|
return fixes;
|
|
108
137
|
}
|
|
109
138
|
export function normalizeMicroserviceDefinition(definition) {
|
|
@@ -259,6 +288,15 @@ export function normalizeFlowSteps(steps) {
|
|
|
259
288
|
const list = asObjects(steps);
|
|
260
289
|
const fixes = normalizeFlowHostGaps(steps, list);
|
|
261
290
|
for (const step of list) {
|
|
291
|
+
if (step.type === 'function') {
|
|
292
|
+
const args = (step.args ?? {});
|
|
293
|
+
if (!Array.isArray(args.parameters)) {
|
|
294
|
+
args.parameters = [];
|
|
295
|
+
step.args = args;
|
|
296
|
+
fixes.push(`'${String(step.stepId ?? step.name ?? '?')}': args.parameters=[] yazıldı — function adımı parametreleri dizi bekler; eşleme bildirimdeki ada göre, sıra konumsal (K5)`);
|
|
297
|
+
}
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
262
300
|
if (step.type !== 'microservice')
|
|
263
301
|
continue;
|
|
264
302
|
const args = (step.args ?? {});
|
|
@@ -288,7 +326,7 @@ const REQUIRED_COLLECTIONS = ['events', 'validations', 'stateBindings', 'childre
|
|
|
288
326
|
// PropValueType (DesignComponentProperty.cs:421) — 'translation' listeden düşünce çeviriye bağlı
|
|
289
327
|
// prop'un KENDİSİ veri sanılıp static Prop'a sarılıyordu: istek 200 dönüyor, prop bozuk kuruluyordu
|
|
290
328
|
// (09-04 Y-22). Enum burada eksiksiz tutulur.
|
|
291
|
-
const PROP_VALUE_TYPES = new Set(['static', 'file', 'flow', 'widget', 'element', 'page', 'code', 'translation']);
|
|
329
|
+
export const PROP_VALUE_TYPES = new Set(['static', 'file', 'flow', 'widget', 'element', 'page', 'code', 'translation']);
|
|
292
330
|
function isPropShaped(value) {
|
|
293
331
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value) && PROP_VALUE_TYPES.has(String(value.type));
|
|
294
332
|
}
|
|
@@ -452,6 +490,77 @@ export function schemaOpValidationErrors(ops) {
|
|
|
452
490
|
}
|
|
453
491
|
return errors;
|
|
454
492
|
}
|
|
493
|
+
/**
|
|
494
|
+
* G-02 (2026-09-15) — prop düzeyindeki `code` bir ŞABLONDUR: runtime yalnız `{{ }}` içini çalıştırır
|
|
495
|
+
* (ng-apps-flow `runPropertyCode`), parantezsiz ref OLDUĞU GİBİ metin döner. Akış tarafında aynı
|
|
496
|
+
* sarmal (setter iteratee, microservice parametresi) ifadeyi parantezsiz koşturduğu için ajan bu
|
|
497
|
+
* alışkanlığı prop'a taşıyor: yanıt temiz, ekran "çalışıyor", değer ise ifadenin kendi metni.
|
|
498
|
+
* eventcalendar'da `"new Date().getFullYear() + …"` tarihe çözülmeye çalışıldı ve V8 onu 2001-01-01
|
|
499
|
+
* okudu. Niyet türetilemez (düz metin mi, ifade mi?) → normalizasyon değil, PAGE-032 block'u.
|
|
500
|
+
*/
|
|
501
|
+
function bracketlessCodeRef(value) {
|
|
502
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
503
|
+
return undefined;
|
|
504
|
+
const record = value;
|
|
505
|
+
if (record.type !== 'code' || typeof record.ref !== 'string')
|
|
506
|
+
return undefined;
|
|
507
|
+
const ref = record.ref.trim();
|
|
508
|
+
return ref !== '' && !ref.includes('{{') ? ref : undefined;
|
|
509
|
+
}
|
|
510
|
+
function codeTemplateMessage(label, ref) {
|
|
511
|
+
return `${label}: code prop'unun ifadesi '{{ }}' içinde değil ('${ref.length > 80 ? `${ref.slice(0, 80)}…` : ref}'). Prop düzeyinde code bir ŞABLONDUR — runtime yalnız '{{ }}' içini çalıştırır, parantezsiz ref olduğu gibi METİN olarak prop'a düşer ve hiçbir hata üretmez. İfadeyse '{{ <ifade> }}' yazın; düz metin kastediyorsanız type: 'static' kullanın. (Akış adımlarındaki value.type='code' parantezsiz koşar — iki sözdizimi farklıdır, platform_guide('expressions').)`;
|
|
512
|
+
}
|
|
513
|
+
function collectBracketlessCodeProps(value, label, errors) {
|
|
514
|
+
if (Array.isArray(value)) {
|
|
515
|
+
value.forEach((entry, index) => collectBracketlessCodeProps(entry, `${label}[${index}]`, errors));
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
if (!value || typeof value !== 'object')
|
|
519
|
+
return;
|
|
520
|
+
const ref = bracketlessCodeRef(value);
|
|
521
|
+
if (ref !== undefined) {
|
|
522
|
+
errors.push(codeTemplateMessage(label, ref));
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
526
|
+
collectBracketlessCodeProps(entry, `${label}.${key}`, errors);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* set_binding ifadeyi `expression` alanında taşır ve platform onu code Prop'un ref'ine yazar
|
|
531
|
+
* (PageSchemaMicroOpApplier; screen-builder patch.ts aynısı); replace_prop ifadeyi `value`'daki
|
|
532
|
+
* code sarmalının ref'inde taşır — aynı şablon kuralı. normalizeSchemaOps'tan SONRA çağrılır:
|
|
533
|
+
* replace_prop'un `expression`'ı ve ref'siz code sarmalı o noktada ref'e taşınmış olur.
|
|
534
|
+
*/
|
|
535
|
+
export function codePropTemplateErrors(ops) {
|
|
536
|
+
const errors = [];
|
|
537
|
+
const walkNodes = (nodes) => {
|
|
538
|
+
for (const node of asObjects(nodes)) {
|
|
539
|
+
const label = String(node.elementId ?? node.name ?? node.type ?? '?');
|
|
540
|
+
if (node.props && typeof node.props === 'object' && !Array.isArray(node.props)) {
|
|
541
|
+
for (const [name, prop] of Object.entries(node.props)) {
|
|
542
|
+
collectBracketlessCodeProps(prop, `${label}.${name}`, errors);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
walkNodes(node.children);
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
for (const op of asObjects(ops)) {
|
|
549
|
+
const label = `'${String(op.target ?? '?')}.${String(op.prop ?? '?')}'`;
|
|
550
|
+
if ((op.op === 'set_binding' || op.op === 'replace_prop') && typeof op.expression === 'string') {
|
|
551
|
+
const ref = bracketlessCodeRef({ type: 'code', ref: op.expression });
|
|
552
|
+
if (ref !== undefined)
|
|
553
|
+
errors.push(codeTemplateMessage(label, ref));
|
|
554
|
+
}
|
|
555
|
+
if (op.op === 'replace_prop') {
|
|
556
|
+
collectBracketlessCodeProps(op.value, label, errors);
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
if (op.op === 'add_component' && op.component)
|
|
560
|
+
walkNodes([op.component]);
|
|
561
|
+
}
|
|
562
|
+
return errors;
|
|
563
|
+
}
|
|
455
564
|
/**
|
|
456
565
|
* F-06 — `style` prop'una kebab-case CSS anahtarı yazmak sessizce etkisiz kalıyor: platform
|
|
457
566
|
* değeri kabul ediyor, `page_schema_get` aynen geri veriyor ve runtime hiç uygulamıyor. Tek işaret
|
|
@@ -512,6 +621,28 @@ export function normalizeComponentDefaults(nodes) {
|
|
|
512
621
|
node[key] = [];
|
|
513
622
|
missing.push(key);
|
|
514
623
|
}
|
|
624
|
+
// S-16 — görünürlük tek yerden okunur: RenderComponent `{#if !props.isHidden}`. Appearance'ta
|
|
625
|
+
// isHidden alanı YOKTUR (backend 2026-09-15'te kaldırdı); gövdede kalırsa platform onu
|
|
626
|
+
// sessizce düşürür ve komponent sayfa açılır açılmaz görünür. Değer props.isHidden'a taşınır;
|
|
627
|
+
// prop zaten verilmişse o kazanır. false yalnız silinir — varsayılan zaten görünür.
|
|
628
|
+
const appearance = node.appearance;
|
|
629
|
+
if (appearance && typeof appearance === 'object' && !Array.isArray(appearance) && Object.hasOwn(appearance, 'isHidden')) {
|
|
630
|
+
const appearanceRecord = appearance;
|
|
631
|
+
const hidden = appearanceRecord.isHidden;
|
|
632
|
+
delete appearanceRecord.isHidden;
|
|
633
|
+
if (hidden !== false && hidden !== null && hidden !== undefined) {
|
|
634
|
+
if (!node.props || typeof node.props !== 'object' || Array.isArray(node.props))
|
|
635
|
+
node.props = {};
|
|
636
|
+
const props = node.props;
|
|
637
|
+
if (Object.hasOwn(props, 'isHidden')) {
|
|
638
|
+
missing.push("appearance.isHidden silindi — props.isHidden zaten verilmiş, o geçerli (appearance'ta isHidden alanı yok)");
|
|
639
|
+
}
|
|
640
|
+
else {
|
|
641
|
+
props.isHidden = isPropShaped(hidden) ? hidden : { type: 'static', ref: null, data: hidden };
|
|
642
|
+
missing.push("appearance.isHidden → props.isHidden taşındı (runtime görünürlüğü yalnız props.isHidden'dan okur; appearance'ta isHidden alanı yok)");
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
515
646
|
if (node.props && typeof node.props === 'object' && !Array.isArray(node.props)) {
|
|
516
647
|
const props = node.props;
|
|
517
648
|
const styleKeys = [];
|
|
@@ -538,14 +669,26 @@ export function normalizeComponentDefaults(nodes) {
|
|
|
538
669
|
* add_component micro-op'unun taşıdığı komponent ağacı da aynı ToDomain yolundan geçer.
|
|
539
670
|
*/
|
|
540
671
|
/**
|
|
541
|
-
* `replace_prop` micro-op'u
|
|
542
|
-
* `new Prop(PropValueType.Static) { Data = op.Value }` kurar
|
|
543
|
-
*
|
|
544
|
-
*
|
|
545
|
-
*
|
|
546
|
-
*
|
|
547
|
-
*
|
|
548
|
-
*
|
|
672
|
+
* `replace_prop` micro-op'u ham değer alır: PageSchemaMicroOpApplier `ApplyOne` sarmalsız değerden
|
|
673
|
+
* `new Prop(PropValueType.Static) { Data = op.Value }` kurar. Prop sarmalı gönderildiğinde sarmalın
|
|
674
|
+
* kendisi `Data`'ya yazılır ve platform onu `Dictionary<string, Prop>` olarak çözmeye çalışıp
|
|
675
|
+
* **500 JsonException** verir ("could not be converted to Prop. Path: $.type"). add_component'te
|
|
676
|
+
* sarmal ZORUNLU olduğu için bu tuzağa düşmek çok kolay; şekil tamamen türetilebilir olduğundan
|
|
677
|
+
* lint değil normalizasyon: static sarmalını açıyoruz.
|
|
678
|
+
*
|
|
679
|
+
* `code` sarmalı ise OLDUĞU GİBİ gider (2026-09-15): applier 2026-09-04'ten beri `value` bir
|
|
680
|
+
* `{type, ref, data}` sözlüğüyse tipini korur (StObjectConverter JSON nesnesini
|
|
681
|
+
* `Dictionary<string, object>`'e çözer — `PageSchemaMicroOpApplierTests` request gövdesiyle ölçer).
|
|
682
|
+
* `replace_prop` `expression` alanını HİÇ OKUMAZ — yalnız set_binding okur; eskiden sarmalı
|
|
683
|
+
* `expression`'a taşıyorduk ve prop 200 dönerken `{type:'static', data:null}` kaydediliyordu.
|
|
684
|
+
* İfade `ref`'tedir (PG-117); ref yoksa string `data`'dan alınır, ikisi de yoksa çağrı durur.
|
|
685
|
+
* Platform code prop'unda yalnız `Ref`'i saklar (`Prop` kurucusu), `data` kalıcı değildir; ama
|
|
686
|
+
* `CreateProp` veri tipini `Data`'dan seçtiği için nesne/dizi `data` 500 verir — ref ile değiştirilir.
|
|
687
|
+
* Aynı sebeple `value`'suz `expression` code sarmalına çevrilir, `value`'nun yanındaki `expression`
|
|
688
|
+
* silinir. `translation` sarmalı da aynı gerekçeyle olduğu gibi gider (G-04): applier
|
|
689
|
+
* `Translation` Prop'u `Ref` + metin `Data` ile kurar; eskiden gateway onu reddedip remove+add
|
|
690
|
+
* öneriyordu ve o yol düğüme yazan setter iteratee'lerinin `element`'ini sildi. Diğer prop tipleri
|
|
691
|
+
* (page/file/flow/widget/element) bu op'la kurulmaz (Y-22).
|
|
549
692
|
*
|
|
550
693
|
* DIŞ sarmalın olmaması İÇ sarmalın da olmaması demek değil: applier'ın kurduğu Prop yine
|
|
551
694
|
* `ToDomain` → `CreateProp` yolundan geçer ve `Data`'nın içi HER SEVİYEDE
|
|
@@ -555,31 +698,64 @@ export function normalizeComponentDefaults(nodes) {
|
|
|
555
698
|
* Studio'nun `mapRawPropToSchemaProp` çıktısının aynısı: dış sarmal YOK, her yaprak sarılı.
|
|
556
699
|
*/
|
|
557
700
|
function normalizeReplacePropValue(op) {
|
|
558
|
-
const value = op.value;
|
|
559
701
|
const label = `'${String(op.target ?? '?')}.${String(op.prop ?? '?')}'`;
|
|
702
|
+
if (op.value === undefined && typeof op.expression === 'string') {
|
|
703
|
+
op.value = { type: 'code', ref: op.expression, data: op.expression };
|
|
704
|
+
delete op.expression;
|
|
705
|
+
return [`${label}: replace_prop 'expression' alanı code sarmalına (value: { type: 'code', ref, data }) çevrildi — platform replace_prop'ta expression'ı okumaz, prop static null kaydedilirdi (ifade için set_binding de kullanılabilir)`];
|
|
706
|
+
}
|
|
707
|
+
const notes = [];
|
|
708
|
+
if (Object.hasOwn(op, 'expression')) {
|
|
709
|
+
delete op.expression;
|
|
710
|
+
notes.push(`${label}: replace_prop 'expression' alanı silindi — platform onu replace_prop'ta okumaz, geçerli olan value`);
|
|
711
|
+
}
|
|
712
|
+
const value = op.value;
|
|
560
713
|
if (!isPropShaped(value)) {
|
|
561
|
-
if (
|
|
562
|
-
|
|
563
|
-
return
|
|
714
|
+
if (deepWrapPropData(value))
|
|
715
|
+
notes.push(`${label}: value'nun iç değerleri Prop'a sarıldı — op ham değer alır ama platform Data'nın içini her seviyede Dictionary<string, Prop> olarak çözer, sarımsız iç değer 500 JsonException verir`);
|
|
716
|
+
return notes;
|
|
564
717
|
}
|
|
565
718
|
const propType = String(value.type);
|
|
566
719
|
if (propType === 'static') {
|
|
567
720
|
const data = value.data;
|
|
568
721
|
op.value = data;
|
|
569
|
-
|
|
722
|
+
notes.push(`${label}: replace_prop value'su Prop sarmalından çıkarıldı — bu op ham değer alır, sarmal gönderilirse platform 500 JsonException verir`);
|
|
570
723
|
if (deepWrapPropData(data))
|
|
571
724
|
notes.push(`${label}: value'nun iç değerleri Prop'a sarıldı`);
|
|
572
725
|
return notes;
|
|
573
726
|
}
|
|
574
727
|
if (propType === 'code') {
|
|
575
|
-
const
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
728
|
+
const code = value;
|
|
729
|
+
if (typeof code.ref !== 'string') {
|
|
730
|
+
if (typeof code.data !== 'string') {
|
|
731
|
+
throw new PlatformApiError(`${label}: code sarmalında ifade yok — runtime ifadeyi ref'ten okur, ref'siz code prop'u 200 dönüp BOŞ kaydedilir. value: { type: 'code', ref: '{{ <ifade> }}' } yazın; sabit değer kastediyorsanız type: 'static' kullanın.`, 400);
|
|
732
|
+
}
|
|
733
|
+
code.ref = code.data;
|
|
734
|
+
notes.push(`${label}: code sarmalında ref yoktu, data'daki ifade ref'e kopyalandı — runtime ifadeyi ref'ten okur`);
|
|
735
|
+
}
|
|
736
|
+
if (code.data !== null && typeof code.data === 'object') {
|
|
737
|
+
notes.push(`${label}: code sarmalının nesne/dizi data'sı ref ile değiştirildi — platform code prop'unda data'yı saklamaz, nesne data'yı Dictionary<string, Prop> diye çözüp 500 verir`);
|
|
738
|
+
code.data = code.ref;
|
|
739
|
+
}
|
|
740
|
+
if (code.data === undefined)
|
|
741
|
+
code.data = code.ref;
|
|
742
|
+
return notes;
|
|
579
743
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
744
|
+
if (propType === 'translation') {
|
|
745
|
+
const translation = value;
|
|
746
|
+
if (typeof translation.ref !== 'string' || translation.ref.trim().length === 0) {
|
|
747
|
+
throw new PlatformApiError(`${label}: translation sarmalında çeviri anahtarı yok — anahtar ref'te taşınır, data yalnız tasarım yedeğidir. value: { type: 'translation', ref: '<anahtar>' } yazın; anahtarlar için translation_list.`, 400);
|
|
748
|
+
}
|
|
749
|
+
if (translation.data !== undefined && translation.data !== null && typeof translation.data === 'object') {
|
|
750
|
+
notes.push(`${label}: translation sarmalının nesne/dizi data'sı silindi — platform translation prop'unda data'yı metin olarak saklar, nesne tip adına dönüşürdü`);
|
|
751
|
+
translation.data = null;
|
|
752
|
+
}
|
|
753
|
+
return notes;
|
|
754
|
+
}
|
|
755
|
+
// Applier her sarmalın tipini korur, ama page/file/flow/widget/element taşıyıcıları başka alanlara
|
|
756
|
+
// (FlowId, RedirectPageId, dosya) yazılır ve bu op'la bağları kurulmaz. Remove+add önerilmez:
|
|
757
|
+
// düğümü yeniden kurmak ona yazan setter iteratee'lerinin element'ini siler (G-04, FLOW-018).
|
|
758
|
+
throw new PlatformApiError(`${label}: '${propType}' tipli Prop replace_prop ile kurulmaz — bu op static, code ve translation sarmalını taşır. Komponenti silip yeniden EKLEMEYİN: ona yazan setter adımlarının hedefi silinir (FLOW-018). Akış bağı için set_event, diğer tipler için page_update(schema) ile tam şema yazın.`, 400);
|
|
583
759
|
}
|
|
584
760
|
export function normalizeSchemaOps(ops) {
|
|
585
761
|
const components = [];
|
|
@@ -597,6 +773,13 @@ export function normalizeSchemaOps(ops) {
|
|
|
597
773
|
normalizeValidationList(op.validations, `'${String(op.target ?? '?')}'`, validations);
|
|
598
774
|
continue;
|
|
599
775
|
}
|
|
776
|
+
if (op.op === 'set_appearance') {
|
|
777
|
+
const appearance = op.appearance;
|
|
778
|
+
if (appearance && typeof appearance === 'object' && Object.hasOwn(appearance, 'isHidden')) {
|
|
779
|
+
throw new PlatformApiError(`'${String(op.target ?? '?')}': set_appearance isHidden yazamaz — appearance'ta isHidden alanı YOKTUR, runtime görünürlüğü yalnız props.isHidden'dan okur (S-16). Gizlemek için replace_prop { target, prop: 'isHidden', value: true }, koşullu göstermek için set_binding { target, prop: 'isHidden', expression } kullanın.`, 400);
|
|
780
|
+
}
|
|
781
|
+
continue;
|
|
782
|
+
}
|
|
600
783
|
if (op.op !== 'add_component' || !op.component)
|
|
601
784
|
continue;
|
|
602
785
|
components.push(...normalizeComponentDefaults([op.component]));
|
|
@@ -645,7 +828,7 @@ export function pageContainerNode() {
|
|
|
645
828
|
},
|
|
646
829
|
blocks: [{ breakpoint: 1024, size: { h: 24, w: 12 }, position: { x: 0, y: 0 } }],
|
|
647
830
|
states: { isSticky: false, isDroppable: true, isScrollable: true, isWrapper: true },
|
|
648
|
-
appearance: { heightType: 'auto',
|
|
831
|
+
appearance: { heightType: 'auto', marginValue: 0, marginType: 'none', paddingType: 'none', paddingValue: 0 },
|
|
649
832
|
events: [],
|
|
650
833
|
validations: [],
|
|
651
834
|
stateBindings: [],
|
|
@@ -822,6 +1005,17 @@ export function sizeStringNote(fixed) {
|
|
|
822
1005
|
return [];
|
|
823
1006
|
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
1007
|
}
|
|
1008
|
+
/**
|
|
1009
|
+
* U-05 (2026-09-09 koşusu): platform yazılmamış bir `appearance` alanını gövdede `null` olarak
|
|
1010
|
+
* döndürüyor — `page_container` her yeni sayfada `widthType: null` ile geliyor. Yanıt katmanı
|
|
1011
|
+
* null'ları eliyor (`responses.ts` `prune`), yani ajan öyle bir alan GÖRMÜYOR; lint ise ham
|
|
1012
|
+
* gövdeye baktığı için `String(null)` = `'null'` diye bilinmeyen mod uyarısı basıyordu. Her yeni
|
|
1013
|
+
* sayfanın ilk `page_schema_patch`'inde çıkan, kullanıcının düzeltemeyeceği gürültü buydu.
|
|
1014
|
+
* `null` ve boş string = **yazılmamış**; `widthString`/`heightString` zaten böyle davranıyor.
|
|
1015
|
+
*/
|
|
1016
|
+
function sizeMode(raw) {
|
|
1017
|
+
return raw === undefined || raw === null || raw === '' ? undefined : String(raw);
|
|
1018
|
+
}
|
|
825
1019
|
/**
|
|
826
1020
|
* İki uyarı tek yürüyüşte:
|
|
827
1021
|
*
|
|
@@ -832,6 +1026,9 @@ export function sizeStringNote(fixed) {
|
|
|
832
1026
|
* - **geçersiz değer / bilinmeyen mod** (`invalidCode`): `normalizeCssSize`'dan geçmeyen
|
|
833
1027
|
* string ya da `WIDTH_TYPES` dışı `widthType` — platform 200 döner ama değer sessizce
|
|
834
1028
|
* düşer (eski backend bilinmeyen enum'u da düşürür).
|
|
1029
|
+
*
|
|
1030
|
+
* Uyarı yalnız **yazılmış** değer için çıkar: `null` ve boş string yazılmamış sayılır
|
|
1031
|
+
* (`sizeMode`). Boyut string'leri bunu zaten yapıyordu, modlar yapmıyordu.
|
|
835
1032
|
*/
|
|
836
1033
|
export function flexSizeWarnings(nodes, mismatchCode, invalidCode) {
|
|
837
1034
|
const warnings = [];
|
|
@@ -839,8 +1036,8 @@ export function flexSizeWarnings(nodes, mismatchCode, invalidCode) {
|
|
|
839
1036
|
for (const node of list) {
|
|
840
1037
|
const appearance = appearanceOf(node);
|
|
841
1038
|
const label = String(node.elementId ?? node.name ?? node.type ?? '?');
|
|
842
|
-
const widthType =
|
|
843
|
-
const heightType =
|
|
1039
|
+
const widthType = sizeMode(appearance.widthType);
|
|
1040
|
+
const heightType = sizeMode(appearance.heightType);
|
|
844
1041
|
if (widthType !== undefined && !WIDTH_TYPES.has(widthType)) {
|
|
845
1042
|
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
1043
|
}
|
|
@@ -861,6 +1058,71 @@ export function flexSizeWarnings(nodes, mismatchCode, invalidCode) {
|
|
|
861
1058
|
walk(asObjects(nodes));
|
|
862
1059
|
return warnings;
|
|
863
1060
|
}
|
|
1061
|
+
/**
|
|
1062
|
+
* İç boşluğun HİÇ çizilmediği tipler — `zeyrek-frontend` `packages/ng-apps-screen-builder/src/constants.ts`
|
|
1063
|
+
* `NO_PADDING_TYPES` (kaplama çerçeveleri) + `spacing.ts` `SHELL_ROOT_TYPES` (kabuk kökleri).
|
|
1064
|
+
*/
|
|
1065
|
+
const PADDING_NEVER_DRAWN_TYPES = new Set(['auth-layout', 'modal', 'drawer', 'layout', 'layout-shell']);
|
|
1066
|
+
/**
|
|
1067
|
+
* Düğümün `appearance.padding*` değeri runtime'da çiziliyor mu. Kural üç yüzeyde aynıdır
|
|
1068
|
+
* (`render.functions.ts` `resolveZonePadding`, `spacing.ts` `leafPaddingCss`/`getSpacingLayers`):
|
|
1069
|
+
* droppable kap dolguyu çocuk sarmalayıcısına, tipli stil modeline göçmüş yaprak kendi iç
|
|
1070
|
+
* kutusuna alır; geri kalan her düğümde (slot dahil) dolgu HİÇBİR kutuya yazılmaz ve Studio
|
|
1071
|
+
* paneli alanı göstermez.
|
|
1072
|
+
*/
|
|
1073
|
+
function drawsPadding(node) {
|
|
1074
|
+
const type = String(node.type ?? '');
|
|
1075
|
+
const states = (node.states && typeof node.states === 'object' ? node.states : {});
|
|
1076
|
+
if (PADDING_NEVER_DRAWN_TYPES.has(type) || states.isShell === true)
|
|
1077
|
+
return false;
|
|
1078
|
+
return states.isDroppable === true || Object.hasOwn(TYPED_STYLE_COMPONENTS, type);
|
|
1079
|
+
}
|
|
1080
|
+
function spacingSides(appearance, layer) {
|
|
1081
|
+
const values = appearance[`${layer}Values`];
|
|
1082
|
+
const sides = values && typeof values === 'object' && !Array.isArray(values)
|
|
1083
|
+
? ['top', 'right', 'bottom', 'left'].map(side => values[side])
|
|
1084
|
+
: [];
|
|
1085
|
+
return [appearance[`${layer}Value`], ...sides].filter(value => value !== undefined && value !== null);
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* S-9 (2026-09-15): layout slot'una `paddingType:'custom'` + `paddingValue:"24px"` yazıldı, istek
|
|
1089
|
+
* 200 döndü, slot 0px dolguyla çizildi ve sekiz sayfanın her birine sarmalayıcı kap eklendi. İki
|
|
1090
|
+
* hata üst üste: slot dolgu çizmez (`drawsPadding`), değer de sayı değil string — boşluk kanalı
|
|
1091
|
+
* sayı saklar, çizim `${value}px` birleştirir ve "24px" → `24pxpx` geçersiz CSS olur.
|
|
1092
|
+
*
|
|
1093
|
+
* - `unusedCode`: çizilmeyen düğümde sıfırdan büyük custom dolgu. Slot'un sayfa içeriğinden
|
|
1094
|
+
* uzaklığı `marginType`/`marginValue`'dur (Studio iskeleti de 8px custom margin yazar).
|
|
1095
|
+
* - `numberCode`: `padding*`/`margin*` değeri sayı değil.
|
|
1096
|
+
*
|
|
1097
|
+
* Yalnız `custom` dolguya bakılır: `normal`/`none` kayıtlı düğümlerde varsayılan olarak durur ve
|
|
1098
|
+
* panelin gizlediği alanda ajanın bilinçli bir kararı değildir.
|
|
1099
|
+
*/
|
|
1100
|
+
export function spacingLayerWarnings(nodes, unusedCode, numberCode) {
|
|
1101
|
+
const warnings = [];
|
|
1102
|
+
const walk = (list) => {
|
|
1103
|
+
for (const node of list) {
|
|
1104
|
+
const appearance = appearanceOf(node);
|
|
1105
|
+
const label = String(node.elementId ?? node.name ?? node.type ?? '?');
|
|
1106
|
+
for (const layer of ['padding', 'margin']) {
|
|
1107
|
+
const invalid = spacingSides(appearance, layer).filter(value => typeof value !== 'number');
|
|
1108
|
+
if (invalid.length > 0) {
|
|
1109
|
+
warnings.push(`${numberCode}: '${label}' düğümünde ${layer}Value/${layer}Values sayı değil (${invalid.map(value => JSON.stringify(value)).join(', ')}) — boşluk kanalı piksel SAYISI saklar ("24px" değil 24); çizim değere 'px' ekler ve string değer geçersiz CSS üretir.`);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
const hasCustomPadding = appearance.paddingType === 'custom'
|
|
1113
|
+
&& spacingSides(appearance, 'padding').some(value => value !== 0 && value !== '0');
|
|
1114
|
+
if (hasCustomPadding && !drawsPadding(node)) {
|
|
1115
|
+
const hint = node.type === 'slot'
|
|
1116
|
+
? "Slot'un sayfa içeriğinden uzaklığı dış boşluğudur: appearance.marginType:'custom' + marginValue (sayı) yaz — slot'un zemini/çerçevesi olmadığı için görsel sonuç iç boşlukla aynıdır."
|
|
1117
|
+
: "İç boşluk yalnız droppable kaplarda ve tipli stil modelindeki yapraklarda (box · text · container · statcard · icon · form) çizilir; kenardan uzaklık için marginType/marginValue, iç boşluk için sarmalayıcı container kullan.";
|
|
1118
|
+
warnings.push(`${unusedCode}: '${label}' (${String(node.type ?? '?')}) düğümüne custom iç boşluk yazıldı ama bu düğümde iç boşluk HİÇBİR kutuya çizilmez — platform 200 döner, ekran değişmez. ${hint}`);
|
|
1119
|
+
}
|
|
1120
|
+
walk(asObjects(node.children));
|
|
1121
|
+
}
|
|
1122
|
+
};
|
|
1123
|
+
walk(asObjects(nodes));
|
|
1124
|
+
return warnings;
|
|
1125
|
+
}
|
|
864
1126
|
const GRID_COLUMNS = 12;
|
|
865
1127
|
const DESKTOP_BREAKPOINT = 1024;
|
|
866
1128
|
const DEFAULT_RECT = { x: 0, y: 0, w: 12, h: 6 };
|
|
@@ -1192,6 +1454,171 @@ export function verticalGapWarnings(nodes, code, onlyParents) {
|
|
|
1192
1454
|
gapWalk('kök', asObjects(nodes), code, onlyParents, true, warnings, false);
|
|
1193
1455
|
return warnings;
|
|
1194
1456
|
}
|
|
1457
|
+
const FLOW_LAYOUTS = new Set(['flex', 'cssgrid']);
|
|
1458
|
+
function childrenOf(node) {
|
|
1459
|
+
if (!Array.isArray(node.children))
|
|
1460
|
+
node.children = [];
|
|
1461
|
+
return node.children;
|
|
1462
|
+
}
|
|
1463
|
+
function locateSlot(nodes, elementId, parent) {
|
|
1464
|
+
for (let index = 0; index < nodes.length; index++) {
|
|
1465
|
+
const node = nodes[index];
|
|
1466
|
+
if (!node || typeof node !== 'object')
|
|
1467
|
+
continue;
|
|
1468
|
+
if (node.elementId === elementId)
|
|
1469
|
+
return { siblings: nodes, parent, index };
|
|
1470
|
+
const found = locateSlot(Array.isArray(node.children) ? node.children : [], elementId, node);
|
|
1471
|
+
if (found)
|
|
1472
|
+
return found;
|
|
1473
|
+
}
|
|
1474
|
+
return undefined;
|
|
1475
|
+
}
|
|
1476
|
+
function collectDesktopPositions(nodes, into) {
|
|
1477
|
+
for (const node of nodes) {
|
|
1478
|
+
const block = desktopBlock(node);
|
|
1479
|
+
if (typeof node.elementId === 'string' && block)
|
|
1480
|
+
into.set(node.elementId, rectOf(block));
|
|
1481
|
+
collectDesktopPositions(asObjects(node.children), into);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
/**
|
|
1485
|
+
* G-10 (2026-09-15): `add_component`/`move_component`'in `index`'i dizi sırasını değiştirir ama
|
|
1486
|
+
* ÇİZİMİ değiştirmez — runtime flex (ve eski `cssgrid`) kabın çocuklarını dizi sırasıyla değil
|
|
1487
|
+
* `blocks.position` sırasıyla (önce y, sonra x) çizer (`zeyrek-frontend` `RenderComponent.svelte` →
|
|
1488
|
+
* `sortNodesByBlockPosition`). Studio flex tuvalinde sıralarken bunu kardeşlerin desktop bloklarını
|
|
1489
|
+
* yeniden yazarak çözer (`flex-designer.functions.ts` `resolveFlexOrder` + `buildFlexOrderedNodes`:
|
|
1490
|
+
* görünen sıraya yerleştir, x=0, birikimli y). Burada aynısı yapılır: akış kabında `index` GÖRÜNEN
|
|
1491
|
+
* sıradaki yerdir (eski kayıtlarda dizi sırası çizimle aynı değildir, Studio sürüklerken diziyi
|
|
1492
|
+
* değil y'yi yazar). Op'lar okunan şemanın kopyasına sırayla uygulanır; eklenen düğümün son konumu
|
|
1493
|
+
* kendi gövdesine, var olan kardeşlerinki yamanın sonuna `move_component {target, position}` olarak
|
|
1494
|
+
* yazılır. Platform position'ı düğümün İLK bloğuna yazar: ilk bloğu desktop olmayan kardeş atlanır ve
|
|
1495
|
+
* uyarılır. Izgara kapta çizim mutlak koordinattır; sona eklemeyen `index` orada PAGE-033 uyarısıdır.
|
|
1496
|
+
* `index` verilmemiş op'a dokunulmaz.
|
|
1497
|
+
*/
|
|
1498
|
+
export function flexIndexOrder(schema, ops) {
|
|
1499
|
+
const sent = asObjects(ops).map(op => JSON.parse(JSON.stringify(op)));
|
|
1500
|
+
const tree = JSON.parse(JSON.stringify(asObjects(schema)));
|
|
1501
|
+
const original = new Map();
|
|
1502
|
+
const reordered = new Set();
|
|
1503
|
+
const warnings = [];
|
|
1504
|
+
collectDesktopPositions(tree, original);
|
|
1505
|
+
for (const op of sent) {
|
|
1506
|
+
if (op.op === 'replace_prop' && op.prop === 'layout') {
|
|
1507
|
+
const target = findNodeByElementId(tree, String(op.target ?? ''));
|
|
1508
|
+
const value = op.value !== null && typeof op.value === 'object' ? op.value : { type: 'static', data: op.value };
|
|
1509
|
+
if (target)
|
|
1510
|
+
target.props = { ...(target.props ?? {}), layout: value };
|
|
1511
|
+
}
|
|
1512
|
+
if (op.op === 'remove_component') {
|
|
1513
|
+
const slot = locateSlot(tree, String(op.target ?? ''), undefined);
|
|
1514
|
+
if (slot)
|
|
1515
|
+
slot.siblings.splice(slot.index, 1);
|
|
1516
|
+
}
|
|
1517
|
+
if (op.op !== 'add_component' && op.op !== 'move_component')
|
|
1518
|
+
continue;
|
|
1519
|
+
let node;
|
|
1520
|
+
let siblings = tree;
|
|
1521
|
+
let parent;
|
|
1522
|
+
if (op.op === 'add_component') {
|
|
1523
|
+
node = op.component !== null && typeof op.component === 'object' ? JSON.parse(JSON.stringify(op.component)) : undefined;
|
|
1524
|
+
}
|
|
1525
|
+
else {
|
|
1526
|
+
const slot = locateSlot(tree, String(op.target ?? ''), undefined);
|
|
1527
|
+
if (slot) {
|
|
1528
|
+
node = slot.siblings.splice(slot.index, 1)[0];
|
|
1529
|
+
siblings = slot.siblings;
|
|
1530
|
+
parent = slot.parent;
|
|
1531
|
+
}
|
|
1532
|
+
const block = node ? desktopBlock(node) : undefined;
|
|
1533
|
+
const position = op.position;
|
|
1534
|
+
if (block && position && typeof position === 'object')
|
|
1535
|
+
block.position = { x: Number(position.x ?? 0), y: Number(position.y ?? 0) };
|
|
1536
|
+
}
|
|
1537
|
+
if (!node)
|
|
1538
|
+
continue;
|
|
1539
|
+
const requestedParent = op.parent;
|
|
1540
|
+
if (op.op === 'add_component' || (requestedParent !== undefined && requestedParent !== null)) {
|
|
1541
|
+
parent = typeof requestedParent === 'string' && requestedParent !== '' && requestedParent !== 'root'
|
|
1542
|
+
? findNodeByElementId(tree, requestedParent)
|
|
1543
|
+
: undefined;
|
|
1544
|
+
if (typeof requestedParent === 'string' && requestedParent !== '' && requestedParent !== 'root' && !parent)
|
|
1545
|
+
continue;
|
|
1546
|
+
siblings = parent ? childrenOf(parent) : tree;
|
|
1547
|
+
}
|
|
1548
|
+
const hasIndex = typeof op.index === 'number' && Number.isFinite(op.index);
|
|
1549
|
+
const insertAt = hasIndex ? Math.max(0, Math.min(op.index, siblings.length)) : siblings.length;
|
|
1550
|
+
siblings.splice(insertAt, 0, node);
|
|
1551
|
+
if (!hasIndex)
|
|
1552
|
+
continue;
|
|
1553
|
+
const elementId = String(node.elementId ?? '?');
|
|
1554
|
+
const parentLabel = String(parent?.elementId ?? 'kök');
|
|
1555
|
+
if (!parent || !FLOW_LAYOUTS.has(String(staticPropValue(parent, 'layout')))) {
|
|
1556
|
+
if (insertAt < siblings.length - 1) {
|
|
1557
|
+
warnings.push(`PAGE-033 (uyarı): '${parentLabel}' ızgara kap — '${elementId}' için verilen index:${op.index} yalnız DİZİ sırasını değiştirir, ÇİZİMİ değil: ızgarada her çocuk kendi blocks.position'ında çizilir. Sırayı değiştirmek için position.y ver (move_component {target, position: {x, y}}) ya da kabı layout:'flex' yap — flex kapta index görünen sırayı belirler ve gateway kardeş konumlarını yeniden yazar.`);
|
|
1558
|
+
}
|
|
1559
|
+
continue;
|
|
1560
|
+
}
|
|
1561
|
+
const others = siblings
|
|
1562
|
+
.filter(child => child !== node && desktopBlock(child) !== undefined)
|
|
1563
|
+
.sort((left, right) => rectOf(desktopBlock(left)).y - rectOf(desktopBlock(right)).y || rectOf(desktopBlock(left)).x - rectOf(desktopBlock(right)).x);
|
|
1564
|
+
const orderAt = Math.max(0, Math.min(op.index, others.length));
|
|
1565
|
+
let y = 0;
|
|
1566
|
+
for (const child of [...others.slice(0, orderAt), node, ...others.slice(orderAt)]) {
|
|
1567
|
+
const block = desktopBlock(child);
|
|
1568
|
+
if (!block)
|
|
1569
|
+
continue;
|
|
1570
|
+
const rect = rectOf(block);
|
|
1571
|
+
if (rect.x !== 0 || rect.y !== y) {
|
|
1572
|
+
block.position = { x: 0, y };
|
|
1573
|
+
reordered.add(String(child.elementId ?? '?'));
|
|
1574
|
+
}
|
|
1575
|
+
y += Math.max(1, rect.h);
|
|
1576
|
+
}
|
|
1577
|
+
if (op.op === 'move_component')
|
|
1578
|
+
reordered.add(elementId);
|
|
1579
|
+
}
|
|
1580
|
+
const addedBodies = new Map();
|
|
1581
|
+
const moveTargets = new Set(sent.filter(op => op.op === 'move_component').map(op => String(op.target ?? '')));
|
|
1582
|
+
const trailing = [];
|
|
1583
|
+
const moved = [];
|
|
1584
|
+
for (const op of sent) {
|
|
1585
|
+
const component = op.component;
|
|
1586
|
+
if (op.op === 'add_component' && component && typeof component.elementId === 'string')
|
|
1587
|
+
addedBodies.set(component.elementId, component);
|
|
1588
|
+
}
|
|
1589
|
+
for (const elementId of reordered) {
|
|
1590
|
+
const node = findNodeByElementId(tree, elementId);
|
|
1591
|
+
const block = node ? desktopBlock(node) : undefined;
|
|
1592
|
+
if (!node || !block)
|
|
1593
|
+
continue;
|
|
1594
|
+
const final = rectOf(block);
|
|
1595
|
+
const body = addedBodies.get(elementId);
|
|
1596
|
+
const bodyBlock = body ? desktopBlock(body) : undefined;
|
|
1597
|
+
if (bodyBlock && !moveTargets.has(elementId)) {
|
|
1598
|
+
const before = rectOf(bodyBlock);
|
|
1599
|
+
if (before.x !== final.x || before.y !== final.y) {
|
|
1600
|
+
bodyBlock.position = { x: final.x, y: final.y };
|
|
1601
|
+
moved.push(`${elementId} y:${before.y}→${final.y}`);
|
|
1602
|
+
}
|
|
1603
|
+
continue;
|
|
1604
|
+
}
|
|
1605
|
+
const before = original.get(elementId);
|
|
1606
|
+
if (!body && !moveTargets.has(elementId) && before && before.x === final.x && before.y === final.y)
|
|
1607
|
+
continue;
|
|
1608
|
+
if (Number(asObjects(node.blocks)[0]?.breakpoint) !== DESKTOP_BREAKPOINT) {
|
|
1609
|
+
warnings.push(`PAGE-033 (uyarı): '${elementId}' flex sırasına göre y:${final.y}'e taşınmalıydı ama ilk bloğu desktop (1024) değil — platform move_component'in position'ını İLK bloğa yazar, yanlış kırılımı bozmamak için konum yazılmadı. Düğümü page_update(schema) ile düzeltin.`);
|
|
1610
|
+
continue;
|
|
1611
|
+
}
|
|
1612
|
+
trailing.push({ op: 'move_component', target: elementId, position: { x: final.x, y: final.y } });
|
|
1613
|
+
moved.push(`${elementId} y:${before?.y ?? '?'}→${final.y}`);
|
|
1614
|
+
}
|
|
1615
|
+
return { ops: [...sent, ...trailing], moved, warnings };
|
|
1616
|
+
}
|
|
1617
|
+
export function flexIndexOrderNote(moved) {
|
|
1618
|
+
if (moved.length === 0)
|
|
1619
|
+
return [];
|
|
1620
|
+
return [`flex kapta index görünen sıraya çevrildi, kardeş konumları yeniden yazıldı (${moved.join(', ')}): runtime akış kabının çocuklarını dizi sırasıyla değil blocks.position sırasıyla (önce y, sonra x) çizer; Studio flex tuvali de sıralarken konumları böyle yazar (x=0, birikimli y)`];
|
|
1621
|
+
}
|
|
1195
1622
|
/**
|
|
1196
1623
|
* View gövdesinde alan→tablo bağı **alias** üzerinden kurulur (`ViewSourceMapper.Map`:
|
|
1197
1624
|
* `tablesByAlias.GetValueOrDefault(input.ViewEntityAlias) ?? tables.FirstOrDefault()`) ama
|
|
@@ -1624,7 +2051,7 @@ export function staleSetterWarnings(flows, ops, schema) {
|
|
|
1624
2051
|
seen.add(key);
|
|
1625
2052
|
const stepName = String(step.name ?? step.stepId ?? 'adsız adım');
|
|
1626
2053
|
warnings.push(readded.has(element)
|
|
1627
|
-
? `FLOW-018 (uyarı): '${element}' bu yamada silinip yeniden
|
|
2054
|
+
? `FLOW-018 (uyarı): '${element}' bu yamada silinip yeniden kuruluyor ve '${flowName}' akışının '${stepName}' setter adımı ona yazıyor. Setter hedefi elementId değil komponentin iç kimliğidir; platform yeni kimlik üretirse iteratee'nin element'i SİLİNİR — akış koşar, istek 200 döner ve komponente HİÇBİR ŞEY yazılmaz. Gateway yazmadan sonra akışları yeniden okur: bağ koptuysa flowTargetsLost onarım op'larını verir. Prop tipini değiştirmek için düğümü yeniden kurmayın — replace_prop sarmalı (static/code/translation) ya da set_prop_type yeter.`
|
|
1628
2055
|
: `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
2056
|
affected.push(`${flowName} · ${stepName} → ${element}`);
|
|
1630
2057
|
}
|
|
@@ -1638,6 +2065,66 @@ export function staleSetterWarnings(flows, ops, schema) {
|
|
|
1638
2065
|
}
|
|
1639
2066
|
return warnings;
|
|
1640
2067
|
}
|
|
2068
|
+
/**
|
|
2069
|
+
* FLOW-018'in yazma SONRASI ayağı (G-04). Uyarı yamadan önce "kopabilir" der ama sonucu bilemez:
|
|
2070
|
+
* backend aynı yamada silinip aynı elementId ile eklenen düğüme eski kimliğini geri verir; o düzeltme
|
|
2071
|
+
* deploy edilmemiş bir ortamda ise `step_iterates.DesignComponentId` FK'sı `SetNull` ile boşalır ve
|
|
2072
|
+
* `flow_get` iteratee'yi `element`'siz döndürür (09-15 koşusu: pano açıldı, alt başlık ve tarih
|
|
2073
|
+
* aralığı boş kaldı). Yamadan önce ve sonra okunan akışlar karşılaştırılır; YENİDEN EKLENEN bir
|
|
2074
|
+
* düğüme yazıp `element`'ini kaybeden her iteratee için elle uygulanacak `flow_patch` op'u üretilir.
|
|
2075
|
+
* Yalnız silinen düğümün kaybı burada sayılmaz: hedef gerçekten yoktur, onu FLOW-018 zaten söyler.
|
|
2076
|
+
*/
|
|
2077
|
+
export function lostSetterTargets(before, after, ops) {
|
|
2078
|
+
const readded = new Set();
|
|
2079
|
+
for (const op of asObjects(ops).filter(op => op.op === 'add_component'))
|
|
2080
|
+
collectElementIds(op.component, readded);
|
|
2081
|
+
const afterById = new Map(asObjects(after).map(flow => [String(flow.id ?? ''), flow]));
|
|
2082
|
+
const lost = [];
|
|
2083
|
+
for (const flow of asObjects(before)) {
|
|
2084
|
+
const saved = afterById.get(String(flow.id ?? ''));
|
|
2085
|
+
if (!saved)
|
|
2086
|
+
continue;
|
|
2087
|
+
const flowName = String(flow.name ?? flow.slug ?? flow.id ?? 'adsız akış');
|
|
2088
|
+
for (const collection of ['steps', 'errorSteps']) {
|
|
2089
|
+
const savedSteps = new Map(asObjects(saved[collection]).map(step => [String(step.stepId ?? ''), step]));
|
|
2090
|
+
for (const step of asObjects(flow[collection]).filter(step => step.type === 'setter')) {
|
|
2091
|
+
const savedStep = savedSteps.get(String(step.stepId ?? ''));
|
|
2092
|
+
if (!savedStep)
|
|
2093
|
+
continue;
|
|
2094
|
+
const savedIteratees = asObjects((savedStep.args ?? {}).iteratees);
|
|
2095
|
+
asObjects((step.args ?? {}).iteratees).forEach((iteratee, index) => {
|
|
2096
|
+
const element = typeof iteratee.element === 'string' ? iteratee.element : '';
|
|
2097
|
+
const savedIteratee = savedIteratees.find(entry => iteratee.id !== undefined && entry.id === iteratee.id) ?? savedIteratees[index];
|
|
2098
|
+
if (!readded.has(element) || !savedIteratee || (typeof savedIteratee.element === 'string' && savedIteratee.element.length > 0))
|
|
2099
|
+
return;
|
|
2100
|
+
const savedIndex = savedIteratees.indexOf(savedIteratee);
|
|
2101
|
+
lost.push(`FLOW-018 (yazma sonrası): '${flowName}' · '${String(step.name ?? step.stepId)}' iteratee ${savedIndex} hedefini kaybetti ('${element}') — akış koşar ama komponente yazmaz. Onarım: flow_patch operations: [{ op: 'add', path: '/${collection}[stepId=${String(step.stepId)}]/args/iteratees/${savedIndex}/element', value: '${element}' }]`);
|
|
2102
|
+
});
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
return lost;
|
|
2107
|
+
}
|
|
2108
|
+
/**
|
|
2109
|
+
* FLOW-017 yazarken element'siz setter'ı reddeder; ama hedef SONRADAN silinince platform alanı
|
|
2110
|
+
* boşaltır ve akış geçerli görünerek koşmaya devam eder (G-04). `flow_get` bu durumu okurken söyler —
|
|
2111
|
+
* o gövdeyle `flow_update` zaten FLOW-017'ye takılır, sebebini önceden bilmek gerekir.
|
|
2112
|
+
*/
|
|
2113
|
+
export function targetlessSetterWarnings(flow) {
|
|
2114
|
+
const record = (flow && typeof flow === 'object' && !Array.isArray(flow) ? flow : {});
|
|
2115
|
+
const body = (record.data && typeof record.data === 'object' && !Array.isArray(record.data) ? record.data : record);
|
|
2116
|
+
const warnings = [];
|
|
2117
|
+
for (const collection of ['steps', 'errorSteps']) {
|
|
2118
|
+
for (const step of asObjects(body[collection]).filter(step => step.type === 'setter')) {
|
|
2119
|
+
asObjects((step.args ?? {}).iteratees).forEach((iteratee, index) => {
|
|
2120
|
+
if (typeof iteratee.element === 'string' && iteratee.element.trim().length > 0)
|
|
2121
|
+
return;
|
|
2122
|
+
warnings.push(`FLOW-017 (okuma): '${String(step.name ?? step.stepId)}' setter adımının iteratee ${index} hedefsiz — element yok, adım koşar ama HİÇBİR ŞEYE yazmaz. Hedef komponent silinmiş ya da yeniden kurulmuş olabilir (FLOW-018). page_schema_get ile elementId'yi bulup flow_patch ile yazın: { op: 'add', path: '/${collection}[stepId=${String(step.stepId)}]/args/iteratees/${index}/element', value: '<elementId>' }`);
|
|
2123
|
+
});
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
return warnings;
|
|
2127
|
+
}
|
|
1641
2128
|
/**
|
|
1642
2129
|
* FLOW-017'nin kör noktası: o kural yalnız BOŞ `element`/`property` görür. 2026-09-09 koşusunda
|
|
1643
2130
|
* (S-14) setter dolu ama HENÜZ VAR OLMAYAN bir elementId'ye bağlandı — platform alanı doğrulamaz,
|