@appaflytech/zeyrek-ai-mcp 0.0.7 → 0.0.9
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/README.md +27 -21
- package/dist/config.js +2 -0
- package/dist/knowledge/backendConstraints.js +3 -0
- package/dist/knowledge/componentCatalog.js +172 -33
- package/dist/knowledge/componentEvents.js +35 -4
- package/dist/knowledge/errorPath.js +1 -1
- package/dist/knowledge/normalize.js +474 -23
- package/dist/knowledge/propTypes.js +5 -4
- package/dist/knowledge/rules.js +113 -6
- package/dist/knowledge/styleResolve.js +52 -5
- package/dist/knowledge/themeResolve.js +936 -0
- package/dist/knowledge/themeRules.js +221 -0
- package/dist/knowledge/themeSchema.js +335 -0
- package/dist/platform/domSummary.js +144 -0
- package/dist/platform/http.js +10 -2
- package/dist/platform/screenshot.js +492 -29
- package/dist/platform/screenshotMemory.js +55 -0
- package/dist/platform/strictArguments.js +32 -0
- package/dist/platform/writeDiff.js +6 -3
- package/dist/resources/examples/layout-app-shell.json +1 -1
- package/dist/resources/examples/page-sign-in.json +1 -1
- package/dist/resources/examples/theme-brand.json +162 -0
- package/dist/resources/guides/component.md +8 -2
- package/dist/resources/guides/expressions.md +6 -0
- package/dist/resources/guides/getting-started.md +6 -0
- package/dist/resources/guides/layout.md +36 -26
- package/dist/resources/guides/microservice.md +160 -6
- package/dist/resources/guides/page.md +165 -59
- package/dist/resources/guides/theme.md +210 -0
- package/dist/resources/guides/translation.md +13 -10
- package/dist/resources/guides/workflow.md +141 -7
- package/dist/resources/schema-fingerprint.json +2 -2
- package/dist/resources/schemas/CreateMicroserviceCommand.json +4 -1
- package/dist/resources/schemas/CreateWorkflowCommand.json +4 -1
- package/dist/resources/schemas/PreviewMicroserviceCommand.json +4 -1
- package/dist/resources/schemas/PreviewWorkflowCommand.json +4 -1
- package/dist/resources/schemas/StepDto.json +4 -1
- package/dist/resources/schemas/UpdateMicroserviceCommand.json +4 -1
- package/dist/resources/schemas/UpdateWorkflowCommand.json +4 -1
- package/dist/server.js +52 -42
- package/dist/tools/components.js +8 -0
- package/dist/tools/designSchemaPatch.js +152 -0
- package/dist/tools/flows.js +24 -21
- package/dist/tools/layouts.js +8 -8
- package/dist/tools/microservices.js +74 -10
- package/dist/tools/pages.js +275 -332
- package/dist/tools/projects.js +66 -9
- package/dist/tools/queries.js +72 -6
- package/dist/tools/schemaOps.js +280 -0
- package/dist/tools/screenWrite.js +2 -1
- package/dist/tools/screens.js +0 -7
- package/dist/tools/themes.js +163 -0
- package/dist/tools/workflows.js +10 -6
- package/package.json +1 -1
package/dist/knowledge/rules.js
CHANGED
|
@@ -4,6 +4,7 @@ import { reservedTenantTables } from './reservedTables.js';
|
|
|
4
4
|
import { headersAccessNames, isHeadersAccessGuarded } from './flowGuards.js';
|
|
5
5
|
import { callsItself, functionCallRefs, isValidParameterName, mentionsAwait, nameProblem, sourceOf, callPrefix } from './functionGuards.js';
|
|
6
6
|
import { backendPolicyRules } from './backendConstraints.js';
|
|
7
|
+
import { themePolicyRules } from './themeRules.js';
|
|
7
8
|
const terminalStepTypes = new Set(['response', 'convert', 'revert']);
|
|
8
9
|
const DESCRIPTION_EXEMPT_STEPS = new Set(['start']);
|
|
9
10
|
const stepKindMatrix = {
|
|
@@ -125,7 +126,7 @@ const conditionRuleIdPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}
|
|
|
125
126
|
// Runtime.svelte'de HAM verilir (`argument: step.args`), yani bu alanlarda ifade değerlendirilmez.
|
|
126
127
|
const flowLiteralTextFields = new Map([
|
|
127
128
|
['alert', ['message', 'description']],
|
|
128
|
-
['confirm', ['message']]
|
|
129
|
+
['confirm', ['message', 'title', 'confirmText', 'cancelText']]
|
|
129
130
|
]);
|
|
130
131
|
const flowExpressionLooking = /\$\{|\b(?:Steps|Page|Row|Widget|LocalStorage|state|self|item|event|error|cookie)\s*(?:\?\.|\.|\[)/;
|
|
131
132
|
// Barındırıcıya özel tipler (`navigate` web'de, `confirm`/`download` mobilde register edilmez)
|
|
@@ -235,7 +236,7 @@ export function flowExpressionSites(step) {
|
|
|
235
236
|
}
|
|
236
237
|
return sites;
|
|
237
238
|
}
|
|
238
|
-
const writeOperations = new Set(['create', 'update', 'delete', 'upsert']);
|
|
239
|
+
const writeOperations = new Set(['create', 'update', 'delete', 'upsert', 'bulk-create', 'bulk-update', 'bulk-delete']);
|
|
239
240
|
/** Built-in `roles` entity'si ve slug alanı — SeedData her projeye SABİT id'lerle açar (EntityConstants.cs). */
|
|
240
241
|
const rolesEntityId = '01917ede-88a2-72ea-8397-2619e5d5dca0';
|
|
241
242
|
const rolesSlugFieldId = '0199c35d-cfc8-77d4-9c20-29b857153147';
|
|
@@ -346,6 +347,38 @@ function unreadSelectFields(step, steps) {
|
|
|
346
347
|
.map(field => String(field.fieldSlug ?? field.slug ?? ''))
|
|
347
348
|
.filter(slug => slug.length > 0 && !new RegExp(`\\b${escapeForPattern(slug)}\\b`).test(usage));
|
|
348
349
|
}
|
|
350
|
+
const SQL_COMMENT_PATTERN = /--[^\n]*|\/\*[\s\S]*?\*\//g;
|
|
351
|
+
const QUERY_PARAMETER_TOKEN = String.raw `(?:\{\{\s*([A-Za-z_]\w*)\s*\}\}|(?<![:@\w'])@([A-Za-z_]\w*))(?:\s*::\s*\w+)*`;
|
|
352
|
+
const EMPTY_STRING_COMPARISON_PATTERN = new RegExp(`${QUERY_PARAMETER_TOKEN}\\s*=\\s*(?<!')''(?!')|(?<!')''\\s*=\\s*${QUERY_PARAMETER_TOKEN}`, 'g');
|
|
353
|
+
/**
|
|
354
|
+
* `rawQuery`'de bir parametreyi boş string'le EŞİTLİK üzerinden karşılaştıran yerlerin parametre
|
|
355
|
+
* adları (`@p = ''`, `@p::text = ''`, `'' = {{p}}`). Yorumlar önce atılır — platform da yorum
|
|
356
|
+
* içindeki belirteci parametre saymıyor. Yalnız `=` aranır: `@p <> ''` boş değerde NULL (yanlış)
|
|
357
|
+
* verir ve "doluysa" niyetiyle yazıldığında sonuç zaten doğrudur.
|
|
358
|
+
*/
|
|
359
|
+
function emptyStringComparisons(rawQuery) {
|
|
360
|
+
if (typeof rawQuery !== 'string')
|
|
361
|
+
return [];
|
|
362
|
+
const names = new Set();
|
|
363
|
+
for (const match of rawQuery.replace(SQL_COMMENT_PATTERN, ' ').matchAll(EMPTY_STRING_COMPARISON_PATTERN)) {
|
|
364
|
+
const name = match[1] ?? match[2] ?? match[3] ?? match[4];
|
|
365
|
+
if (name !== undefined)
|
|
366
|
+
names.add(name);
|
|
367
|
+
}
|
|
368
|
+
return [...names];
|
|
369
|
+
}
|
|
370
|
+
/** `rawQuery`'nin başvurduğu parametre adları (`@p`, `{{p}}`), yorumlar atılarak; ilk görülme sırasıyla. */
|
|
371
|
+
export function rawQueryParameterNames(rawQuery) {
|
|
372
|
+
if (typeof rawQuery !== 'string')
|
|
373
|
+
return [];
|
|
374
|
+
const names = new Set();
|
|
375
|
+
for (const match of rawQuery.replace(SQL_COMMENT_PATTERN, ' ').matchAll(new RegExp(QUERY_PARAMETER_TOKEN, 'g'))) {
|
|
376
|
+
const name = match[1] ?? match[2];
|
|
377
|
+
if (name !== undefined)
|
|
378
|
+
names.add(name);
|
|
379
|
+
}
|
|
380
|
+
return [...names];
|
|
381
|
+
}
|
|
349
382
|
const isRecord = (value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
350
383
|
const functionsGuide = 'ngapps://guides/functions';
|
|
351
384
|
export const policyRules = [
|
|
@@ -418,7 +451,7 @@ export const policyRules = [
|
|
|
418
451
|
id: 'MS-016',
|
|
419
452
|
module: 'microservice',
|
|
420
453
|
appliesTo: ['create', 'update', 'preview'],
|
|
421
|
-
sourceRef: "İK koşusu M-3: operation 'upsert' olan uçta expression'la yazılan alan UPDATE yolunda yeniden değerlendiriliyor; Incremental sicil alanı EMP-0005 → EMP-0006 diye sessizce değişti.
|
|
454
|
+
sourceRef: "İK koşusu M-3: operation 'upsert' olan uçta expression'la yazılan alan UPDATE yolunda yeniden değerlendiriliyor; Incremental sicil alanı EMP-0005 → EMP-0006 diye sessizce değişti. Alan bazlı writeOn (create|update|always) 2026-09-04'te eklendi (B-12), bu yüzden kural artık çözümü söyler ve writeOn:'create' taşıyan alanda susar",
|
|
422
455
|
check: definition => {
|
|
423
456
|
const parameters = (definition.sourceParameters ?? {});
|
|
424
457
|
if (String(parameters.operation ?? '') !== 'upsert')
|
|
@@ -426,13 +459,14 @@ export const policyRules = [
|
|
|
426
459
|
const generated = asArray(parameters.fields)
|
|
427
460
|
.map(entry => entry)
|
|
428
461
|
.filter(field => String(field.source ?? '') === 'expression')
|
|
462
|
+
.filter(field => String(field.writeOn ?? 'always').toLowerCase() !== 'create')
|
|
429
463
|
.map(field => String(field.alias ?? field.fieldId ?? '?'));
|
|
430
464
|
if (generated.length === 0)
|
|
431
465
|
return [];
|
|
432
466
|
return [{
|
|
433
467
|
ruleId: 'MS-016',
|
|
434
468
|
severity: 'warn',
|
|
435
|
-
message: `upsert ucunda expression kaynaklı alan var (${generated.join(', ')}): upsert UPDATE yolunda da bu ifadeyi yeniden değerlendirir, yani sicil/kod gibi ÜRETİLEN bir değer güncellemede sessizce değişir.
|
|
469
|
+
message: `upsert ucunda expression kaynaklı alan var (${generated.join(', ')}): upsert UPDATE yolunda da bu ifadeyi yeniden değerlendirir, yani sicil/kod gibi ÜRETİLEN bir değer güncellemede sessizce değişir. Alan yalnız oluşturmada yazılacaksa fields[] öğesine writeOn: 'create' koyun — upsert'in UPDATE dalı o alanı satıra hiç koymaz ve bu uyarı susar. writeOn yalnız operation:'upsert' ucunda geçerlidir (MS-017) ve bir 'kısmi güncelleme' bayrağı DEĞİLDİR: listelenen alan gövdede gelmezse yine NULL yazılır.`,
|
|
436
470
|
guide: 'ngapps://guides/microservice'
|
|
437
471
|
}];
|
|
438
472
|
}
|
|
@@ -451,6 +485,23 @@ export const policyRules = [
|
|
|
451
485
|
guide: 'ngapps://guides/microservice'
|
|
452
486
|
}]
|
|
453
487
|
},
|
|
488
|
+
{
|
|
489
|
+
id: 'QRY-007',
|
|
490
|
+
module: 'query',
|
|
491
|
+
appliesTo: ['create', 'update', 'preview'],
|
|
492
|
+
sourceRef: "2026-09-15 bulgusu: pano sorgusu '(@tech = '' OR w.technician_id::text = @tech)' ile 0 satır döndü, hata yok. Kök neden Query.PrepareQueryParameters / NormalizePreviewParameters: boş ya da yalnız boşluktan oluşan değer NULL bağlanır, bu yüzden '@p = ''' hiçbir değerle doğru olmaz. Bulgunun 'dokunulmamış dropdown null gönderiyor' teşhisi yanlıştı — '' gönderen form da aynı sonucu alır. warn: kalıp hatalı ama yazmayı durdurmak gerekmiyor",
|
|
493
|
+
check: definition => {
|
|
494
|
+
const names = emptyStringComparisons(definition.rawQuery);
|
|
495
|
+
if (names.length === 0)
|
|
496
|
+
return [];
|
|
497
|
+
return [{
|
|
498
|
+
ruleId: 'QRY-007',
|
|
499
|
+
severity: 'warn',
|
|
500
|
+
message: `rawQuery parametreyi boş string'le karşılaştırıyor (${names.map(name => `@${name} = ''`).join(', ')}): platform boş ya da yalnız boşluktan oluşan parametre değerini NULL bağlar, NULL = '' hiçbir zaman doğru olmaz ve 'filtre boşsa hepsini getir' dalı ASLA çalışmaz — filtre boş geldiğinde sorgu hatasız 0 satır döner. Değerin form bileşeninden null ya da '' gelmesi fark etmez. Şöyle yazın: (@${names[0]} IS NULL OR kolon::text = @${names[0]}).`,
|
|
501
|
+
guide: 'ngapps://guides/microservice'
|
|
502
|
+
}];
|
|
503
|
+
}
|
|
504
|
+
},
|
|
454
505
|
{
|
|
455
506
|
id: 'MS-018',
|
|
456
507
|
module: 'microservice',
|
|
@@ -2215,7 +2266,7 @@ export const policyRules = [
|
|
|
2215
2266
|
id: 'FLOW-022',
|
|
2216
2267
|
module: 'flow',
|
|
2217
2268
|
appliesTo: ['create', 'update'],
|
|
2218
|
-
sourceRef: "alert.ts `const { message, alertType, description } = task.argument` → toast.error(message, { description }) / dialog.alert(message); confirm.ts `dialog.confirm(
|
|
2269
|
+
sourceRef: "alert.ts `const { message, alertType, description } = task.argument` → toast.error(message, { description }) / dialog.alert(message); confirm.ts `dialog.confirm(message, { title, confirmText, cancelText })` (boş çerçeve metni dialog.functions.ts resolveDialogFrame'de sabit metne düşer). Argüman Runtime.svelte'de HAM geçer (`argument: step.args`) — bu alanlarda hiçbir ifade değerlendirilmez, yazılan metin ekranda harfi harfine görünür (2026-09-06·2 koşusu S-13'ün ikinci turu: rehber hata mesajını göstermek için alert adımını öneriyordu)",
|
|
2219
2270
|
check: definition => flowAllSteps(definition)
|
|
2220
2271
|
.flatMap((step) => {
|
|
2221
2272
|
const type = String(step.type ?? '');
|
|
@@ -2666,7 +2717,7 @@ export const policyRules = [
|
|
|
2666
2717
|
id: 'WF-021',
|
|
2667
2718
|
module: 'workflow',
|
|
2668
2719
|
appliesTo: ['create', 'update'],
|
|
2669
|
-
sourceRef: "IStep.cs:
|
|
2720
|
+
sourceRef: "IStep.cs:88-104 bağlanmamış/eksik fail dalını global-error dalına yönlendirir; ikisi de yoksa canlıda WorkflowStepFailedException fırlatılır (2026-09-16), önizlemede ActivityContext.SetUnhandledError 500 gövdesi kurar. automation'da 'response' adımı yasak (stepKindMatrix) olduğu için gateway bu tipte enjeksiyon yapmaz — errorPath.ts",
|
|
2670
2721
|
check: (definition, context) => {
|
|
2671
2722
|
if (context.workflowKind !== 'automation')
|
|
2672
2723
|
return [];
|
|
@@ -2738,6 +2789,7 @@ export const policyRules = [
|
|
|
2738
2789
|
},
|
|
2739
2790
|
// Backend doğrulayıcılarının aynası (ENT-015…018, MS-017, WF-026, QRY/VIEW/DTO/ROW/DS/PRJ/TR/FLD/FILE/EXT):
|
|
2740
2791
|
// tek liste `backendConstraints.ts`'te durur, buradan yalnız politika motoruna katılır.
|
|
2792
|
+
...themePolicyRules,
|
|
2741
2793
|
...backendPolicyRules()
|
|
2742
2794
|
];
|
|
2743
2795
|
/**
|
|
@@ -2821,3 +2873,58 @@ export async function microfunctionStepFindings(platform, project, environment,
|
|
|
2821
2873
|
}
|
|
2822
2874
|
return applyPolicyMode(platform.config, findings, policyOverride);
|
|
2823
2875
|
}
|
|
2876
|
+
const partialWriteOperations = new Set(['create', 'update', 'upsert']);
|
|
2877
|
+
const inputReferencePattern = /\binput\.([A-Za-z_$][\w$]*)/g;
|
|
2878
|
+
function inputReferencesOf(rule) {
|
|
2879
|
+
return asArray(rule.fields)
|
|
2880
|
+
.filter((field) => Boolean(field) && typeof field === 'object')
|
|
2881
|
+
.flatMap(field => [...String(field.value ?? '').matchAll(inputReferencePattern)].map(match => match[1]))
|
|
2882
|
+
.filter((name) => typeof name === 'string');
|
|
2883
|
+
}
|
|
2884
|
+
/**
|
|
2885
|
+
* WF-031 — yazma adımı, akışın giriş DTO'sunda OLMAYAN bir `input.x` alanını yazıyorsa uyarır.
|
|
2886
|
+
* Böyle bir ifade çalışma anında `undefined` döner, `Parse` onu `null` yapar ve platform kolonu
|
|
2887
|
+
* NULL yazar: satırdaki mevcut değer silinir, yanıt yine 200'dür (risk uygulaması Kanban taşıma
|
|
2888
|
+
* bulgusu, 2026-09-15 — `{ id, status }` gövdesi `title`/`owner_id`/`due_date`'i sildi).
|
|
2889
|
+
*
|
|
2890
|
+
* Kural yalnız DTO'da HİÇ olmayan alanı sayar: "DTO'da var ama çağıran göndermedi" vakası
|
|
2891
|
+
* tanımdan görülemez, onu rehber anlatır. DTO şeması okunamazsa (404/erişim) kural susar —
|
|
2892
|
+
* bir okuma hatası yazma çağrısını engellememelidir.
|
|
2893
|
+
*/
|
|
2894
|
+
export async function undefinedInputFieldFindings(platform, project, environment, definition, kind, policyOverride = false) {
|
|
2895
|
+
if (platform.config.policyMode === 'off')
|
|
2896
|
+
return [];
|
|
2897
|
+
const body = workflowBody(definition, kind);
|
|
2898
|
+
if (!hasText(body.parameterDtoId))
|
|
2899
|
+
return [];
|
|
2900
|
+
const candidates = workflowSteps(body)
|
|
2901
|
+
.filter(step => step.type === 'table' && step.rule)
|
|
2902
|
+
.filter(step => partialWriteOperations.has(String(step.rule.operation ?? '').toLowerCase()))
|
|
2903
|
+
.map(step => ({ step, references: [...new Set(inputReferencesOf(step.rule))] }))
|
|
2904
|
+
.filter(candidate => candidate.references.length > 0);
|
|
2905
|
+
if (candidates.length === 0)
|
|
2906
|
+
return [];
|
|
2907
|
+
let dtoFieldNames;
|
|
2908
|
+
try {
|
|
2909
|
+
const payload = await platform.tenant('GET', project, `/datatransferobjects/${String(body.parameterDtoId)}/scheme`, { environment });
|
|
2910
|
+
const items = asArray(payload?.data ?? payload).filter((item) => Boolean(item) && typeof item === 'object');
|
|
2911
|
+
dtoFieldNames = new Set(items.map(item => String(item.name ?? '')));
|
|
2912
|
+
}
|
|
2913
|
+
catch (error) {
|
|
2914
|
+
if (error instanceof PlatformApiError)
|
|
2915
|
+
return [];
|
|
2916
|
+
throw error;
|
|
2917
|
+
}
|
|
2918
|
+
const findings = candidates.flatMap(({ step, references }) => {
|
|
2919
|
+
const missing = references.filter(name => !dtoFieldNames.has(name));
|
|
2920
|
+
return missing.length > 0
|
|
2921
|
+
? [{
|
|
2922
|
+
ruleId: 'WF-031',
|
|
2923
|
+
severity: 'warn',
|
|
2924
|
+
message: `'${stepLabel(step)}' adımı ${missing.map(name => `input.${name}`).join(', ')} alanlarını yazıyor ama akışın giriş DTO'sunda bu alanlar YOK: ifade undefined döner, platform kolonu NULL yazar ve satırdaki mevcut değer SİLİNİR (yanıt yine 200). Yazma adımında SET listesi fields[]'tir, gövde değil — 'göndermediğim alan değişmez' (REST PATCH) sezgisi burada geçmez. İki doğru çıkış: (1) adımı EYLEM BAŞINA daraltın, fields[] yalnız bu eylemin gerçekten yazdığı alanları listelesin (kanban taşıma: yalnız status); (2) alanı DTO'ya ekleyin VE çağıranın her istekte gönderdiğinden emin olun — DTO'ya eklemek yalnız izin verir, göndermeyi garanti etmez. Alan kümesi ancak çağrı anında belliyse akışta oku-birleştir: önce table get, sonra "typeof input.x !== 'undefined' && input.x !== null ? input.x : read_row.data.x" (bedeli fazladan okuma ve son-yazan-kazanır).`,
|
|
2925
|
+
guide: 'ngapps://guides/workflow'
|
|
2926
|
+
}]
|
|
2927
|
+
: [];
|
|
2928
|
+
});
|
|
2929
|
+
return applyPolicyMode(platform.config, findings, policyOverride);
|
|
2930
|
+
}
|
|
@@ -22,6 +22,12 @@
|
|
|
22
22
|
* `TYPED_STYLE_COMPONENTS` kaydına eklenir — başka hiçbir yer değişmez.
|
|
23
23
|
*/
|
|
24
24
|
export const STYLE_PRESET_KEY = '$preset';
|
|
25
|
+
/**
|
|
26
|
+
* Düğümün hangi BİLEŞEN PRESETİ versiyonundan kurulduğunu anan işaret (frontend
|
|
27
|
+
* `style-presets.ts` kopyası). Kaskad katmanı değildir: versiyonun prop'ları düğüm oluşturulurken
|
|
28
|
+
* zaten yazılır, bu anahtar yalnız seçicinin hangi satırı seçili göstereceğini söyler.
|
|
29
|
+
*/
|
|
30
|
+
export const STYLE_COMPONENT_PRESET_KEY = '$componentPreset';
|
|
25
31
|
/**
|
|
26
32
|
* Satır yüksekliği ÜÇ yazımı da kabul eder: oran (1.4), yüzde ('140%') ve CSS
|
|
27
33
|
* uzunluğu ('24px'). Çıplak sayı ikircikli tek durumdur — eski panel YÜZDE
|
|
@@ -53,17 +59,24 @@ export const deepMergeStyle = (base, override) => {
|
|
|
53
59
|
};
|
|
54
60
|
export const resolveNodeStyleLayers = (style, source, nodeType) => {
|
|
55
61
|
const styleObject = isPlainObject(style) ? style : undefined;
|
|
56
|
-
|
|
62
|
+
// İKİ sihirli anahtar da düşer. `$componentPreset` bir kaskad katmanı DEĞİLDİR: bileşen preseti
|
|
63
|
+
// versiyonu düğüm kurulurken prop'lara materialize edilir, bu anahtar yalnız "hangi versiyondan
|
|
64
|
+
// geldi"yi anar (`style-presets.ts:180-184`, 2026-09-07). Ayıklanmazsa emotion onu çöp bir CSS
|
|
65
|
+
// kuralına çevirir ve `resolvedStyle` ajana stil diye '$componentPreset: V1' gösterir (S-2).
|
|
66
|
+
const { [STYLE_PRESET_KEY]: presetName, [STYLE_COMPONENT_PRESET_KEY]: componentPreset, ...own } = styleObject ?? {};
|
|
57
67
|
const componentStyle = nodeType && isPlainObject(source?.componentStyles?.[nodeType]) ? source.componentStyles[nodeType] : undefined;
|
|
58
68
|
const preset = typeof presetName === 'string' && isPlainObject(source?.presets?.[presetName]) ? source.presets[presetName] : undefined;
|
|
59
69
|
let theme;
|
|
60
70
|
if (componentStyle || preset) {
|
|
61
71
|
theme = deepMergeStyle(componentStyle ?? {}, preset ?? {});
|
|
72
|
+
// Preset'in KENDİSİ de işaret taşıyabilir: tema JSON'u elle düzenlenmişse.
|
|
62
73
|
delete theme[STYLE_PRESET_KEY];
|
|
74
|
+
delete theme[STYLE_COMPONENT_PRESET_KEY];
|
|
63
75
|
}
|
|
76
|
+
const version = typeof componentPreset === 'string' && componentPreset ? componentPreset : undefined;
|
|
64
77
|
if (!styleObject && typeof style === 'string' && style)
|
|
65
|
-
return { theme, own: style };
|
|
66
|
-
return { theme, own: Object.keys(own).length ? own : undefined };
|
|
78
|
+
return { theme, own: style, componentPreset: version };
|
|
79
|
+
return { theme, own: Object.keys(own).length ? own : undefined, componentPreset: version };
|
|
67
80
|
};
|
|
68
81
|
export const resolveNodeStyle = (style, source, nodeType) => {
|
|
69
82
|
if (!isPlainObject(style) && style !== undefined && style !== null)
|
|
@@ -308,6 +321,23 @@ export const applyLegacyTextProps = (input, root) => {
|
|
|
308
321
|
: `var(${STATUS_COLOR_VARS[color] ?? STATUS_COLOR_VARS.primary})`;
|
|
309
322
|
}
|
|
310
323
|
};
|
|
324
|
+
/* ---- button: emekli kind:'custom' takma adı (frontend `button-style-logic.ts` kopyası) ---- */
|
|
325
|
+
export const BUTTON_LEGACY_CUSTOM_COLOR_VAR = '--ng-btn-custom-color';
|
|
326
|
+
/**
|
|
327
|
+
* `button`'ın EMEKLİ `kind: 'custom'` + `customColor` ikilisi (Dalga 4, 2026-09-14) kayıtlı
|
|
328
|
+
* sayfalarda aynen çizilir: Surface › Background BOŞSA köke eski `--ng-btn-custom-color`
|
|
329
|
+
* değişkeni yazılır, bileşenin `.ng-button--custom` bloğu okur. Bilerek `background` DEĞİL
|
|
330
|
+
* değişken: eski custom outline/text butonlar zemin değil kenarlık/yazı rengi alıyordu.
|
|
331
|
+
*/
|
|
332
|
+
export const applyLegacyButtonProps = (input, root) => {
|
|
333
|
+
if (root.background !== undefined)
|
|
334
|
+
return;
|
|
335
|
+
if (input.kind !== 'custom')
|
|
336
|
+
return;
|
|
337
|
+
if (!isRenderableColorLike(input.customColor))
|
|
338
|
+
return;
|
|
339
|
+
root[BUTTON_LEGACY_CUSTOM_COLOR_VAR] = String(input.customColor).trim();
|
|
340
|
+
};
|
|
311
341
|
/* ---- container: başlık bölgesi (frontend `container-style-logic.ts` kopyası) ---- */
|
|
312
342
|
const CONTAINER_HEADER_SELECTOR = '&__header';
|
|
313
343
|
const CONTAINER_TITLE_SELECTOR = '&__title';
|
|
@@ -419,6 +449,20 @@ export const TYPED_STYLE_COMPONENTS = {
|
|
|
419
449
|
// Taşma hedefi olmayan komponentte contentOverflow ölü kural üretirdi.
|
|
420
450
|
omit: { surfaceStyle: ['contentOverflow'] }
|
|
421
451
|
},
|
|
452
|
+
button: {
|
|
453
|
+
groups: ['surfaceStyle'],
|
|
454
|
+
ext: { extra: applyLegacyButtonProps },
|
|
455
|
+
// `kind` emekli değil; takma ad yalnız eski 'custom' değerini okur.
|
|
456
|
+
legacyProps: ['kind', 'customColor'],
|
|
457
|
+
// Kaydırılan içeriği yok; taşma alanı ölü kural üretirdi.
|
|
458
|
+
omit: { surfaceStyle: ['contentOverflow'] }
|
|
459
|
+
},
|
|
460
|
+
form: {
|
|
461
|
+
// Çerçeve <form> KÖKÜNDE: yüzey köke, taşma gövdeye (S-19, 2026-09-15).
|
|
462
|
+
// Genel düğüm yüzeyi yolu değeri kökün dışındaki kutuya yazıp ikinci çerçeve çiziyordu.
|
|
463
|
+
groups: ['surfaceStyle'],
|
|
464
|
+
ext: { overflowTarget: '&__body' }
|
|
465
|
+
},
|
|
422
466
|
icon: {
|
|
423
467
|
// Yalnız hover: surface çipin (chipShape/chipColor), typography da boyutun
|
|
424
468
|
// (size) ve rengin (color) çift kanalı olurdu — görsel kararlar semantik
|
|
@@ -439,7 +483,6 @@ export const TYPED_STYLE_COMPONENTS = {
|
|
|
439
483
|
export const NODE_SURFACE_TYPES = new Set([
|
|
440
484
|
'table',
|
|
441
485
|
'datatable',
|
|
442
|
-
'form',
|
|
443
486
|
'collapsible-container',
|
|
444
487
|
'drawer',
|
|
445
488
|
'modal',
|
|
@@ -511,12 +554,16 @@ export function resolveNodeStyleReport(node, source) {
|
|
|
511
554
|
const ownStyle = rawProp(props, 'style');
|
|
512
555
|
const config = node.type ? TYPED_STYLE_COMPONENTS[node.type] : undefined;
|
|
513
556
|
const presetName = isPlainObject(ownStyle) ? ownStyle[STYLE_PRESET_KEY] : undefined;
|
|
557
|
+
const versionName = isPlainObject(ownStyle) ? ownStyle[STYLE_COMPONENT_PRESET_KEY] : undefined;
|
|
514
558
|
const typedGroups = config ? config.groups.filter(name => isPlainObject(rawProp(props, name))) : [];
|
|
559
|
+
const magicKeys = [STYLE_PRESET_KEY, STYLE_COMPONENT_PRESET_KEY];
|
|
515
560
|
const layers = {
|
|
516
561
|
typedGroups,
|
|
517
562
|
componentStyle: Boolean(node.type && isPlainObject(source?.componentStyles?.[node.type])),
|
|
518
563
|
preset: typeof presetName === 'string' && presetName ? presetName : null,
|
|
519
|
-
|
|
564
|
+
componentPreset: typeof versionName === 'string' && versionName ? versionName : null,
|
|
565
|
+
// İşaretler stil DEĞİLDİR: yalnız onları taşıyan düğüm "kendi stili var" sayılmamalı.
|
|
566
|
+
own: isPlainObject(ownStyle) ? Object.keys(ownStyle).filter(key => !magicKeys.includes(key)).length > 0 : Boolean(ownStyle)
|
|
520
567
|
};
|
|
521
568
|
if (!config) {
|
|
522
569
|
// Kayıtsız tip: eski birleşik kaskad. Kap tiplerinde tipli yüzey grubu
|