@appaflytech/zeyrek-ai-mcp 0.0.5 → 0.0.7
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 +9 -1
- package/README.md +14 -38
- package/dist/config.js +36 -3
- package/dist/knowledge/backendConstraints.js +982 -0
- package/dist/knowledge/componentCatalog.js +549 -92
- package/dist/knowledge/componentEvents.js +120 -25
- package/dist/knowledge/coverage.js +43 -0
- package/dist/knowledge/flowGuards.js +30 -0
- package/dist/knowledge/functionGuards.js +134 -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 +500 -16
- package/dist/knowledge/outlines.js +167 -12
- package/dist/knowledge/policy.js +14 -5
- package/dist/knowledge/propTypes.js +80 -0
- package/dist/knowledge/rules.js +961 -44
- package/dist/knowledge/schemaGuard.js +199 -0
- package/dist/knowledge/styleResolve.js +63 -4
- package/dist/observability/trace.js +27 -3
- package/dist/platform/csv.js +123 -0
- package/dist/platform/errors.js +107 -6
- package/dist/platform/http.js +34 -4
- package/dist/platform/patch.js +27 -0
- package/dist/platform/refs.js +104 -0
- package/dist/platform/responses.js +25 -6
- package/dist/platform/screenshot.js +544 -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/flow-call-function.json +66 -0
- package/dist/resources/examples/function-total.json +70 -0
- package/dist/resources/examples/layout-app-shell.json +1 -1
- package/dist/resources/examples/microfunction-node-sum.json +56 -0
- package/dist/resources/examples/microservice-table-list.json +2 -4
- package/dist/resources/examples/page-dynamic-form.json +3 -3
- package/dist/resources/examples/page-flex-sections.json +99 -0
- package/dist/resources/examples/page-sign-in.json +4 -3
- package/dist/resources/guides/component.md +81 -8
- package/dist/resources/guides/entity.md +712 -20
- package/dist/resources/guides/expressions.md +47 -2
- package/dist/resources/guides/external-component.md +9 -0
- package/dist/resources/guides/functions.md +262 -0
- package/dist/resources/guides/getting-started.md +82 -7
- package/dist/resources/guides/layout.md +53 -10
- package/dist/resources/guides/merge.md +231 -0
- package/dist/resources/guides/microfunction.md +166 -0
- package/dist/resources/guides/microservice.md +287 -15
- package/dist/resources/guides/middleware.md +89 -1
- package/dist/resources/guides/modify-existing.md +23 -0
- package/dist/resources/guides/page.md +402 -49
- package/dist/resources/guides/translation.md +150 -8
- package/dist/resources/guides/view.md +16 -1
- package/dist/resources/guides/workflow.md +199 -28
- 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/AnalyzeImportQuery.json +51 -0
- package/dist/resources/schemas/ApplyMergePlanCommand.json +19 -0
- package/dist/resources/schemas/CreateBulkRowCommand.json +36 -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/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 +145 -3
- 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 +106 -14
- 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 +6 -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 +135 -1
- package/dist/resources/schemas/PreviewQueryCommand.json +5 -2
- package/dist/resources/schemas/PreviewWorkflowCommand.json +45 -9
- 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 +34 -3
- package/dist/resources/schemas/UpdateBulkRowCommand.json +36 -0
- package/dist/resources/schemas/UpdateDatasourceCommand.json +15 -6
- package/dist/resources/schemas/UpdateEntityCommand.json +52 -11
- 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 +147 -4
- 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 +106 -14
- package/dist/server.js +17 -1
- package/dist/tools/access.js +3 -3
- package/dist/tools/activities.js +40 -0
- package/dist/tools/applications.js +1 -1
- package/dist/tools/automations.js +76 -34
- package/dist/tools/common.js +55 -0
- package/dist/tools/components.js +16 -7
- package/dist/tools/datasources.js +83 -21
- package/dist/tools/dependencies.js +1 -1
- package/dist/tools/deployments.js +34 -2
- package/dist/tools/dtos.js +25 -12
- package/dist/tools/entities.js +113 -28
- package/dist/tools/environments.js +52 -0
- package/dist/tools/externalComponents.js +9 -3
- package/dist/tools/files.js +57 -42
- package/dist/tools/flows.js +205 -13
- package/dist/tools/folders.js +7 -2
- package/dist/tools/functions.js +331 -0
- package/dist/tools/globalFunctions.js +63 -0
- package/dist/tools/importSchemas.js +79 -0
- package/dist/tools/imports.js +241 -0
- package/dist/tools/layouts.js +103 -29
- package/dist/tools/merges.js +113 -0
- package/dist/tools/microfunctions.js +155 -0
- package/dist/tools/microservices.js +270 -40
- package/dist/tools/organizations.js +5 -6
- package/dist/tools/pages.js +421 -104
- package/dist/tools/platformInfo.js +155 -11
- package/dist/tools/projects.js +183 -33
- package/dist/tools/queries.js +32 -10
- package/dist/tools/revisions.js +94 -0
- package/dist/tools/rows.js +484 -50
- package/dist/tools/screenWrite.js +68 -0
- package/dist/tools/screens.js +28 -0
- package/dist/tools/templates.js +32 -5
- package/dist/tools/translations.js +24 -8
- package/dist/tools/views.js +20 -10
- package/dist/tools/workflows.js +56 -21
- package/package.json +7 -2
|
@@ -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' };
|
|
@@ -79,6 +80,33 @@ function fillRootAlias(table, fixes) {
|
|
|
79
80
|
table.alias = alias;
|
|
80
81
|
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`);
|
|
81
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* S-06 (2026-09-09 koşusu): yazma alanlarında `alias` sözleşmede YOKTUR. Okuma/dönüş seçimleri
|
|
85
|
+
* `TableSelect` ile modellenir ve `Alias`/`Path`/`FieldSlug` taşır; yazılacak alanlar ise
|
|
86
|
+
* `TableField` ile — o sınıfta yalnız `FieldId`, `Source`, `Value`, `FolderId`, `Order`,
|
|
87
|
+
* `WriteOn` vardır (`MicroserviceTableGetParameter.cs:326`; aynı tip `WorkflowDto.cs:252`
|
|
88
|
+
* table step kuralında da kullanılıyor). Gönderilen `alias` model bağlamada düşer, yanıt onu
|
|
89
|
+
* geri getirmez ve `silentlyDropped` her yazma çağrısında sahte bir kayıp satırı basardı.
|
|
90
|
+
* Alan gövdeden **istek gitmeden** çıkarılır ve `gatewayNormalized` ile bildirilir — kayıp değil,
|
|
91
|
+
* sözleşme dışı bir alan.
|
|
92
|
+
*
|
|
93
|
+
* Sıra önemlidir: `fillRootAlias` kök alias'ı türetirken `fields[].alias`'ı da aday sayar, bu
|
|
94
|
+
* yüzden ayıklama ondan SONRA çalışır.
|
|
95
|
+
*/
|
|
96
|
+
function dropWriteFieldAliases(table, fixes) {
|
|
97
|
+
const dropped = [];
|
|
98
|
+
for (const field of asObjects(table.fields)) {
|
|
99
|
+
const alias = String(field.alias ?? '').trim();
|
|
100
|
+
if (!Object.hasOwn(field, 'alias'))
|
|
101
|
+
continue;
|
|
102
|
+
delete field.alias;
|
|
103
|
+
if (alias)
|
|
104
|
+
dropped.push(`${alias}.${String(field.fieldSlug ?? field.fieldId ?? '?')}`);
|
|
105
|
+
}
|
|
106
|
+
if (dropped.length === 0)
|
|
107
|
+
return;
|
|
108
|
+
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)`);
|
|
109
|
+
}
|
|
82
110
|
/**
|
|
83
111
|
* Platform sort/limit'i null bırakır ve path'i türetmez; Studio ikisini de guard'sız okur.
|
|
84
112
|
* Eksik olanları doldurur, dolu olanlara DOKUNMAZ (yanlış değerleri MS-007 block'u yakalar).
|
|
@@ -103,6 +131,7 @@ export function normalizeTableParameters(parameters, fallbackEntityId) {
|
|
|
103
131
|
fillJoinPaths(rootEntityId, table.joins, [], aliasChains, fixes);
|
|
104
132
|
fillSelectionPaths(table, aliasChains, fixes);
|
|
105
133
|
}
|
|
134
|
+
dropWriteFieldAliases(table, fixes);
|
|
106
135
|
return fixes;
|
|
107
136
|
}
|
|
108
137
|
export function normalizeMicroserviceDefinition(definition) {
|
|
@@ -194,9 +223,79 @@ export function normalizeWorkflowDefinition(definition) {
|
|
|
194
223
|
* '<adım adı>.data'dır; verilmemiş isRaw'ı buna eşitliyoruz ki editörde bir kez açılan akış
|
|
195
224
|
* sessizce başka bir şeye dönüşmesin. Kalan tutarsızlığı FLOW-009 yakalar.
|
|
196
225
|
*/
|
|
197
|
-
|
|
226
|
+
/**
|
|
227
|
+
* Adım tipi sözlüğü backend'in, KOŞAN tipler barındırıcının: motor yedi tip register eder, web
|
|
228
|
+
* `Runtime.svelte` altı, mobil `ScreenRuntime.tsx` beş tip ekler ve aynı sayfa kaydı ikisinde de
|
|
229
|
+
* koşar. Register edilmemiş bir tipe gelen akış sessizce ölür (`flow-engine.ts` `process()`
|
|
230
|
+
* callback bulamazsa next/completed/stopped hiç çağrılmaz), üstelik `run()` promise'i
|
|
231
|
+
* çözülmediği için `__ngAppsFlowActivity.running` sıfıra inmez. İki hâl tamamen türetilebilir
|
|
232
|
+
* olduğu için lint değil NORMALİZASYON — kalanı (`confirm`/`download` mobilde yok) yalnız
|
|
233
|
+
* uygulama tarafında kapanabilir, `FLOW-023` onları uyarır.
|
|
234
|
+
*/
|
|
235
|
+
function normalizeFlowHostGaps(steps, list) {
|
|
198
236
|
const fixes = [];
|
|
199
|
-
|
|
237
|
+
const label = (step) => String(step.stepId ?? step.name ?? '?');
|
|
238
|
+
// 'end' adımını hiçbir barındırıcı register etmez ve Studio yeni akışı YALNIZ bir start adımıyla
|
|
239
|
+
// tohumlar (`event-actions.context.ts` initFlow) — yani End düğümü hiçbir yerde gerekmiyor.
|
|
240
|
+
// Ona işaret eden dal `nextStepId: null` olur (motor completed/stopped'ı düzgün çağırır) ve ölü
|
|
241
|
+
// adım gövdeden çıkar; bırakılsaydı FLOW-005 onu haklı olarak erişilemez adım diye bloklardı.
|
|
242
|
+
const endStepIds = new Set(list
|
|
243
|
+
.filter(step => step.type === 'end')
|
|
244
|
+
.map(step => String(step.stepId ?? ''))
|
|
245
|
+
.filter(id => id.length > 0));
|
|
246
|
+
for (const step of list) {
|
|
247
|
+
// navigate ile redirect'in argümanı AYNI ({ path, pageId, query, targetBlank }) ama navigate'i
|
|
248
|
+
// yalnız mobil register eder; redirect ise iki barındırıcıda da koşar (mobilde aynı navigate
|
|
249
|
+
// uygulamasına eşlenir), yani redirect kesin üstün.
|
|
250
|
+
if (step.type === 'navigate') {
|
|
251
|
+
step.type = 'redirect';
|
|
252
|
+
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`);
|
|
253
|
+
}
|
|
254
|
+
if (endStepIds.size === 0)
|
|
255
|
+
continue;
|
|
256
|
+
const branches = step.branches;
|
|
257
|
+
if (Array.isArray(branches)) {
|
|
258
|
+
for (const branch of asObjects(branches)) {
|
|
259
|
+
if (!endStepIds.has(String(branch.nextStepId ?? '')))
|
|
260
|
+
continue;
|
|
261
|
+
branch.nextStepId = null;
|
|
262
|
+
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`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
else if (branches && typeof branches === 'object') {
|
|
266
|
+
const dictionary = branches;
|
|
267
|
+
for (const name of Object.keys(dictionary)) {
|
|
268
|
+
if (!endStepIds.has(String(dictionary[name] ?? '')))
|
|
269
|
+
continue;
|
|
270
|
+
dictionary[name] = null;
|
|
271
|
+
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`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (Array.isArray(steps) && endStepIds.size > 0) {
|
|
276
|
+
for (let index = steps.length - 1; index >= 0; index--) {
|
|
277
|
+
const step = steps[index];
|
|
278
|
+
if (!step || typeof step !== 'object' || step.type !== 'end')
|
|
279
|
+
continue;
|
|
280
|
+
steps.splice(index, 1);
|
|
281
|
+
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`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return fixes;
|
|
285
|
+
}
|
|
286
|
+
export function normalizeFlowSteps(steps) {
|
|
287
|
+
const list = asObjects(steps);
|
|
288
|
+
const fixes = normalizeFlowHostGaps(steps, list);
|
|
289
|
+
for (const step of list) {
|
|
290
|
+
if (step.type === 'function') {
|
|
291
|
+
const args = (step.args ?? {});
|
|
292
|
+
if (!Array.isArray(args.parameters)) {
|
|
293
|
+
args.parameters = [];
|
|
294
|
+
step.args = args;
|
|
295
|
+
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)`);
|
|
296
|
+
}
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
200
299
|
if (step.type !== 'microservice')
|
|
201
300
|
continue;
|
|
202
301
|
const args = (step.args ?? {});
|
|
@@ -223,7 +322,10 @@ const REQUIRED_COLLECTIONS = ['events', 'validations', 'stateBindings', 'childre
|
|
|
223
322
|
* girmez) — doğrulayıcı "valid" der, yazma patlar. Tamamen türetilebilir olduğu için lint
|
|
224
323
|
* değil normalizasyon.
|
|
225
324
|
*/
|
|
226
|
-
|
|
325
|
+
// PropValueType (DesignComponentProperty.cs:421) — 'translation' listeden düşünce çeviriye bağlı
|
|
326
|
+
// prop'un KENDİSİ veri sanılıp static Prop'a sarılıyordu: istek 200 dönüyor, prop bozuk kuruluyordu
|
|
327
|
+
// (09-04 Y-22). Enum burada eksiksiz tutulur.
|
|
328
|
+
const PROP_VALUE_TYPES = new Set(['static', 'file', 'flow', 'widget', 'element', 'page', 'code', 'translation']);
|
|
227
329
|
function isPropShaped(value) {
|
|
228
330
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value) && PROP_VALUE_TYPES.has(String(value.type));
|
|
229
331
|
}
|
|
@@ -387,6 +489,52 @@ export function schemaOpValidationErrors(ops) {
|
|
|
387
489
|
}
|
|
388
490
|
return errors;
|
|
389
491
|
}
|
|
492
|
+
/**
|
|
493
|
+
* F-06 — `style` prop'una kebab-case CSS anahtarı yazmak sessizce etkisiz kalıyor: platform
|
|
494
|
+
* değeri kabul ediyor, `page_schema_get` aynen geri veriyor ve runtime hiç uygulamıyor. Tek işaret
|
|
495
|
+
* tarayıcı konsolundaki tek satır ("Using kebab-case for css properties in objects is not
|
|
496
|
+
* supported. Did you mean minHeight?") ve `page_screenshot` konsolu döndürmediği için yalnız MCP
|
|
497
|
+
* ile çalışan bir ajan bunu HİÇ göremiyor. 09-05 koşusunun en pahalı yanlış teşhisi buydu (~15 dk)
|
|
498
|
+
* ve yanlış teşhis düğümleri silip yeniden kurmaya, o da 20 setter'ın kopmasına yol açtı.
|
|
499
|
+
*
|
|
500
|
+
* Seçici anahtarlara (`&__body`, `:hover`, `--token`) DOKUNULMAZ; yalnız `a-b` biçimli düz CSS
|
|
501
|
+
* özellik adları camelCase'e çevrilir ve çevrim `gatewayNormalized` ile bildirilir.
|
|
502
|
+
*/
|
|
503
|
+
const styleKeyPattern = /^[a-z][a-z0-9]*(-[a-z0-9]+)+$/;
|
|
504
|
+
export function isStylePropName(name) {
|
|
505
|
+
return name === 'style' || /Style$/.test(name);
|
|
506
|
+
}
|
|
507
|
+
export function normalizeStyleKeys(value, label, fixed) {
|
|
508
|
+
if (Array.isArray(value)) {
|
|
509
|
+
for (const entry of value)
|
|
510
|
+
normalizeStyleKeys(entry, label, fixed);
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
if (!value || typeof value !== 'object')
|
|
514
|
+
return;
|
|
515
|
+
const record = value;
|
|
516
|
+
for (const key of Object.keys(record)) {
|
|
517
|
+
normalizeStyleKeys(record[key], label, fixed);
|
|
518
|
+
if (!styleKeyPattern.test(key))
|
|
519
|
+
continue;
|
|
520
|
+
const camel = key.replace(/-([a-z0-9])/g, (_, character) => character.toUpperCase());
|
|
521
|
+
if (Object.hasOwn(record, camel)) {
|
|
522
|
+
delete record[key];
|
|
523
|
+
}
|
|
524
|
+
else {
|
|
525
|
+
record[camel] = record[key];
|
|
526
|
+
delete record[key];
|
|
527
|
+
}
|
|
528
|
+
fixed.push(`${label}: '${key}' → '${camel}'`);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
export function styleKeyNote(fixed) {
|
|
532
|
+
if (fixed.length === 0)
|
|
533
|
+
return [];
|
|
534
|
+
return [`stil anahtarları camelCase'e çevrildi (${fixed.join(' · ')}) — runtime kebab-case anahtarı OKUMAZ, `
|
|
535
|
+
+ 'değer kaydedilir ama hiç uygulanmaz ve tek işareti tarayıcı konsoludur (F-06). '
|
|
536
|
+
+ "Seçici anahtarlara ('&__body', ':hover', '--token') dokunulmadı."];
|
|
537
|
+
}
|
|
390
538
|
export function normalizeComponentDefaults(nodes) {
|
|
391
539
|
const fixed = [];
|
|
392
540
|
for (const node of asObjects(nodes)) {
|
|
@@ -402,7 +550,14 @@ export function normalizeComponentDefaults(nodes) {
|
|
|
402
550
|
missing.push(key);
|
|
403
551
|
}
|
|
404
552
|
if (node.props && typeof node.props === 'object' && !Array.isArray(node.props)) {
|
|
405
|
-
const
|
|
553
|
+
const props = node.props;
|
|
554
|
+
const styleKeys = [];
|
|
555
|
+
for (const name of Object.keys(props).filter(isStylePropName)) {
|
|
556
|
+
normalizeStyleKeys(props[name], `${String(node.elementId ?? node.name ?? '?')}.${name}`, styleKeys);
|
|
557
|
+
}
|
|
558
|
+
if (styleKeys.length > 0)
|
|
559
|
+
missing.push(`stil anahtarları camelCase'e çevrildi: ${styleKeys.join(' · ')}`);
|
|
560
|
+
const { wrapped, deepened } = wrapNodeProps(props);
|
|
406
561
|
if (wrapped.length > 0)
|
|
407
562
|
missing.push(`prop'lar Prop sarmalına alındı: ${wrapped.join('+')}`);
|
|
408
563
|
if (deepened.length > 0)
|
|
@@ -459,7 +614,9 @@ function normalizeReplacePropValue(op) {
|
|
|
459
614
|
op.expression = expression;
|
|
460
615
|
return [`${label}: replace_prop code prop'u 'expression' alanına taşındı — kod prop'u value ile değil expression ile verilir`];
|
|
461
616
|
}
|
|
462
|
-
|
|
617
|
+
// Applier her value'yu Static (ifade verilmişse Code) Prop'a çevirir; diğer tipler bu op'la
|
|
618
|
+
// ifade EDİLEMEZ. Sessiz bozuk kayıt yerine çağrı burada durur (09-04 Y-22).
|
|
619
|
+
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
620
|
}
|
|
464
621
|
export function normalizeSchemaOps(ops) {
|
|
465
622
|
const components = [];
|
|
@@ -467,6 +624,9 @@ export function normalizeSchemaOps(ops) {
|
|
|
467
624
|
const validations = [];
|
|
468
625
|
for (const op of asObjects(ops)) {
|
|
469
626
|
if (op.op === 'replace_prop') {
|
|
627
|
+
if (isStylePropName(String(op.prop ?? ''))) {
|
|
628
|
+
normalizeStyleKeys(op.value, `'${String(op.target ?? '?')}.${String(op.prop)}'`, props);
|
|
629
|
+
}
|
|
470
630
|
props.push(...normalizeReplacePropValue(op));
|
|
471
631
|
continue;
|
|
472
632
|
}
|
|
@@ -501,6 +661,8 @@ export const PAGE_CONTAINER_ELEMENT_ID = 'page_container';
|
|
|
501
661
|
* `CreatePageWhenApplicationCreatedEventHandler` + `SeedData`). Renderer varsayılanı
|
|
502
662
|
* BİLMEZ — `isFlexLayout` yalnız düğümün kendi prop'una bakar — bu yüzden gateway'in kurduğu
|
|
503
663
|
* kapsayıcı da seti açıkça taşır; yoksa Studio'dan açılan sayfa flex, MCP'den açılan ızgara olurdu.
|
|
664
|
+
* 2026-09-01: `wrap` varsayılanı `true` oldu (`DEFAULT_CONTAINER_LAYOUT_PROPS`) ve sete eklendi —
|
|
665
|
+
* `row` + `wrap:true` altında w:12 çocuklar alt satıra sararak dikey akar (eski tek-satır sıkışması bitti).
|
|
504
666
|
*/
|
|
505
667
|
export function pageContainerNode() {
|
|
506
668
|
return {
|
|
@@ -515,7 +677,8 @@ export function pageContainerNode() {
|
|
|
515
677
|
horizontalAlign: { type: 'static', data: 'left' },
|
|
516
678
|
verticalAlign: { type: 'static', data: 'top' },
|
|
517
679
|
size: { type: 'static', data: 'shrink' },
|
|
518
|
-
gap: { type: 'static', data: 'medium' }
|
|
680
|
+
gap: { type: 'static', data: 'medium' },
|
|
681
|
+
wrap: { type: 'static', data: true }
|
|
519
682
|
},
|
|
520
683
|
blocks: [{ breakpoint: 1024, size: { h: 24, w: 12 }, position: { x: 0, y: 0 } }],
|
|
521
684
|
states: { isSticky: false, isDroppable: true, isScrollable: true, isWrapper: true },
|
|
@@ -632,6 +795,123 @@ export function heightTypeNote(stamped) {
|
|
|
632
795
|
: stamped.join(', ');
|
|
633
796
|
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
797
|
}
|
|
798
|
+
/**
|
|
799
|
+
* Flex çocuğunun boyut modeli (2026-09-01, `AddSizeStringsToComponentAppearance`):
|
|
800
|
+
* `appearance.widthType` beş değer alır — `cover`/`contain` medya sığdırma, `auto`/`fixed`/`fill`
|
|
801
|
+
* flex-çocuk boyut modu. `fixed` iken serbest CSS uzunluğu `widthString`/`heightString`'den okunur;
|
|
802
|
+
* `fill`/`auto` string taşımaz (Studio paneli mod değişiminde string'i SİLER —
|
|
803
|
+
* `appearance-size.functions.ts` `buildFlexWidthMode`). Öncelik: fill > fixed(+string) > auto >
|
|
804
|
+
* eski 12 kolon span (`widthType` hiç yoksa). Eski backend bilinmeyen `widthType`'ı sessizce
|
|
805
|
+
* düşürür (`ComponentWidthType` enum'una `Fixed`/`Fill` 2026-09-01'de eklendi).
|
|
806
|
+
*/
|
|
807
|
+
const WIDTH_TYPES = new Set(['cover', 'contain', 'auto', 'fixed', 'fill']);
|
|
808
|
+
/**
|
|
809
|
+
* `packages/ng-apps-screen-builder/src/flex-layout.ts` `normalizeCssSize`'ın birebir portu:
|
|
810
|
+
* çıplak sayı → px; birimler px/%/vw/vh/vmin/vmax/rem/em/ch; `calc(...)` aynen geçer; gerisi
|
|
811
|
+
* (negatif, `pt`, `fr`, `auto`…) undefined — platform kaydeder ama panel/runtime değeri düşürür.
|
|
812
|
+
*/
|
|
813
|
+
export function normalizeCssSize(raw) {
|
|
814
|
+
if (typeof raw === 'number')
|
|
815
|
+
return Number.isFinite(raw) && raw > 0 ? `${raw}px` : undefined;
|
|
816
|
+
const value = String(raw ?? '').trim();
|
|
817
|
+
if (!value)
|
|
818
|
+
return undefined;
|
|
819
|
+
if (/^\d+(\.\d+)?$/.test(value))
|
|
820
|
+
return `${value}px`;
|
|
821
|
+
if (/^\d+(\.\d+)?(px|%|vw|vh|vmin|vmax|rem|em|ch)$/.test(value))
|
|
822
|
+
return value;
|
|
823
|
+
if (/^calc\(.+\)$/.test(value))
|
|
824
|
+
return value;
|
|
825
|
+
return undefined;
|
|
826
|
+
}
|
|
827
|
+
function appearanceOf(node) {
|
|
828
|
+
return (node.appearance && typeof node.appearance === 'object' && !Array.isArray(node.appearance)
|
|
829
|
+
? node.appearance
|
|
830
|
+
: {});
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Studio paritesi: panel çıplak sayıyı `px`e çevirerek yazar (`buildFlexSizeValue`), gateway de
|
|
834
|
+
* aynısını yapar ki iki yoldan kurulan şema aynı görünsün. Yalnız türetilebilir olan çevrilir;
|
|
835
|
+
* geçersiz değerler burada DEĞİL `flexSizeWarnings`'te raporlanır (uyarı, silme değil — platform
|
|
836
|
+
* da silmiyor, yalnız okumuyor).
|
|
837
|
+
*/
|
|
838
|
+
export function normalizeSizeStrings(nodes) {
|
|
839
|
+
const fixed = [];
|
|
840
|
+
for (const node of asObjects(nodes)) {
|
|
841
|
+
const appearance = appearanceOf(node);
|
|
842
|
+
for (const field of ['widthString', 'heightString']) {
|
|
843
|
+
const raw = appearance[field];
|
|
844
|
+
if (raw === undefined || raw === null || raw === '')
|
|
845
|
+
continue;
|
|
846
|
+
const normalized = normalizeCssSize(raw);
|
|
847
|
+
if (normalized !== undefined && normalized !== raw) {
|
|
848
|
+
appearance[field] = normalized;
|
|
849
|
+
node.appearance = appearance;
|
|
850
|
+
fixed.push(`${String(node.elementId ?? node.name ?? node.type ?? '?')}.${field}: ${String(raw)} → ${normalized}`);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
fixed.push(...normalizeSizeStrings(node.children));
|
|
854
|
+
}
|
|
855
|
+
return fixed;
|
|
856
|
+
}
|
|
857
|
+
export function sizeStringNote(fixed) {
|
|
858
|
+
if (fixed.length === 0)
|
|
859
|
+
return [];
|
|
860
|
+
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`];
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* U-05 (2026-09-09 koşusu): platform yazılmamış bir `appearance` alanını gövdede `null` olarak
|
|
864
|
+
* döndürüyor — `page_container` her yeni sayfada `widthType: null` ile geliyor. Yanıt katmanı
|
|
865
|
+
* null'ları eliyor (`responses.ts` `prune`), yani ajan öyle bir alan GÖRMÜYOR; lint ise ham
|
|
866
|
+
* gövdeye baktığı için `String(null)` = `'null'` diye bilinmeyen mod uyarısı basıyordu. Her yeni
|
|
867
|
+
* sayfanın ilk `page_schema_patch`'inde çıkan, kullanıcının düzeltemeyeceği gürültü buydu.
|
|
868
|
+
* `null` ve boş string = **yazılmamış**; `widthString`/`heightString` zaten böyle davranıyor.
|
|
869
|
+
*/
|
|
870
|
+
function sizeMode(raw) {
|
|
871
|
+
return raw === undefined || raw === null || raw === '' ? undefined : String(raw);
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* İki uyarı tek yürüyüşte:
|
|
875
|
+
*
|
|
876
|
+
* - **mod uyumsuzluğu** (`mismatchCode`): `widthString`/`heightString` var ama ilgili
|
|
877
|
+
* `*Type` `'fixed'` değil — string yalnız `fixed` modda okunur (`getFlexChildStyle`;
|
|
878
|
+
* `fill` her şeyi ezer), Studio paneli mod değişiminde string'i siler. Yazan ajan
|
|
879
|
+
* büyük olasılıkla modu yazmayı unutmuştur.
|
|
880
|
+
* - **geçersiz değer / bilinmeyen mod** (`invalidCode`): `normalizeCssSize`'dan geçmeyen
|
|
881
|
+
* string ya da `WIDTH_TYPES` dışı `widthType` — platform 200 döner ama değer sessizce
|
|
882
|
+
* düşer (eski backend bilinmeyen enum'u da düşürür).
|
|
883
|
+
*
|
|
884
|
+
* Uyarı yalnız **yazılmış** değer için çıkar: `null` ve boş string yazılmamış sayılır
|
|
885
|
+
* (`sizeMode`). Boyut string'leri bunu zaten yapıyordu, modlar yapmıyordu.
|
|
886
|
+
*/
|
|
887
|
+
export function flexSizeWarnings(nodes, mismatchCode, invalidCode) {
|
|
888
|
+
const warnings = [];
|
|
889
|
+
const walk = (list) => {
|
|
890
|
+
for (const node of list) {
|
|
891
|
+
const appearance = appearanceOf(node);
|
|
892
|
+
const label = String(node.elementId ?? node.name ?? node.type ?? '?');
|
|
893
|
+
const widthType = sizeMode(appearance.widthType);
|
|
894
|
+
const heightType = sizeMode(appearance.heightType);
|
|
895
|
+
if (widthType !== undefined && !WIDTH_TYPES.has(widthType)) {
|
|
896
|
+
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.`);
|
|
897
|
+
}
|
|
898
|
+
for (const [field, type, mode] of [['widthString', widthType, 'widthType'], ['heightString', heightType, 'heightType']]) {
|
|
899
|
+
const raw = appearance[field];
|
|
900
|
+
if (raw === undefined || raw === null || raw === '')
|
|
901
|
+
continue;
|
|
902
|
+
if (normalizeCssSize(raw) === undefined) {
|
|
903
|
+
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.`);
|
|
904
|
+
}
|
|
905
|
+
if (type !== 'fixed') {
|
|
906
|
+
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.`);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
walk(asObjects(node.children));
|
|
910
|
+
}
|
|
911
|
+
};
|
|
912
|
+
walk(asObjects(nodes));
|
|
913
|
+
return warnings;
|
|
914
|
+
}
|
|
635
915
|
const GRID_COLUMNS = 12;
|
|
636
916
|
const DESKTOP_BREAKPOINT = 1024;
|
|
637
917
|
const DEFAULT_RECT = { x: 0, y: 0, w: 12, h: 6 };
|
|
@@ -732,7 +1012,7 @@ function moveActive(items, active) {
|
|
|
732
1012
|
? item
|
|
733
1013
|
: moved.find(entry => entry.elementId === item.elementId) ?? item));
|
|
734
1014
|
}
|
|
735
|
-
function reorderSiblings(nodes) {
|
|
1015
|
+
function reorderSiblings(nodes, causes) {
|
|
736
1016
|
const laidOut = nodes
|
|
737
1017
|
.map((node, index) => ({ node, elementId: typeof node.elementId === 'string' && node.elementId !== '' ? node.elementId : `#${index}` }))
|
|
738
1018
|
.filter(entry => !ROOT_LAYER_TYPES.has(String(entry.node.type ?? '')));
|
|
@@ -741,8 +1021,17 @@ function reorderSiblings(nodes) {
|
|
|
741
1021
|
let items = laidOut.map(entry => ({ elementId: entry.elementId, rect: rectOf(desktopBlock(entry.node)) }));
|
|
742
1022
|
for (const entry of laidOut) {
|
|
743
1023
|
const active = items.find(item => item.elementId === entry.elementId);
|
|
744
|
-
if (active)
|
|
745
|
-
|
|
1024
|
+
if (!active)
|
|
1025
|
+
continue;
|
|
1026
|
+
// Kimin ittiğini burada yakalıyoruz: bildirim "y değişti" demekle kalmayıp hangi kardeşle
|
|
1027
|
+
// hücre paylaşıldığını da söylesin (G-31). Aynı düğüm birden çok turda itilirse SON iten
|
|
1028
|
+
// kalır — düzeni açıklayan da odur.
|
|
1029
|
+
const beforeMove = new Map(items.map(item => [item.elementId, item.rect.y]));
|
|
1030
|
+
items = moveActive(items, active);
|
|
1031
|
+
for (const item of items) {
|
|
1032
|
+
if (beforeMove.get(item.elementId) !== item.rect.y)
|
|
1033
|
+
causes.set(item.elementId, entry.elementId);
|
|
1034
|
+
}
|
|
746
1035
|
}
|
|
747
1036
|
let moved = 0;
|
|
748
1037
|
for (const [index, entry] of laidOut.entries()) {
|
|
@@ -764,11 +1053,11 @@ function reorderSiblings(nodes) {
|
|
|
764
1053
|
}
|
|
765
1054
|
return moved;
|
|
766
1055
|
}
|
|
767
|
-
function reorderPass(nodes) {
|
|
1056
|
+
function reorderPass(nodes, causes) {
|
|
768
1057
|
const siblings = asObjects(nodes);
|
|
769
|
-
let moved = reorderSiblings(siblings);
|
|
1058
|
+
let moved = reorderSiblings(siblings, causes);
|
|
770
1059
|
for (const node of siblings) {
|
|
771
|
-
moved += reorderPass(node.children);
|
|
1060
|
+
moved += reorderPass(node.children, causes);
|
|
772
1061
|
}
|
|
773
1062
|
return moved;
|
|
774
1063
|
}
|
|
@@ -811,12 +1100,17 @@ const MAX_REORDER_PASSES = 8;
|
|
|
811
1100
|
*/
|
|
812
1101
|
export function reorderSchemaBlocks(nodes) {
|
|
813
1102
|
const before = [];
|
|
1103
|
+
const causes = new Map();
|
|
814
1104
|
collectLayout(nodes, before);
|
|
815
|
-
for (let pass = 0; pass < MAX_REORDER_PASSES && reorderPass(nodes) > 0; pass++)
|
|
1105
|
+
for (let pass = 0; pass < MAX_REORDER_PASSES && reorderPass(nodes, causes) > 0; pass++)
|
|
816
1106
|
continue;
|
|
817
1107
|
return before
|
|
818
1108
|
.filter(entry => rectOf(desktopBlock(entry.node)).y !== entry.y)
|
|
819
|
-
.map(entry =>
|
|
1109
|
+
.map(entry => {
|
|
1110
|
+
const cause = causes.get(entry.label);
|
|
1111
|
+
const reason = cause && cause !== entry.label ? ` — '${cause}' ile aynı hücreleri paylaşıyordu` : '';
|
|
1112
|
+
return `${entry.label} y:${entry.y}→${rectOf(desktopBlock(entry.node)).y}${entry.hasBlock ? '' : " (desktop block'u yazıldı)"}${reason}`;
|
|
1113
|
+
});
|
|
820
1114
|
}
|
|
821
1115
|
export function reorderNote(moved) {
|
|
822
1116
|
if (moved.length === 0)
|
|
@@ -1177,6 +1471,9 @@ export function stringStyleWarning(ids) {
|
|
|
1177
1471
|
* kapsayıcıya `direction: 'column'` yazmak SESSİZCE hiçbir şey yapmaz — doğrulama duvarı da
|
|
1178
1472
|
* susar, çünkü prop katalogda VARDIR (2026-08-25 ölçümü: `page_schema_validate` yedi prop'a da
|
|
1179
1473
|
* `valid: true` döndü). Katalog varsayılanına eşit değerler elenir; onlar niyet taşımaz.
|
|
1474
|
+
* `wrap` varsayılanı 2026-09-01'de beş kapsayıcı şemasında ve Studio
|
|
1475
|
+
* `DEFAULT_CONTAINER_LAYOUT_PROPS`'ta `false` → `true` oldu; buradaki küme onu izler,
|
|
1476
|
+
* yoksa varsayılanla kurulmuş her düğüm yanlış pozitif üretir.
|
|
1180
1477
|
*/
|
|
1181
1478
|
const FLEX_LAYOUT_DEFAULTS = {
|
|
1182
1479
|
direction: 'row',
|
|
@@ -1184,16 +1481,34 @@ const FLEX_LAYOUT_DEFAULTS = {
|
|
|
1184
1481
|
verticalAlign: 'top',
|
|
1185
1482
|
size: 'shrink',
|
|
1186
1483
|
gap: 'medium',
|
|
1187
|
-
wrap:
|
|
1484
|
+
wrap: true
|
|
1188
1485
|
};
|
|
1189
1486
|
/** Kapsayıcı düzenini kuran prop kümesi (`ContainerComponent` şeması): `layout` + ona bağlı altı ayar. */
|
|
1190
1487
|
export const FLEX_LAYOUT_PROP_NAMES = new Set(['layout', ...Object.keys(FLEX_LAYOUT_DEFAULTS)]);
|
|
1488
|
+
/**
|
|
1489
|
+
* Düzen prop grubunu taşıyan komponent tipleri: `packages/ng-apps-ui/src/components` altında
|
|
1490
|
+
* kendi `schema.ts` dosyasında `horizontalAlign` tanımlayanlar. Kural yalnız bunlara uygulanır — `button` · `avatar` ·
|
|
1491
|
+
* `icon` gibi komponentlerin KENDİ `size` prop'u flex `size`'ı değildir ve dokuz komponent için
|
|
1492
|
+
* yanlış uyarı üretiyordu (09-04 Y-08).
|
|
1493
|
+
*/
|
|
1494
|
+
const FLEX_LAYOUT_HOST_TYPES = new Set(['aibox', 'box', 'box-section', 'container', 'page-container', 'form', 'layout', 'widget-area']);
|
|
1495
|
+
/**
|
|
1496
|
+
* Eski `cssgrid` (box/aibox'ın akan gerçek `display:grid` kipi; 2026-09-02'de seçenek olmaktan
|
|
1497
|
+
* çıktı, kayıtlı düğümlerde çalışmaya devam ediyor) düzen prop'larının HEPSİNİ ölü yapmaz:
|
|
1498
|
+
* `getGridChildrenStyle` boşluğu (`gap`/`gapCustom` → `resolveFlexGap`) ve `verticalAlign`'ı
|
|
1499
|
+
* ('stretch' ise `align-items`) okur. Bu ikisini ölü diye bildirmek, çalışan bir ayarı
|
|
1500
|
+
* sildirirdi. Kaynak: `packages/ng-apps-screen-builder/src/grid-layout.ts`.
|
|
1501
|
+
*/
|
|
1502
|
+
const CSS_GRID_READ_PROPS = new Set(['gap', 'verticalAlign']);
|
|
1191
1503
|
export function inertFlexPropNodes(nodes) {
|
|
1192
1504
|
const found = [];
|
|
1193
1505
|
const walk = (list) => {
|
|
1194
1506
|
for (const node of list) {
|
|
1195
|
-
if (!isFlexContainer(node)) {
|
|
1507
|
+
if (FLEX_LAYOUT_HOST_TYPES.has(String(node.type ?? '')) && !isFlexContainer(node)) {
|
|
1508
|
+
const isCssGrid = staticPropValue(node, 'layout') === 'cssgrid';
|
|
1196
1509
|
const inert = Object.keys(FLEX_LAYOUT_DEFAULTS).filter(name => {
|
|
1510
|
+
if (isCssGrid && CSS_GRID_READ_PROPS.has(name))
|
|
1511
|
+
return false;
|
|
1197
1512
|
const value = staticPropValue(node, name);
|
|
1198
1513
|
return value !== undefined && value !== null && value !== FLEX_LAYOUT_DEFAULTS[name];
|
|
1199
1514
|
});
|
|
@@ -1214,6 +1529,69 @@ export function inertFlexPropWarning(entries) {
|
|
|
1214
1529
|
+ "sessizce yok sayılır (istek reddedilmez, prop katalogda vardır). Akış düzeni istiyorsan aynı düğüme "
|
|
1215
1530
|
+ "layout:'flex' de yaz, istemiyorsan bu prop'ları kaldır — platform_guide('page') Kural 1f-2."];
|
|
1216
1531
|
}
|
|
1532
|
+
/**
|
|
1533
|
+
* Bir yamanın SİLECEĞİ akışlar. Platform referanssız kalan akışı KALICI siler; uyarı yamadan sonra
|
|
1534
|
+
* geldiğinde "önce yedekle" tavsiyesi uygulanamaz hâle geliyordu (09-05 koşusu: sekiz adımlı giriş
|
|
1535
|
+
* akışı sıfırdan yeniden yazıldı). Bu yüzden plan yamadan ÖNCE hesaplanır — `remove_component`
|
|
1536
|
+
* kadar `set_event`/`remove_event` de akış öldürür ve o yolda hiç uyarı yoktu.
|
|
1537
|
+
*/
|
|
1538
|
+
export function orphanedFlowPlan(schema, pageEvents, ops) {
|
|
1539
|
+
const nodes = asObjects(schema);
|
|
1540
|
+
const opList = asObjects(ops);
|
|
1541
|
+
const plan = [];
|
|
1542
|
+
const survivingRefs = (skipElementId, skipEvent) => {
|
|
1543
|
+
const surviving = new Set();
|
|
1544
|
+
for (const event of asObjects(pageEvents)) {
|
|
1545
|
+
if (typeof event.value === 'string')
|
|
1546
|
+
surviving.add(event.value);
|
|
1547
|
+
}
|
|
1548
|
+
collectFlowRefsExcept(nodes, skipElementId, surviving);
|
|
1549
|
+
if (skipEvent !== undefined) {
|
|
1550
|
+
const node = findNodeByElementId(nodes, skipElementId);
|
|
1551
|
+
for (const event of asObjects(node?.events)) {
|
|
1552
|
+
if (String(event.name ?? '') !== skipEvent && typeof event.value === 'string')
|
|
1553
|
+
surviving.add(event.value);
|
|
1554
|
+
}
|
|
1555
|
+
for (const flowId of collectFlowRefs({ ...(node ?? {}), events: [] }))
|
|
1556
|
+
surviving.add(flowId);
|
|
1557
|
+
}
|
|
1558
|
+
return surviving;
|
|
1559
|
+
};
|
|
1560
|
+
for (const op of opList) {
|
|
1561
|
+
const target = String(op.target ?? '');
|
|
1562
|
+
if (target === '')
|
|
1563
|
+
continue;
|
|
1564
|
+
if (op.op === 'remove_component') {
|
|
1565
|
+
const doomed = findNodeByElementId(nodes, target);
|
|
1566
|
+
if (!doomed)
|
|
1567
|
+
continue;
|
|
1568
|
+
const losing = collectFlowRefs(doomed);
|
|
1569
|
+
const surviving = survivingRefs(target);
|
|
1570
|
+
const orphaned = [...losing].filter(flowId => !surviving.has(flowId));
|
|
1571
|
+
if (orphaned.length > 0)
|
|
1572
|
+
plan.push({ target, op: 'remove_component', flowIds: orphaned });
|
|
1573
|
+
continue;
|
|
1574
|
+
}
|
|
1575
|
+
if (op.op !== 'set_event' && op.op !== 'remove_event')
|
|
1576
|
+
continue;
|
|
1577
|
+
const eventName = String(op.event ?? '');
|
|
1578
|
+
const node = findNodeByElementId(nodes, target);
|
|
1579
|
+
const current = asObjects(node?.events).find(event => String(event.name ?? '') === eventName);
|
|
1580
|
+
const currentValue = typeof current?.value === 'string' ? current.value : '';
|
|
1581
|
+
if (currentValue === '' || currentValue === String(op.eventValue ?? ''))
|
|
1582
|
+
continue;
|
|
1583
|
+
const surviving = survivingRefs(target, eventName);
|
|
1584
|
+
if (!surviving.has(currentValue))
|
|
1585
|
+
plan.push({ target, op: String(op.op), flowIds: [currentValue] });
|
|
1586
|
+
}
|
|
1587
|
+
return plan;
|
|
1588
|
+
}
|
|
1589
|
+
export function orphanedFlowMessage(plan) {
|
|
1590
|
+
const lines = plan.map(entry => `'${entry.target}' (${entry.op}) → ${entry.flowIds.join(', ')}`);
|
|
1591
|
+
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(' · ')}. `
|
|
1592
|
+
+ 'Akışı korumak için önce flow_get ile gövdesini yedekleyin ya da set_event ile başka bir komponente bağlayın. '
|
|
1593
|
+
+ 'Silinmesini bilerek istiyorsanız aynı çağrıyı allowFlowDeletion: true ile tekrarlayın; yanıt hangi akışların silindiğini listeler.';
|
|
1594
|
+
}
|
|
1217
1595
|
export function orphanedFlowWarnings(schema, pageEvents, ops) {
|
|
1218
1596
|
const removals = asObjects(ops).filter(op => op.op === 'remove_component');
|
|
1219
1597
|
if (removals.length === 0)
|
|
@@ -1251,6 +1629,112 @@ function findNodeByElementId(nodes, elementId) {
|
|
|
1251
1629
|
}
|
|
1252
1630
|
return undefined;
|
|
1253
1631
|
}
|
|
1632
|
+
/**
|
|
1633
|
+
* `orphanedFlowWarnings` silinen düğümün REFERANS VERDİĞİ akışları bulur; burası ters yönü kapsar:
|
|
1634
|
+
* silinen düğümü HEDEFLEYEN setter adımlarını. Aynı `elementId` ile hemen yeniden eklense bile
|
|
1635
|
+
* platform komponent kimliğini yeniden üretir ve setter sessizce ölür — akış koşar, istek 200
|
|
1636
|
+
* döner, komponente hiçbir şey yazılmaz ve tek belirti boş komponenttir. ÖLÇÜLDÜ 2026-09-04
|
|
1637
|
+
* (benchmark 4. koşu, `S-9`): teşhis ~8 dakika sürdü, uç doğru veri döndürdüğü ve şema geçerli
|
|
1638
|
+
* olduğu için hatayı yalnız ekran görüntüsü gösterdi. Kural `platform_guide('page')` Kural 1f'de
|
|
1639
|
+
* yazılı ama düğüm silinmeden ÖNCE okunması gerekiyor; bu uyarı tek erken sinyaldir.
|
|
1640
|
+
*/
|
|
1641
|
+
export function staleSetterWarnings(flows, ops, schema) {
|
|
1642
|
+
const opList = asObjects(ops);
|
|
1643
|
+
const removed = new Set(opList
|
|
1644
|
+
.filter(op => op.op === 'remove_component')
|
|
1645
|
+
.map(op => String(op.target ?? ''))
|
|
1646
|
+
.filter(target => target.length > 0));
|
|
1647
|
+
if (removed.size === 0)
|
|
1648
|
+
return [];
|
|
1649
|
+
// Silinen KAPSAYICININ altındaki düğümler de gider. Eskiden yalnız op'un kendi target'ına
|
|
1650
|
+
// bakılıyordu; 09-05 koşusunda `sign_in_form` silinince ona değil ÇOCUĞU `btn_sign_in`'e yazan
|
|
1651
|
+
// setter sessizce koptu ve uyarı hiç çıkmadı (G-21). Komşu işlev `orphanedFlowWarnings` alt ağacı
|
|
1652
|
+
// zaten geziyordu — iki işlev aynı olaya bakıp farklı derinlik kullanıyordu.
|
|
1653
|
+
for (const target of [...removed]) {
|
|
1654
|
+
const doomed = findNodeByElementId(asObjects(schema), target);
|
|
1655
|
+
if (doomed)
|
|
1656
|
+
collectElementIds(doomed.children, removed);
|
|
1657
|
+
}
|
|
1658
|
+
const readded = new Set();
|
|
1659
|
+
for (const op of opList.filter(op => op.op === 'add_component'))
|
|
1660
|
+
collectElementIds(op.component, readded);
|
|
1661
|
+
const warnings = [];
|
|
1662
|
+
const affected = [];
|
|
1663
|
+
const seen = new Set();
|
|
1664
|
+
for (const flow of asObjects(flows)) {
|
|
1665
|
+
const flowName = String(flow.name ?? flow.slug ?? flow.id ?? 'adsız akış');
|
|
1666
|
+
for (const step of [...asObjects(flow.steps), ...asObjects(flow.errorSteps)].filter(step => step.type === 'setter')) {
|
|
1667
|
+
const args = (step.args ?? {});
|
|
1668
|
+
for (const iteratee of asObjects(args.iteratees)) {
|
|
1669
|
+
const element = typeof iteratee.element === 'string' ? iteratee.element : '';
|
|
1670
|
+
if (!removed.has(element))
|
|
1671
|
+
continue;
|
|
1672
|
+
const key = `${flow.id ?? flowName}|${step.name ?? step.stepId}|${element}`;
|
|
1673
|
+
if (seen.has(key))
|
|
1674
|
+
continue;
|
|
1675
|
+
seen.add(key);
|
|
1676
|
+
const stepName = String(step.name ?? step.stepId ?? 'adsız adım');
|
|
1677
|
+
warnings.push(readded.has(element)
|
|
1678
|
+
? `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.`
|
|
1679
|
+
: `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.`);
|
|
1680
|
+
affected.push(`${flowName} · ${stepName} → ${element}`);
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
// Özet satırı BAŞA gelir: yama işlenmeden önce kaç iteratee'nin ve hangi akışların etkileneceği
|
|
1685
|
+
// tek bakışta görünsün (2026-09-06 · S-5, P0-2 Step 7). Tek tek satırlar altında kalır.
|
|
1686
|
+
if (affected.length > 0) {
|
|
1687
|
+
const flowNames = [...new Set(affected.map(entry => entry.split(' · ')[0]))];
|
|
1688
|
+
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.`);
|
|
1689
|
+
}
|
|
1690
|
+
return warnings;
|
|
1691
|
+
}
|
|
1692
|
+
/**
|
|
1693
|
+
* FLOW-017'nin kör noktası: o kural yalnız BOŞ `element`/`property` görür. 2026-09-09 koşusunda
|
|
1694
|
+
* (S-14) setter dolu ama HENÜZ VAR OLMAYAN bir elementId'ye bağlandı — platform alanı doğrulamaz,
|
|
1695
|
+
* 200 döner ve `element`i null kaydeder; adım koşar, hata vermez, hiçbir şeye yazmaz. Tek belirti
|
|
1696
|
+
* boş komponent. Yakalayan şey yazma SONRASI `silentlyDropped` diff'iydi; bu işlev aynı şeyi
|
|
1697
|
+
* yazmadan önce söyler. Barındırıcı kaydını okumak gerektiği için `policyRules` içine giremez
|
|
1698
|
+
* (check senkron ve yalnız gövdeyi görür) — yeri FLOW-003/FLOW-013 ile aynı, `tools/flows.ts`.
|
|
1699
|
+
*/
|
|
1700
|
+
export function unknownSetterTargetWarnings(steps, errorSteps, hostNodes) {
|
|
1701
|
+
const known = new Set();
|
|
1702
|
+
collectElementIds(hostNodes, known);
|
|
1703
|
+
// Küme boşsa barındırıcı okunamamış ya da şema henüz kurulmamıştır: yanlış pozitif üretmektense
|
|
1704
|
+
// sessiz kalırız — uyarının değeri "şemada VAR olanı da söylüyor" olmasından geliyor.
|
|
1705
|
+
if (known.size === 0)
|
|
1706
|
+
return [];
|
|
1707
|
+
const warnings = [];
|
|
1708
|
+
const seen = new Set();
|
|
1709
|
+
for (const step of [...asObjects(steps), ...asObjects(errorSteps)].filter(step => step.type === 'setter')) {
|
|
1710
|
+
const args = (step.args ?? {});
|
|
1711
|
+
for (const iteratee of asObjects(args.iteratees)) {
|
|
1712
|
+
const element = typeof iteratee.element === 'string' ? iteratee.element.trim() : '';
|
|
1713
|
+
if (element.length === 0 || known.has(element))
|
|
1714
|
+
continue;
|
|
1715
|
+
const stepName = String(step.name ?? step.stepId ?? 'adsız adım');
|
|
1716
|
+
const key = `${stepName}|${element}`;
|
|
1717
|
+
if (seen.has(key))
|
|
1718
|
+
continue;
|
|
1719
|
+
seen.add(key);
|
|
1720
|
+
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.`);
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
return warnings;
|
|
1724
|
+
}
|
|
1725
|
+
function collectElementIds(node, into) {
|
|
1726
|
+
if (Array.isArray(node)) {
|
|
1727
|
+
for (const entry of node)
|
|
1728
|
+
collectElementIds(entry, into);
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1731
|
+
if (!node || typeof node !== 'object')
|
|
1732
|
+
return;
|
|
1733
|
+
const record = node;
|
|
1734
|
+
if (typeof record.elementId === 'string')
|
|
1735
|
+
into.add(record.elementId);
|
|
1736
|
+
collectElementIds(record.children, into);
|
|
1737
|
+
}
|
|
1254
1738
|
function collectFlowRefs(node) {
|
|
1255
1739
|
const found = new Set();
|
|
1256
1740
|
const visitProps = (value) => {
|