@appaflytech/zeyrek-ai-mcp 0.0.22 → 0.0.24
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 +15 -0
- package/dist/config.js +9 -0
- package/dist/index.js +2 -0
- package/dist/knowledge/componentCatalog.js +4 -2
- package/dist/knowledge/componentEvents.js +3 -3
- package/dist/knowledge/mobileComponentCatalog.js +308 -50
- package/dist/knowledge/mobileComponentEvents.js +130 -3
- package/dist/knowledge/normalize.js +16 -6
- package/dist/knowledge/platform/lint.js +110 -3
- package/dist/knowledge/platform/mobile.js +1 -1
- package/dist/knowledge/platform/mobileRules.js +70 -11
- package/dist/knowledge/platform/web.js +1 -1
- package/dist/knowledge/rules.js +106 -11
- package/dist/knowledge/styleResolve.js +49 -3
- package/dist/knowledge/styleResolveNative.js +104 -4
- package/dist/observability/trace.js +1 -1
- package/dist/platform/argumentAliases.js +128 -0
- package/dist/platform/bodyFile.js +103 -0
- package/dist/platform/capture/capture.js +29 -5
- package/dist/platform/capture/console-log.js +80 -0
- package/dist/platform/capture/index.js +1 -0
- package/dist/platform/diagnostics.js +10 -4
- package/dist/platform/http.js +23 -7
- package/dist/platform/responses.js +6 -2
- package/dist/platform/schemaReadMemory.js +60 -0
- package/dist/platform/secrets.js +9 -0
- package/dist/platform/strictArguments.js +68 -12
- package/dist/platform/tokenVault.js +187 -4
- package/dist/resources/guides/expressions.md +1 -1
- package/dist/resources/guides/microservice.md +9 -2
- package/dist/resources/guides/middleware.md +5 -1
- package/dist/resources/guides/mobile.md +165 -10
- package/dist/resources/guides/page.md +161 -41
- package/dist/resources/guides/theme.md +4 -1
- package/dist/resources/guides/workflow.md +7 -1
- package/dist/resources/schema-fingerprint.json +2 -2
- package/dist/server.js +1 -1
- package/dist/tools/datasources.js +1 -1
- package/dist/tools/designSchemaPatch.js +4 -3
- package/dist/tools/externalComponents.js +1 -1
- package/dist/tools/flows.js +70 -4
- package/dist/tools/layouts.js +5 -5
- package/dist/tools/microservices.js +26 -10
- package/dist/tools/pages.js +192 -36
- package/dist/tools/platformInfo.js +4 -1
- package/dist/tools/rows.js +7 -1
- package/dist/tools/schemaOps.js +1 -1
- package/dist/tools/screenWrite.js +4 -3
- package/dist/tools/screens.js +81 -0
- package/package.json +1 -1
package/dist/knowledge/rules.js
CHANGED
|
@@ -137,11 +137,14 @@ export function readsBeforeGuard(source, root) {
|
|
|
137
137
|
* code.ts:49). Eşleşme yoksa flow-engine.ts:79 sessizce durur. Studio tarafında da zincir
|
|
138
138
|
* `sourceBranch === 'always'` ile çizilir (EventActionsStart.svelte:47, EventActionsItem.svelte:298).
|
|
139
139
|
* condition dalları rule id'sidir (serbest), microservice'te 'success' yalnız eski şema için
|
|
140
|
-
* yaşatılır (microservice.ts:
|
|
140
|
+
* yaşatılır (microservice.ts:174 `isHasSuccess`). 'error' dalı 2026-09-26'dan beri motorun
|
|
141
|
+
* KENDİSİ tanır (ADR 0004, ZM-07/g-02): bağlıysa hata anında `always` hiç koşmaz, sürer
|
|
142
|
+
* (microservice.ts:182 `hasBranch`) — eski motor tanımadığı dalı hiç aramadığı için bu ekleme
|
|
143
|
+
* geriye dönük zararsızdır.
|
|
141
144
|
*/
|
|
142
145
|
const flowBranchNames = {
|
|
143
146
|
start: ['always'],
|
|
144
|
-
microservice: ['always', 'success'],
|
|
147
|
+
microservice: ['always', 'success', 'error'],
|
|
145
148
|
confirm: ['yes', 'no', 'always'],
|
|
146
149
|
function: ['always'],
|
|
147
150
|
end: []
|
|
@@ -154,7 +157,7 @@ const flowBranchNames = {
|
|
|
154
157
|
*/
|
|
155
158
|
export const knownFlowStepTypes = new Set([
|
|
156
159
|
'start', 'end', 'query', 'setter', 'download', 'condition', 'redirect', 'setcookie', 'alert', 'confirm',
|
|
157
|
-
'microservice', 'validation', 'flow-trigger', 'storage', 'navigate', 'code', 'function'
|
|
160
|
+
'microservice', 'validation', 'flow-trigger', 'storage', 'navigate', 'code', 'function', 'share'
|
|
158
161
|
]);
|
|
159
162
|
const flowFreeBranchTypes = new Set(['condition']);
|
|
160
163
|
/** `condition.types.ts` ConditionArgumentRuleType — motorun tanıdığı üçlü. */
|
|
@@ -167,7 +170,8 @@ const conditionRuleIdPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}
|
|
|
167
170
|
// Runtime.svelte'de HAM verilir (`argument: step.args`), yani bu alanlarda ifade değerlendirilmez.
|
|
168
171
|
const flowLiteralTextFields = new Map([
|
|
169
172
|
['alert', ['message', 'description']],
|
|
170
|
-
['confirm', ['message', 'title', 'confirmText', 'cancelText']]
|
|
173
|
+
['confirm', ['message', 'title', 'confirmText', 'cancelText']],
|
|
174
|
+
['share', ['title']]
|
|
171
175
|
]);
|
|
172
176
|
const flowExpressionLooking = /\$\{|\b(?:Steps|Page|Row|Widget|LocalStorage|state|self|item|event|error|cookie)\s*(?:\?\.|\.|\[)/;
|
|
173
177
|
// Barındırıcıya özel tipler BURADA kural üretmez. `navigate`i gateway normalizasyonu zaten
|
|
@@ -294,25 +298,71 @@ function writesRolesWithoutSlug(entityId, parameters) {
|
|
|
294
298
|
}
|
|
295
299
|
const rolesSlugMessage = "Built-in 'roles' entity'sine slug YAZMADAN rol satırı ÜRETİLİYOR (operation create/upsert). Insert 200 döner, satır role_list'te görünür ve page.roleIds ile bağlanır — ama yetkilendirme SLUG karşılaştırır: platformun get-user sorgusu roles+user_roles join'inden slug dizisi üretir ve Authorized(user.roles) o rol için hiçbir zaman tutmaz. fields'a slug'ı ekleyin ('super-admin' slug'ını vermeyin — platform süper yöneticiyi o slug ile arar). Upsert'te slug'ı İFADEYLE üretmeyin: UPDATE yolunda ifade yeniden değerlendirilir ve var olan rolün slug'ı sessizce değişir (MS-016) — üretilen slug'ı yalnız create ucunda yazın. Rolü kullanıcıya bağlamak ayrı bir user_roles satırıdır.";
|
|
296
300
|
const contractKeys = ['returnDtoId', 'parameterDtoId', 'returnBehavior', 'parameterBehavior'];
|
|
297
|
-
|
|
301
|
+
/**
|
|
302
|
+
* Filtre işleci sözlüğü — backend `OperatorType` enum'u (paketlenmiş `Filter` şeması; eşitliği
|
|
303
|
+
* `test/filter-operators.test.ts` kilitler). Studio filtre ARAYÜZÜNÜN sözlüğü (`notContains`, `between`…,
|
|
304
|
+
* `componentCatalog.ts`) ayrı bir kavramdır: arayüz onları bu işleçlere çevirir.
|
|
305
|
+
*/
|
|
306
|
+
export const filterOperators = new Set(['eq', 'neq', 'lt', 'lte', 'gt', 'gte', 'contains', 'icontains', 'in', 'notin', 'startswith', 'endswith', 'istartswith', 'iendswith', 'isnull', 'isnotnull']);
|
|
307
|
+
/** Ajanların sık yazdığı yanlış adlar → doğru işleç. Yalnız ÖNERİLİR, sessizce çevrilmez. */
|
|
308
|
+
const filterOperatorSuggestions = {
|
|
309
|
+
ne: 'neq', '!=': 'neq', '<>': 'neq', noteq: 'neq', notequal: 'neq', notequals: 'neq', not_eq: 'neq',
|
|
310
|
+
'=': 'eq', '==': 'eq', equal: 'eq', equals: 'eq',
|
|
311
|
+
'<': 'lt', '<=': 'lte', '>': 'gt', '>=': 'gte',
|
|
312
|
+
like: 'contains', ilike: 'icontains', nin: 'notin', 'not in': 'notin', not_in: 'notin'
|
|
313
|
+
};
|
|
298
314
|
function flattenFilterTree(filters) {
|
|
299
315
|
return filters
|
|
300
316
|
.filter((filter) => Boolean(filter) && typeof filter === 'object')
|
|
301
317
|
.flatMap(filter => [filter, ...flattenFilterTree(asArray(filter.children))]);
|
|
302
318
|
}
|
|
319
|
+
/**
|
|
320
|
+
* Bilinmeyen işleçler, öneriyle: `status: 'ne' (neq mı demek istedin?)`. Grup çocukları da okunur. Ad
|
|
321
|
+
* büyük/küçük harfe DUYARLI eşlenir — backend de öyle eşler (`GetValueFromDescription` `==`); `NEQ`
|
|
322
|
+
* bilinmeyendir ve küçük harfli adı önerilir.
|
|
323
|
+
*/
|
|
324
|
+
function unknownFilterOperators(filters) {
|
|
325
|
+
return flattenFilterTree(filters)
|
|
326
|
+
.filter(filter => filter.operator !== undefined && filter.operator !== null && !filterOperators.has(String(filter.operator)))
|
|
327
|
+
.map(filter => {
|
|
328
|
+
const operator = String(filter.operator);
|
|
329
|
+
const lower = operator.toLowerCase();
|
|
330
|
+
const suggestion = filterOperators.has(lower) ? lower : filterOperatorSuggestions[lower];
|
|
331
|
+
return `${String(filter.fieldSlug ?? filter.fieldId ?? '?')}: '${operator}'${suggestion ? ` (${suggestion} mı demek istedin?)` : ''}`;
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
const unknownOperatorConsequence = "Bilinmeyen işleç yola ve backend sürümüne göre ya 400 alır ya sessizce 'eq'e düşer ve sorgu yanlış sonuç döner (2026-09-27'den sonraki backend uç çağrısının `filters` parametresini 400 ile reddeder; workflow tablo adımı hâlâ sessizce 'eq' yapar); ad büyük/küçük harfe duyarlıdır — doğru işleci yazın";
|
|
303
335
|
function unknownOperatorFindings(ruleId, owner, filters) {
|
|
304
|
-
const unknownOperators =
|
|
305
|
-
.filter(filter => filter.operator !== undefined && filter.operator !== null && !filterOperators.has(String(filter.operator).toLowerCase()))
|
|
306
|
-
.map(filter => `${String(filter.fieldSlug ?? filter.fieldId ?? '?')}: '${String(filter.operator)}'`);
|
|
336
|
+
const unknownOperators = unknownFilterOperators(filters);
|
|
307
337
|
return unknownOperators.length > 0
|
|
308
338
|
? [{
|
|
309
339
|
ruleId,
|
|
310
340
|
severity: 'block',
|
|
311
|
-
message: `${owner}: bilinmeyen filtre
|
|
341
|
+
message: `${owner}: bilinmeyen filtre işleci — ${unknownOperators.join(', ')}. ${unknownOperatorConsequence}. Geçerli sözlük: ${[...filterOperators].join(' · ')} ('like' yok — metin araması için 'contains'/'icontains').`,
|
|
312
342
|
guide: 'ngapps://guides/microservice'
|
|
313
343
|
}]
|
|
314
344
|
: [];
|
|
315
345
|
}
|
|
346
|
+
/**
|
|
347
|
+
* Çalışma anında gelen filtre (`row_list.filters`, `microservice_execute` `filters` parametresi): dizi ya
|
|
348
|
+
* da JSON dizesi. Bilinmeyen işleç varsa hata METNİ döner — araç isteği göndermeden 400 verir
|
|
349
|
+
* (plans/mobil-plantcare-acigi g-03). Ayrıştırılamayan değer backend'e bırakılır.
|
|
350
|
+
*/
|
|
351
|
+
export function filterOperatorError(owner, filters) {
|
|
352
|
+
let parsed = filters;
|
|
353
|
+
if (typeof filters === 'string') {
|
|
354
|
+
try {
|
|
355
|
+
parsed = JSON.parse(filters);
|
|
356
|
+
}
|
|
357
|
+
catch {
|
|
358
|
+
return undefined;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
const unknownOperators = Array.isArray(parsed) ? unknownFilterOperators(parsed) : [];
|
|
362
|
+
return unknownOperators.length > 0
|
|
363
|
+
? `${owner}: bilinmeyen filtre işleci — ${unknownOperators.join(', ')}. İstek GÖNDERİLMEDİ. ${unknownOperatorConsequence}. Geçerli sözlük: ${[...filterOperators].join(' · ')}.`
|
|
364
|
+
: undefined;
|
|
365
|
+
}
|
|
316
366
|
function isReadOperation(parameters) {
|
|
317
367
|
const operation = String(parameters?.operation ?? '').toLowerCase();
|
|
318
368
|
return !writeOperations.has(operation);
|
|
@@ -2365,7 +2415,7 @@ export const policyRules = [
|
|
|
2365
2415
|
id: 'FLOW-022',
|
|
2366
2416
|
module: 'flow',
|
|
2367
2417
|
appliesTo: ['create', 'update'],
|
|
2368
|
-
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)",
|
|
2418
|
+
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). share adımında (2026-09-25) yalnız `title` düz metindir: @ng-apps/flow resolveShareContent onu olduğu gibi verir, `message` ve `path` ifadedir",
|
|
2369
2419
|
check: definition => flowAllSteps(definition)
|
|
2370
2420
|
.flatMap((step) => {
|
|
2371
2421
|
const type = String(step.type ?? '');
|
|
@@ -2378,11 +2428,56 @@ export const policyRules = [
|
|
|
2378
2428
|
.map(field => ({
|
|
2379
2429
|
ruleId: 'FLOW-022',
|
|
2380
2430
|
severity: 'warn',
|
|
2381
|
-
message:
|
|
2431
|
+
message: type === 'share'
|
|
2432
|
+
? `'${flowStepLabel(step)}' share adımının '${field}' alanı ifade içeriyor ama bu alan DÜZ METİNDİR: başlık paylaşım penceresine olduğu gibi gider (iOS'ta e-posta konusu, Android'de seçici başlığı), ifade değerlendirilmez. Değişen içeriği ifade olan 'message' alanına yazın — ör. message: "'Şuna bak: ' + Page.properties.name"; bağlantı 'path' alanındadır.`
|
|
2433
|
+
: `'${flowStepLabel(step)}' ${type} adımının '${field}' alanı ifade içeriyor ama bu alan DÜZ METİNDİR: ${type}.ts task.argument.${field}'i doğrudan ekrana geçirir ve args hiçbir yerde değerlendirilmez (Runtime.svelte \`argument: step.args\`). Kullanıcı ifadenin sonucunu değil ifadenin KENDİSİNİ görür. Ucun mesajını göstermek için bir setter (ya da code) adımı kullanın — value { type: 'code', ref: 'Steps.error?.message' }; hata metninin nerede durduğu page.md §hata yolu'nda.`,
|
|
2382
2434
|
guide: 'ngapps://guides/page'
|
|
2383
2435
|
}));
|
|
2384
2436
|
})
|
|
2385
2437
|
},
|
|
2438
|
+
{
|
|
2439
|
+
id: 'FLOW-027',
|
|
2440
|
+
module: 'flow',
|
|
2441
|
+
appliesTo: ['create', 'update'],
|
|
2442
|
+
sourceRef: "share adımının argümanı StepManagementArgument'ın Title/Message/Path alanlarında saklanır (StepType.cs `share`, kök docs/adr/0001-share-akis-adimi.md); url/link/text gibi anahtarlar sözleşmede YOKTUR ve platform onları 200 ile sessizce düşürür — adım hiçbir şey paylaşmadan akış sürer. `message` ve `path` İFADEDİR (@ng-apps/flow resolveShareContent → runCode): tırnaksız 'https://…' JS'te etiket + yorumdur, undefined döner",
|
|
2443
|
+
check: definition => flowAllSteps(definition)
|
|
2444
|
+
.filter(step => step.type === 'share')
|
|
2445
|
+
.flatMap((step) => {
|
|
2446
|
+
const args = (step.args ?? {});
|
|
2447
|
+
const filled = (key) => typeof args[key] === 'string' && String(args[key]).trim().length > 0;
|
|
2448
|
+
const findings = [];
|
|
2449
|
+
const strayLinks = ['url', 'link', 'href'].filter(filled);
|
|
2450
|
+
const strayTexts = ['text', 'body', 'content'].filter(filled);
|
|
2451
|
+
if (strayLinks.length > 0) {
|
|
2452
|
+
findings.push({
|
|
2453
|
+
ruleId: 'FLOW-027',
|
|
2454
|
+
severity: filled('path') ? 'warn' : 'block',
|
|
2455
|
+
message: `'${flowStepLabel(step)}' share adımının bağlantısı args.${strayLinks.join('/args.')} alanında: bu anahtar StepManagementArgument sözleşmesinde YOK, platform onu 200 ile sessizce düşürür ve adım bağlantısız kalır. Bağlantıyı args.path'e ifade olarak yazın — ör. "'https://ornek.app/urun/' + Page.queryParams.id".`,
|
|
2456
|
+
guide: 'ngapps://guides/page'
|
|
2457
|
+
});
|
|
2458
|
+
}
|
|
2459
|
+
if (strayTexts.length > 0) {
|
|
2460
|
+
findings.push({
|
|
2461
|
+
ruleId: 'FLOW-027',
|
|
2462
|
+
severity: filled('message') ? 'warn' : 'block',
|
|
2463
|
+
message: `'${flowStepLabel(step)}' share adımının metni args.${strayTexts.join('/args.')} alanında: bu anahtar StepManagementArgument sözleşmesinde YOK, platform onu sessizce düşürür. Metni args.message'a ifade olarak yazın — ör. "'Şuna bak: ' + Page.properties.name".`,
|
|
2464
|
+
guide: 'ngapps://guides/page'
|
|
2465
|
+
});
|
|
2466
|
+
}
|
|
2467
|
+
for (const key of ['message', 'path']) {
|
|
2468
|
+
const value = typeof args[key] === 'string' ? String(args[key]).trim() : '';
|
|
2469
|
+
if (/^[a-z][a-z\d+\-.]*:\/\//i.test(value)) {
|
|
2470
|
+
findings.push({
|
|
2471
|
+
ruleId: 'FLOW-027',
|
|
2472
|
+
severity: 'block',
|
|
2473
|
+
message: `'${flowStepLabel(step)}' share adımının '${key}' alanı tırnaksız bir adres: '${value}'. Bu alan İFADEDİR — tırnaksız adres JS'te etiket + yorumdur, undefined döner ve hiçbir şey paylaşılmaz. Dize olarak yazın: "'${value}'".`,
|
|
2474
|
+
guide: 'ngapps://guides/page'
|
|
2475
|
+
});
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
return findings;
|
|
2479
|
+
})
|
|
2480
|
+
},
|
|
2386
2481
|
{
|
|
2387
2482
|
id: 'FLOW-023',
|
|
2388
2483
|
module: 'flow',
|
|
@@ -154,6 +154,24 @@ const resolveShadow = (shadow) => {
|
|
|
154
154
|
return undefined;
|
|
155
155
|
return SHADOW_VALUES[shadow] ?? String(shadow);
|
|
156
156
|
};
|
|
157
|
+
/**
|
|
158
|
+
* Döndür / ölçekle / kaydır alanlarının kabul aralığı — frontend `style-groups/transform-fields.ts`
|
|
159
|
+
* `TRANSFORM_RANGES`'in kopyası (altın vakalar kilitler). Aralık dışı ve birimli değer yazılmaz.
|
|
160
|
+
*/
|
|
161
|
+
export const TRANSFORM_RANGES = {
|
|
162
|
+
rotate: [-360, 360],
|
|
163
|
+
scale: [10, 500],
|
|
164
|
+
translateX: [-1000, 1000],
|
|
165
|
+
translateY: [-1000, 1000]
|
|
166
|
+
};
|
|
167
|
+
/** Sayı ya da sayısal metin, aralığın içinde; aksi `undefined` — frontend `transformValue`. */
|
|
168
|
+
export const transformValue = (value, field) => {
|
|
169
|
+
if (typeof value !== 'number' && (typeof value !== 'string' || value.trim() === ''))
|
|
170
|
+
return undefined;
|
|
171
|
+
const parsed = Number(value);
|
|
172
|
+
const [min, max] = TRANSFORM_RANGES[field];
|
|
173
|
+
return Number.isFinite(parsed) && parsed >= min && parsed <= max ? parsed : undefined;
|
|
174
|
+
};
|
|
157
175
|
/**
|
|
158
176
|
* Grupların alan listeleri — frontend `style-groups/style-group-schemas.ts`
|
|
159
177
|
* `STYLE_GROUP_FIELDS`'in kopyası. Parite testi `components.json`'daki gerçek
|
|
@@ -170,7 +188,11 @@ export const STYLE_GROUP_FIELDS = {
|
|
|
170
188
|
'borderLeft',
|
|
171
189
|
'shadow',
|
|
172
190
|
'contentOverflow',
|
|
173
|
-
'opacity'
|
|
191
|
+
'opacity',
|
|
192
|
+
'rotate',
|
|
193
|
+
'scale',
|
|
194
|
+
'translateX',
|
|
195
|
+
'translateY'
|
|
174
196
|
],
|
|
175
197
|
typographyStyle: [
|
|
176
198
|
'textColor',
|
|
@@ -220,6 +242,21 @@ export function composeTypedStyle(input, ext) {
|
|
|
220
242
|
if (opacity !== undefined && opacity >= 0 && opacity < 100) {
|
|
221
243
|
groups.opacity = String(opacity / 100);
|
|
222
244
|
}
|
|
245
|
+
// Döndür / ölçekle / kaydır (ADR 0005): kaydır → döndür → ölçekle, köken merkez;
|
|
246
|
+
// ölçek yüzdedir, nötr değer ve aralık dışı yazılmaz — frontend `composeSurfaceTransform`.
|
|
247
|
+
const transform = [];
|
|
248
|
+
const x = transformValue(surface.translateX, 'translateX') ?? 0;
|
|
249
|
+
const y = transformValue(surface.translateY, 'translateY') ?? 0;
|
|
250
|
+
if (x !== 0 || y !== 0)
|
|
251
|
+
transform.push(`translate(${x}px, ${y}px)`);
|
|
252
|
+
const rotate = transformValue(surface.rotate, 'rotate');
|
|
253
|
+
if (rotate !== undefined && rotate !== 0)
|
|
254
|
+
transform.push(`rotate(${rotate}deg)`);
|
|
255
|
+
const scale = transformValue(surface.scale, 'scale');
|
|
256
|
+
if (scale !== undefined && scale !== 100)
|
|
257
|
+
transform.push(`scale(${scale / 100})`);
|
|
258
|
+
if (transform.length)
|
|
259
|
+
groups.transform = transform.join(' ');
|
|
223
260
|
// `ROOT_TARGET` (`&`) kökün kendisidir: kendi derleyicisi olmayan kaplarda
|
|
224
261
|
// yüzey düğüm kutusuna yazılır ve kaydırılan da odur.
|
|
225
262
|
if (isSet(surface.contentOverflow) && ext?.overflowTarget) {
|
|
@@ -251,8 +288,10 @@ export function composeTypedStyle(input, ext) {
|
|
|
251
288
|
assign(rules, 'borderColor', hover.borderColor);
|
|
252
289
|
assign(rules, 'boxShadow', resolveShadow(hover.shadow));
|
|
253
290
|
const lift = asDimension(hover.lift);
|
|
254
|
-
|
|
255
|
-
|
|
291
|
+
// Kaldırma düğümün kendi transform'unu ezmez, önüne eklenir.
|
|
292
|
+
if (lift !== undefined) {
|
|
293
|
+
rules.transform = `translateY(calc(-1 * ${lift}))${typeof groups.transform === 'string' ? ` ${groups.transform}` : ''}`;
|
|
294
|
+
}
|
|
256
295
|
if (Object.keys(rules).length) {
|
|
257
296
|
const duration = asNumber(hover.transitionMs) ?? 160;
|
|
258
297
|
groups.transition = `background-color ${duration}ms ease, border-color ${duration}ms ease, box-shadow ${duration}ms ease, transform ${duration}ms ease`;
|
|
@@ -500,6 +539,13 @@ export const NODE_SURFACE_TYPES = new Set([
|
|
|
500
539
|
'qm-sidebar',
|
|
501
540
|
'auth-layout'
|
|
502
541
|
]);
|
|
542
|
+
/**
|
|
543
|
+
* `style`'ı kendi içeriğinin CSS'i olarak alan tipler: `html`in `style`'ı uzun metindir ve yazarın
|
|
544
|
+
* işaretlemesine kapsamlı girer — web'de emotion sınıfı (`html.functions.ts` `resolveHtmlStyleClass`),
|
|
545
|
+
* mobilde korumalı belgenin stil bloğu (plans/mobil-plantcare-acigi f-20). "Düz CSS string style"
|
|
546
|
+
* (PAGE-014) ve "ham CSS çizilmez" (MOB-002) uyarıları bu tiplerde yanlıştır.
|
|
547
|
+
*/
|
|
548
|
+
export const DOCUMENT_STYLE_TYPES = new Set(['html']);
|
|
503
549
|
const NODE_SURFACE_EXTENSION = { overflowTarget: ROOT_TARGET };
|
|
504
550
|
/** Düğüm kutusuna inen yüzey; kap tipi değilse ya da grup boşsa yok. */
|
|
505
551
|
export function composeNodeSurfaceStyle(type, surfaceStyle) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { rawProp } from './styleResolve.js';
|
|
1
|
+
import { TRANSFORM_RANGES, rawProp, transformValue } from './styleResolve.js';
|
|
2
2
|
import { mergeTypeScale, resolveTheme } from './themeResolve.js';
|
|
3
3
|
/* ---- css-value.ts ---- */
|
|
4
4
|
const warn = (context, message) => {
|
|
@@ -45,6 +45,46 @@ export function parseFontFamily(value) {
|
|
|
45
45
|
const first = String(value).split(',')[0].trim().replace(/^["']|["']$/g, '');
|
|
46
46
|
return first || null;
|
|
47
47
|
}
|
|
48
|
+
const GENERIC_FONT_FAMILIES = new Set([
|
|
49
|
+
'serif', 'sans-serif', 'monospace', 'cursive', 'fantasy', 'system-ui',
|
|
50
|
+
'ui-serif', 'ui-sans-serif', 'ui-monospace', 'ui-rounded', 'emoji', 'math', 'fangsong'
|
|
51
|
+
]);
|
|
52
|
+
/** CSS generik ailesi mi (`sans-serif`, `monospace`…) — tırnaklanırsa aile adı sanılır. */
|
|
53
|
+
export function isGenericFontFamily(name) {
|
|
54
|
+
return GENERIC_FONT_FAMILIES.has(name.trim().toLowerCase());
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Yığının yedek generiği: son generik ad, yoksa `sans-serif`. Temanın yığını `"Figtree",
|
|
58
|
+
* sans-serif` biçimindedir; tek ad yazılan `typographyStyle` yedeğini buradan miras alır (ZM-10).
|
|
59
|
+
*/
|
|
60
|
+
export function fontFallback(stack) {
|
|
61
|
+
const generic = String(stack ?? '').split(',').map(part => part.trim().replace(/^["']|["']$/g, '')).filter(isGenericFontFamily).pop();
|
|
62
|
+
return generic ?? 'sans-serif';
|
|
63
|
+
}
|
|
64
|
+
/** Yığının yalın ilk adı (tırnaksız); yükleyicinin istediği ad budur (ZM-10, f-05). */
|
|
65
|
+
export function firstFontFamily(value) {
|
|
66
|
+
return parseFontFamily(value) ?? '';
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Font adı platforma göre çözülür (ZM-10, K3). Frontend'de bu WEB/cihaz diye ikiye ayrılır
|
|
70
|
+
* (`font-family.ts` / `font-family.native.ts`, Metro `.native`'i seçer) ama bu ayna TEK koldur:
|
|
71
|
+
* `test/style-resolve-native.test.ts` frontend'in `theme-native.ts`'ini düz Node `import()` ile
|
|
72
|
+
* okur ve Node/Vite çözümü platform uzantısını (`.native.ts`) hiç görmez — parite testinin
|
|
73
|
+
* kıyasladığı gerçek davranış bu yüzden HER ZAMAN web kolu (`font-family.ts`); cihazdaki yalın-ad
|
|
74
|
+
* davranışı yalnız Metro bundle'ında oluşur ve bu aynanın kapsamı dışındadır (g-02 parity fix).
|
|
75
|
+
* Tek ad generik yedeksiz kalırsa RN web'de font yüklenemediğinde tarayıcı varsayılanı (Times)
|
|
76
|
+
* çizilir; bu yüzden tek ad tırnaklanıp temanın generiğiyle yığına çevrilir, yazarın kendi yığını
|
|
77
|
+
* (virgüllü) aynen geçer.
|
|
78
|
+
*/
|
|
79
|
+
export function resolveFontFamily(value, fallback) {
|
|
80
|
+
const text = value.trim();
|
|
81
|
+
if (text === '' || text.includes(','))
|
|
82
|
+
return text;
|
|
83
|
+
const name = firstFontFamily(text);
|
|
84
|
+
if (isGenericFontFamily(name))
|
|
85
|
+
return name;
|
|
86
|
+
return `"${name}", ${fallback}`;
|
|
87
|
+
}
|
|
48
88
|
export function parseLineHeight(value, fontSize, context) {
|
|
49
89
|
if (value === undefined || value === null || value === '')
|
|
50
90
|
return null;
|
|
@@ -202,6 +242,9 @@ export function resolveThemeNative(theme, mode = 'light') {
|
|
|
202
242
|
if (narrow)
|
|
203
243
|
scale[`${level}Mobile`] = px(narrow, fontSize);
|
|
204
244
|
}
|
|
245
|
+
const bodyStack = vars['--ng-font'] ?? tokens.fontFamily;
|
|
246
|
+
const headingStack = vars['--ng-font-heading'] ?? bodyStack;
|
|
247
|
+
const fallback = fontFallback(bodyStack);
|
|
205
248
|
return {
|
|
206
249
|
mode,
|
|
207
250
|
vars,
|
|
@@ -214,8 +257,11 @@ export function resolveThemeNative(theme, mode = 'light') {
|
|
|
214
257
|
pill: px(vars['--ng-radius-pill'], 999)
|
|
215
258
|
},
|
|
216
259
|
font: {
|
|
217
|
-
family:
|
|
218
|
-
headingFamily:
|
|
260
|
+
family: resolveFontFamily(bodyStack, fallback),
|
|
261
|
+
headingFamily: resolveFontFamily(headingStack, fallback),
|
|
262
|
+
source: firstFontFamily(bodyStack),
|
|
263
|
+
headingSource: firstFontFamily(headingStack),
|
|
264
|
+
fallback,
|
|
219
265
|
size: fontSize,
|
|
220
266
|
weight: vars['--ng-font-weight'] ?? tokens.fontWeight,
|
|
221
267
|
scale
|
|
@@ -302,6 +348,55 @@ const composeBorders = (surface, shell, theme, context) => {
|
|
|
302
348
|
shell.borderStyle = style;
|
|
303
349
|
}
|
|
304
350
|
};
|
|
351
|
+
/** Yüzey grubunun bilinen alanları; kalanı uyarıya yazılır (frontend `SURFACE_KEYS`). MOB-002 de buradan okur. */
|
|
352
|
+
export const SURFACE_KEYS = new Set([
|
|
353
|
+
'background',
|
|
354
|
+
'radius',
|
|
355
|
+
'borderTop',
|
|
356
|
+
'borderRight',
|
|
357
|
+
'borderBottom',
|
|
358
|
+
'borderLeft',
|
|
359
|
+
'shadow',
|
|
360
|
+
'contentOverflow',
|
|
361
|
+
'opacity',
|
|
362
|
+
'rotate',
|
|
363
|
+
'scale',
|
|
364
|
+
'translateX',
|
|
365
|
+
'translateY',
|
|
366
|
+
'borderWidth',
|
|
367
|
+
'borderStyle',
|
|
368
|
+
'borderColor'
|
|
369
|
+
]);
|
|
370
|
+
/**
|
|
371
|
+
* Döndür / ölçekle / kaydır (ADR 0005): web ile aynı sıra ve aralık, RN dizisi. Dolu ama geçersiz
|
|
372
|
+
* değer (birimli metin, aralık dışı) uyarıya yazılır — frontend `composeTransform`.
|
|
373
|
+
*/
|
|
374
|
+
const composeTransform = (surface, shell, warnings) => {
|
|
375
|
+
const read = (field) => {
|
|
376
|
+
const raw = surface[field];
|
|
377
|
+
if (!isSet(raw))
|
|
378
|
+
return undefined;
|
|
379
|
+
const value = transformValue(raw, field);
|
|
380
|
+
if (value === undefined) {
|
|
381
|
+
const [min, max] = TRANSFORM_RANGES[field];
|
|
382
|
+
warnings?.push(`surfaceStyle.${field}: ${String(raw)} geçersiz — ${min}…${max} arası sayı, atlandı`);
|
|
383
|
+
}
|
|
384
|
+
return value;
|
|
385
|
+
};
|
|
386
|
+
const transform = [];
|
|
387
|
+
const x = read('translateX') ?? 0;
|
|
388
|
+
const y = read('translateY') ?? 0;
|
|
389
|
+
if (x !== 0 || y !== 0)
|
|
390
|
+
transform.push({ translateX: x }, { translateY: y });
|
|
391
|
+
const rotate = read('rotate');
|
|
392
|
+
if (rotate !== undefined && rotate !== 0)
|
|
393
|
+
transform.push({ rotate: `${rotate}deg` });
|
|
394
|
+
const scale = read('scale');
|
|
395
|
+
if (scale !== undefined && scale !== 100)
|
|
396
|
+
transform.push({ scale: scale / 100 });
|
|
397
|
+
if (transform.length)
|
|
398
|
+
shell.transform = transform;
|
|
399
|
+
};
|
|
305
400
|
const composeSurface = (surface, shell, theme, context) => {
|
|
306
401
|
const background = asText(surface.background, theme);
|
|
307
402
|
if (background)
|
|
@@ -325,12 +420,17 @@ const composeSurface = (surface, shell, theme, context) => {
|
|
|
325
420
|
if (overflow === 'auto')
|
|
326
421
|
context.warnings?.push('contentOverflow: auto — RN kaydırmayı bileşenin kendisi açar, kutu hidden yapıldı');
|
|
327
422
|
}
|
|
423
|
+
composeTransform(surface, shell, context.warnings);
|
|
424
|
+
for (const [key, value] of Object.entries(surface)) {
|
|
425
|
+
if (!SURFACE_KEYS.has(key) && isSet(value))
|
|
426
|
+
context.warnings?.push(`surfaceStyle.${key}: mobilde karşılığı yok, atlandı`);
|
|
427
|
+
}
|
|
328
428
|
};
|
|
329
429
|
const composeTypography = (typography, text, theme, context) => {
|
|
330
430
|
const color = asText(typography.textColor, theme);
|
|
331
431
|
if (color)
|
|
332
432
|
text.color = color;
|
|
333
|
-
const family =
|
|
433
|
+
const family = resolveFontFamily(asText(typography.fontFamily, theme) ?? '', theme.font.fallback);
|
|
334
434
|
if (family)
|
|
335
435
|
text.fontFamily = family;
|
|
336
436
|
const fontSize = parsePanelLength(typeof typography.fontSize === 'string' ? resolveThemeToken(typography.fontSize, theme) : typography.fontSize, context);
|
|
@@ -351,7 +351,7 @@ export function logGatewayEvent(message) {
|
|
|
351
351
|
*/
|
|
352
352
|
export function logCapabilityGap(gap) {
|
|
353
353
|
const lines = [
|
|
354
|
-
`┌─ ${clockOf(new Date())} · KABİLİYET BOŞLUĞU · ${gap.severity}${gap.module ? ` · ${gap.module}` : ''}`,
|
|
354
|
+
`┌─ ${clockOf(new Date())} · KABİLİYET BOŞLUĞU · ${gap.severity}${gap.module ? ` · ${gap.module}` : ''}${gap.project ? ` · proje ${gap.project}` : ''}`,
|
|
355
355
|
`│ hedef : ${condense(gap.goal, maxBodyChars)}`,
|
|
356
356
|
`│ engel : ${condense(gap.blocker, maxBodyChars)}`
|
|
357
357
|
];
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import { PlatformApiError } from './errors.js';
|
|
3
|
+
import { RefResolver, isGuid } from './refs.js';
|
|
4
|
+
const text = (value) => typeof value === 'string' ? value : String(value ?? '');
|
|
5
|
+
const hostAlias = (from, host) => ({
|
|
6
|
+
tool: '*',
|
|
7
|
+
from,
|
|
8
|
+
when: (shape, args) => 'host' in shape && 'hostId' in shape && !(from in shape) && args.host === undefined && args.hostId === undefined,
|
|
9
|
+
apply: async (value) => ({ set: { host, hostId: value }, note: `${from} → host: '${host}', hostId: ${text(value)}` })
|
|
10
|
+
});
|
|
11
|
+
export const ARGUMENT_ALIASES = [
|
|
12
|
+
hostAlias('pageId', 'page'),
|
|
13
|
+
hostAlias('layoutId', 'layout'),
|
|
14
|
+
hostAlias('componentId', 'component'),
|
|
15
|
+
{
|
|
16
|
+
tool: '*',
|
|
17
|
+
from: 'project',
|
|
18
|
+
when: (shape, args) => 'projectId' in shape && !('project' in shape) && args.projectId === undefined,
|
|
19
|
+
apply: async (value, _args, { platform }) => {
|
|
20
|
+
if (isGuid(text(value)))
|
|
21
|
+
return { set: { projectId: text(value) }, note: `project → projectId: ${text(value)}` };
|
|
22
|
+
// Önce slug (`/p/{slug}`), sonra ad: liste arama sonucunda slug ya da ad birebir (büyük/küçük harf duyarsız).
|
|
23
|
+
const bySlug = await platform.org('GET', `/p/${encodeURIComponent(text(value))}`).catch(() => undefined);
|
|
24
|
+
const wanted = text(value).toLocaleLowerCase('tr');
|
|
25
|
+
const listed = bySlug?.data?.id
|
|
26
|
+
? undefined
|
|
27
|
+
: await platform.org('GET', '/projects', { query: { searchTerm: text(value), pageLength: 50 } }).catch(() => undefined);
|
|
28
|
+
const projectId = bySlug?.data?.id ?? listed?.data?.find(project => [project.slug, project.name].some(candidate => candidate?.toLocaleLowerCase('tr') === wanted))?.id;
|
|
29
|
+
if (!projectId)
|
|
30
|
+
throw new PlatformApiError(`'${text(value)}' diye bir proje yok (slug ya da ad); projectId'yi project_list ile bulun.`, 400);
|
|
31
|
+
return { set: { projectId }, note: `project '${text(value)}' → projectId: ${projectId}` };
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
tool: '*',
|
|
36
|
+
from: 'id',
|
|
37
|
+
when: (shape, args) => 'microservice' in shape && 'application' in shape && !('id' in shape) && args.microservice === undefined,
|
|
38
|
+
apply: async (value, args, { platform }) => {
|
|
39
|
+
if (!isGuid(text(value)))
|
|
40
|
+
return { set: { microservice: text(value) }, note: `id → microservice: '${text(value)}'` };
|
|
41
|
+
if (typeof args.application !== 'string')
|
|
42
|
+
throw new PlatformApiError("id bir microservice GUID'i; slug'ını çözmek için 'application' (uygulama slug'ı) da verin.", 400);
|
|
43
|
+
const resolver = new RefResolver(platform, args.project, args.environment);
|
|
44
|
+
const applicationId = await resolver.applicationId(args.application);
|
|
45
|
+
const payload = await platform.tenant('GET', args.project, `/microservices/${applicationId}/${text(value)}`, { environment: args.environment });
|
|
46
|
+
const slug = payload?.data?.slug;
|
|
47
|
+
if (!slug)
|
|
48
|
+
throw new PlatformApiError(`'${args.application}' uygulamasında ${text(value)} id'li microservice yok; microservice_list ile slug'ı bulun.`, 400);
|
|
49
|
+
return { set: { microservice: slug }, note: `id ${text(value)} → microservice: '${slug}'` };
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
tool: 'entity_get',
|
|
54
|
+
from: 'applicationId',
|
|
55
|
+
apply: async (value, args, { platform }) => {
|
|
56
|
+
const project = args.project;
|
|
57
|
+
const environment = args.environment;
|
|
58
|
+
const resolver = new RefResolver(platform, project, environment);
|
|
59
|
+
const applicationId = await resolver.applicationId(text(value));
|
|
60
|
+
const entity = await resolver.entity(text(args.id));
|
|
61
|
+
const payload = await platform.tenant('GET', project, `/entities/${entity.datasourceId}/${entity.entityId}`, { environment });
|
|
62
|
+
const linked = (payload?.data?.applications ?? []).some(link => [link.id, link.applicationId].some(id => id?.toLowerCase() === applicationId.toLowerCase()));
|
|
63
|
+
if (!linked) {
|
|
64
|
+
throw new PlatformApiError(`'${text(args.id)}' entity'si '${text(value)}' uygulamasına bağlı değil; entity_get uygulamaya göre değil entity'ye göre çalışır — applicationId'yi kaldırın ya da uygulamanın entity'lerini entity_list {applicationId} ile listeleyin.`, 400);
|
|
65
|
+
}
|
|
66
|
+
return { set: { datasourceId: entity.datasourceId }, note: `applicationId '${text(value)}' → datasourceId: ${entity.datasourceId} (entity '${text(args.id)}' bu uygulamaya bağlı)` };
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
tool: '*',
|
|
71
|
+
from: 'applicationId',
|
|
72
|
+
when: (shape, args) => shape.applicationId !== undefined && typeof args.applicationId === 'string' && !shape.applicationId.safeParse(args.applicationId).success,
|
|
73
|
+
apply: async (value, args, { platform }) => {
|
|
74
|
+
const applicationId = await new RefResolver(platform, args.project, args.environment).applicationId(text(value));
|
|
75
|
+
return { set: { applicationId }, note: `applicationId '${text(value)}' → ${applicationId}` };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
];
|
|
79
|
+
export async function applyArgumentAliases(tool, args, context) {
|
|
80
|
+
if (!args || typeof args !== 'object' || Array.isArray(args))
|
|
81
|
+
return { args, applied: [] };
|
|
82
|
+
let current = { ...args };
|
|
83
|
+
const applied = [];
|
|
84
|
+
for (const alias of ARGUMENT_ALIASES) {
|
|
85
|
+
if (alias.tool !== '*' && alias.tool !== tool)
|
|
86
|
+
continue;
|
|
87
|
+
if (!(alias.from in current))
|
|
88
|
+
continue;
|
|
89
|
+
if (!(alias.when ? alias.when(context.shape, current) : !(alias.from in context.shape)))
|
|
90
|
+
continue;
|
|
91
|
+
const outcome = await alias.apply(current[alias.from], current, context);
|
|
92
|
+
const { [alias.from]: _replaced, ...rest } = current;
|
|
93
|
+
current = { ...rest, ...outcome.set };
|
|
94
|
+
applied.push(outcome.note);
|
|
95
|
+
}
|
|
96
|
+
return { args: current, applied };
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Takma adı olmayan bilinmeyen argüman için "bunu mu demek istedin": okunan alanlar arasından büyük/küçük
|
|
100
|
+
* harf duyarsız düzenleme uzaklığı 2'yi geçmeyen en yakın ad (`hostID` → `hostId`, `views` → `view`).
|
|
101
|
+
*/
|
|
102
|
+
export function closestArgument(key, accepted) {
|
|
103
|
+
const distance = (left, right) => {
|
|
104
|
+
const row = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
105
|
+
for (let i = 1; i <= left.length; i++) {
|
|
106
|
+
let diagonal = row[0] ?? 0;
|
|
107
|
+
row[0] = i;
|
|
108
|
+
for (let j = 1; j <= right.length; j++) {
|
|
109
|
+
const above = row[j] ?? 0;
|
|
110
|
+
row[j] = Math.min(above + 1, (row[j - 1] ?? 0) + 1, diagonal + (left[i - 1] === right[j - 1] ? 0 : 1));
|
|
111
|
+
diagonal = above;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return row[right.length] ?? 0;
|
|
115
|
+
};
|
|
116
|
+
const ranked = accepted
|
|
117
|
+
.map(name => ({ name, score: distance(key.toLowerCase(), name.toLowerCase()) }))
|
|
118
|
+
.filter(entry => entry.score <= 2)
|
|
119
|
+
.sort((left, right) => left.score - right.score);
|
|
120
|
+
return ranked[0]?.name;
|
|
121
|
+
}
|
|
122
|
+
const activeNotes = new AsyncLocalStorage();
|
|
123
|
+
export function runWithArgumentNotes(notes, run) {
|
|
124
|
+
return activeNotes.run(notes, run);
|
|
125
|
+
}
|
|
126
|
+
export function argumentNotes() {
|
|
127
|
+
return activeNotes.getStore() ?? { aliasesApplied: [] };
|
|
128
|
+
}
|