@appaflytech/zeyrek-ai-mcp 0.0.48 → 0.0.50
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 +5 -0
- package/dist/config.js +2 -0
- package/dist/knowledge/access.js +4 -3
- package/dist/knowledge/assistantRun.js +165 -0
- package/dist/knowledge/componentCatalog.js +31 -0
- package/dist/knowledge/componentEvents.js +4 -1
- package/dist/knowledge/mobileComponentCatalog.js +21 -3
- package/dist/knowledge/mobileComponentEvents.js +9 -0
- package/dist/knowledge/mobileStore.js +14 -0
- package/dist/knowledge/motionResolve.js +1 -1
- package/dist/knowledge/platform/lint.js +36 -3
- package/dist/knowledge/platform/mobileRules.js +6 -0
- package/dist/knowledge/styleResolve.js +7 -0
- package/dist/platform/capture/actions.js +67 -0
- package/dist/platform/capture/capture.js +26 -21
- package/dist/platform/errors.js +9 -0
- package/dist/platform/http.js +7 -2
- package/dist/platform/storeArt/compose.js +114 -0
- package/dist/platform/storeArt/deck.js +133 -0
- package/dist/platform/storeArt/gallery.js +331 -0
- package/dist/platform/storeArt/layouts.js +121 -0
- package/dist/platform/storeArt/styles.js +54 -0
- package/dist/platform/tokenVault.js +15 -0
- package/dist/resources/examples/mobile-reorder-persist.json +491 -0
- package/dist/resources/guides/assistant.md +194 -0
- package/dist/resources/guides/deployment.md +19 -1
- package/dist/resources/guides/microservice.md +7 -0
- package/dist/resources/guides/mobile-publish.md +13 -0
- package/dist/resources/guides/mobile.md +4 -0
- package/dist/resources/guides/page.md +62 -0
- package/dist/resources/prompts/mobile-publish-prepare.md +3 -1
- package/dist/resources/schema-fingerprint.json +4 -4
- package/dist/resources/schemas/AccessMatrixDto.json +2 -1
- package/dist/resources/schemas/AccessPermissionDto.json +2 -1
- package/dist/resources/schemas/AccessResourceDto.json +2 -1
- package/dist/resources/schemas/AssistantCapabilitiesDto.json +256 -0
- package/dist/resources/schemas/AssistantPublishStateDto.json +57 -0
- package/dist/resources/schemas/AssistantScenarioDto.json +91 -0
- package/dist/resources/schemas/AssistantScenarioRunDto.json +62 -0
- package/dist/resources/schemas/AssistantSettingsDto.json +52 -0
- package/dist/resources/schemas/AssistantSkillDto.json +78 -0
- package/dist/resources/schemas/AssistantSummaryDto.json +100 -0
- package/dist/resources/schemas/CreateAssistantScenarioCommand.json +81 -0
- package/dist/resources/schemas/CreateAssistantSkillCommand.json +83 -0
- package/dist/resources/schemas/CreateMicroserviceCommand.json +16 -0
- package/dist/resources/schemas/DescribeAssistantCapabilityCommand.json +145 -0
- package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +5 -1
- package/dist/resources/schemas/MicroserviceDto.json +16 -0
- package/dist/resources/schemas/PublishAssistantCommand.json +15 -0
- package/dist/resources/schemas/RunAssistantScenariosCommand.json +39 -0
- package/dist/resources/schemas/SimulateAssistantCommand.json +106 -0
- package/dist/resources/schemas/UpdateAssistantScenarioCommand.json +79 -0
- package/dist/resources/schemas/UpdateAssistantSettingsCommand.json +52 -0
- package/dist/resources/schemas/UpdateAssistantSkillCommand.json +81 -0
- package/dist/resources/schemas/UpdateMicroserviceCommand.json +16 -0
- package/dist/resources/store-art/editor.css +389 -0
- package/dist/resources/store-art/editor.html +123 -0
- package/dist/resources/store-art/editor.js +990 -0
- package/dist/server.js +2 -0
- package/dist/tools/applications.js +4 -4
- package/dist/tools/assistant.js +382 -0
- package/dist/tools/deployments.js +9 -3
- package/dist/tools/microservices.js +1 -1
- package/dist/tools/mobile.js +2 -2
- package/dist/tools/mobileStore.js +56 -19
- package/dist/tools/pages.js +21 -5
- package/dist/tools/projects.js +2 -2
- package/dist/tools/templates.js +1 -1
- package/dist/tools/themes.js +2 -2
- package/package.json +2 -2
package/.env.example
CHANGED
|
@@ -78,3 +78,8 @@ TENANT_SWAGGER_URL=
|
|
|
78
78
|
MODULES=
|
|
79
79
|
|
|
80
80
|
MCP_PORT=5100
|
|
81
|
+
|
|
82
|
+
# Uygulama asistanı (Anthropic). docker-compose*.yml Assistant__ApiKey / Assistant__WorkspaceId buradan okur;
|
|
83
|
+
# anahtar appsettings.json'a ya da compose dosyasına yazılmaz. Kullanıcı anahtarı (sk-ant-usr-…) çalışma alanı kimliği ister.
|
|
84
|
+
ASSISTANT_API_KEY=
|
|
85
|
+
ANTHROPIC_WORKSPACE_ID=
|
package/dist/config.js
CHANGED
|
@@ -48,6 +48,7 @@ export const moduleNames = [
|
|
|
48
48
|
"caseRuntime",
|
|
49
49
|
"module",
|
|
50
50
|
"mobile",
|
|
51
|
+
"assistant",
|
|
51
52
|
];
|
|
52
53
|
export const modulePresets = {
|
|
53
54
|
core: [
|
|
@@ -103,6 +104,7 @@ export const modulePresets = {
|
|
|
103
104
|
"revision",
|
|
104
105
|
"connectData",
|
|
105
106
|
"mobile",
|
|
107
|
+
"assistant",
|
|
106
108
|
],
|
|
107
109
|
admin: ["platform", "template", "organization", "deployment", "environment", "merge", "activity"],
|
|
108
110
|
};
|
package/dist/knowledge/access.js
CHANGED
|
@@ -53,7 +53,7 @@ export function systemManagedEntity(ref) {
|
|
|
53
53
|
}
|
|
54
54
|
/**
|
|
55
55
|
* İzin kataloğu (`AccessPermissionConstants`, b-22 — 43 slug; BPM b-01 beşini ekledi, 2026-09-24'te
|
|
56
|
-
* `automation.trigger` çıktı: otomasyon yetki kataloğunda değil — K-2). `supportsScope` altı entity
|
|
56
|
+
* `automation.trigger` çıktı: otomasyon yetki kataloğunda değil — K-2; 2026-10-22'de tür kapsamlı `assistant.use` girdi). `supportsScope` altı entity
|
|
57
57
|
* eyleminde ve `process.view-all`'da, `supportsFieldAccess` yalnız `field.read`'te. Katalog yalnız kompleks projede
|
|
58
58
|
* tohumlanır; kaynağı `access_permission_list`'tir, bu kopya istek gitmeden denetim içindir.
|
|
59
59
|
*/
|
|
@@ -66,10 +66,11 @@ export const accessPermissionSlugs = [
|
|
|
66
66
|
'file.read', 'file.upload', 'file.delete', 'folder.read', 'folder.upload', 'folder.delete',
|
|
67
67
|
'organization-unit.read', 'organization-unit.manage', 'organization-unit.assign',
|
|
68
68
|
'system.access.manage', 'system.access.explain', 'system.user.read', 'system.user.manage',
|
|
69
|
-
'process.start', 'process.manage', 'process.view-all', 'task.complete', 'task.reassign'
|
|
69
|
+
'process.start', 'process.manage', 'process.view-all', 'task.complete', 'task.reassign',
|
|
70
|
+
'assistant.use'
|
|
70
71
|
];
|
|
71
72
|
export const scopedPermissions = new Set(['entity.read', 'entity.update', 'entity.delete', 'entity.export', 'entity.reveal', 'entity.reassign', 'process.view-all']);
|
|
72
|
-
export const accessResourceTypes = ['entity', 'field', 'row', 'microservice', 'page', 'workflow', 'automation', 'ui-flow', 'function', 'microfunction', 'query', 'view', 'file', 'folder', 'organization-unit', 'system', 'process', 'task'];
|
|
73
|
+
export const accessResourceTypes = ['entity', 'field', 'row', 'microservice', 'page', 'workflow', 'automation', 'ui-flow', 'function', 'microfunction', 'query', 'view', 'file', 'folder', 'organization-unit', 'system', 'process', 'task', 'assistant'];
|
|
73
74
|
export const accessScopes = ['own', 'unit', 'unit-tree', 'subordinates', 'function', 'company', 'all'];
|
|
74
75
|
const knownPermissions = new Set(accessPermissionSlugs);
|
|
75
76
|
const conditionSubjects = new Set(['userId', 'unitIds', 'positionIds', 'companyIds', 'roleSlugs', 'functionCodes']);
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { maskSecrets } from '../platform/secrets.js';
|
|
2
|
+
/** `event: x\ndata: {...}\n\n` bloklarını olay listesine çevirir; bozuk bir veri satırı atlanır */
|
|
3
|
+
export function parseSseEvents(text) {
|
|
4
|
+
const events = [];
|
|
5
|
+
for (const block of text.split(/\r?\n\r?\n/)) {
|
|
6
|
+
let type = 'message';
|
|
7
|
+
const dataLines = [];
|
|
8
|
+
for (const line of block.split(/\r?\n/)) {
|
|
9
|
+
if (line.startsWith('event:'))
|
|
10
|
+
type = line.slice(6).trim();
|
|
11
|
+
else if (line.startsWith('data:'))
|
|
12
|
+
dataLines.push(line.slice(5).trim());
|
|
13
|
+
}
|
|
14
|
+
if (dataLines.length === 0)
|
|
15
|
+
continue;
|
|
16
|
+
try {
|
|
17
|
+
events.push({ type, data: JSON.parse(dataLines.join('\n')) });
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
events.push({ type, data: { raw: dataLines.join('\n') } });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return events;
|
|
24
|
+
}
|
|
25
|
+
const BLOCK_SAMPLE_ROWS = 20;
|
|
26
|
+
const BLOCK_ROW_FIELDS = ['id', 'label', 'status', 'statusLabel', 'href'];
|
|
27
|
+
/** Senaryo koşusunun ve simülasyonun olay akışını tek özet nesneye indirir; metin yalnız cevap olayından gelir */
|
|
28
|
+
export function summarizeAssistantRun(events) {
|
|
29
|
+
const summary = {};
|
|
30
|
+
const scenarios = [];
|
|
31
|
+
const calls = [];
|
|
32
|
+
const sources = [];
|
|
33
|
+
let text = '';
|
|
34
|
+
for (const event of events) {
|
|
35
|
+
const data = event.data;
|
|
36
|
+
switch (event.type) {
|
|
37
|
+
case 'scenario':
|
|
38
|
+
scenarios.push({ code: String(data.code ?? ''), status: String(data.status ?? ''), reason: data.reason ?? null, calledCapabilities: data.calledCapabilities ?? [], durationMs: Number(data.durationMs ?? 0), ...(data.answer !== undefined ? { answer: data.answer ?? null } : {}), ...(data.refusal !== undefined ? { refusal: data.refusal ?? null } : {}) });
|
|
39
|
+
break;
|
|
40
|
+
case 'skill':
|
|
41
|
+
summary.skill = { slug: String(data.slug ?? ''), name: String(data.name ?? '') };
|
|
42
|
+
break;
|
|
43
|
+
case 'tool_start':
|
|
44
|
+
calls.push({ slug: String(data.slug ?? ''), statusCode: null, bytes: 0, denied: false, ...(data.arguments != null ? { arguments: maskSecrets(data.arguments).payload } : {}) });
|
|
45
|
+
break;
|
|
46
|
+
case 'tool_result': {
|
|
47
|
+
const call = calls.find(candidate => candidate.slug === String(data.slug ?? '') && candidate.statusCode === null);
|
|
48
|
+
if (call)
|
|
49
|
+
Object.assign(call, { statusCode: typeof data.statusCode === 'number' ? data.statusCode : null, bytes: Number(data.bytes ?? 0), denied: data.denied === true });
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
case 'source':
|
|
53
|
+
sources.push(String(data.slug ?? ''));
|
|
54
|
+
break;
|
|
55
|
+
case 'suggestion': {
|
|
56
|
+
const args = data.arguments ?? {};
|
|
57
|
+
const choices = Array.isArray(data.choices) ? data.choices : [];
|
|
58
|
+
const properties = (data.inputSchema?.properties) ?? {};
|
|
59
|
+
summary.suggestion = {
|
|
60
|
+
suggestionId: String(data.suggestionId ?? ''),
|
|
61
|
+
slug: String(data.slug ?? ''),
|
|
62
|
+
httpMethod: String(data.httpMethod ?? ''),
|
|
63
|
+
arguments: args,
|
|
64
|
+
missing: data.missing ?? [],
|
|
65
|
+
fields: Object.keys(args),
|
|
66
|
+
choices: choices.map(choice => ({ field: String(choice.field ?? ''), total: Number(choice.total ?? 0), searchable: choice.searchable === true, preselected: typeof choice.preselected === 'string' ? choice.preselected : null, options: (choice.options ?? []).map(option => String(option.label ?? '')) })),
|
|
67
|
+
choicesError: data.choicesError ?? {},
|
|
68
|
+
display: data.display ?? {},
|
|
69
|
+
titles: Object.fromEntries(Object.entries(properties).filter(([, property]) => typeof property?.title === 'string').map(([key, property]) => [key, String(property.title)])),
|
|
70
|
+
formHref: typeof data.formHref === 'string' ? data.formHref : null
|
|
71
|
+
};
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
case 'refusal':
|
|
75
|
+
summary.refusal = String(data.kind ?? '');
|
|
76
|
+
break;
|
|
77
|
+
case 'text':
|
|
78
|
+
text += String(data.text ?? '');
|
|
79
|
+
break;
|
|
80
|
+
case 'clarify':
|
|
81
|
+
summary.clarify = String(data.question ?? '');
|
|
82
|
+
break;
|
|
83
|
+
case 'usage':
|
|
84
|
+
summary.usage = data;
|
|
85
|
+
break;
|
|
86
|
+
case 'error':
|
|
87
|
+
summary.error = { code: String(data.code ?? ''), message: String(data.message ?? '') };
|
|
88
|
+
break;
|
|
89
|
+
case 'block': {
|
|
90
|
+
const block = data.data ?? {};
|
|
91
|
+
const rows = Array.isArray(block.rows) ? block.rows.length : block.fields ? 1 : 0;
|
|
92
|
+
const sample = Array.isArray(block.rows)
|
|
93
|
+
? block.rows.slice(0, BLOCK_SAMPLE_ROWS).map(row => Object.fromEntries(BLOCK_ROW_FIELDS.filter(field => row[field] !== undefined && row[field] !== null).map(field => [field, row[field]])))
|
|
94
|
+
: undefined;
|
|
95
|
+
summary.blocks = [...(summary.blocks ?? []), { kind: String(data.kind ?? ''), capability: String(block.capability ?? ''), rows, total: Number(block.total ?? rows), truncated: block.truncated === true, ...(sample ? { sample } : {}), ...(block.href ? { path: block.path, href: block.href } : {}) }];
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case 'stage':
|
|
99
|
+
summary.stages = [...(summary.stages ?? []), { stage: String(data.stage ?? ''), status: String(data.status ?? ''), ...(data.detail != null ? { detail: data.detail } : {}) }];
|
|
100
|
+
break;
|
|
101
|
+
case 'published':
|
|
102
|
+
summary.published = data;
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (scenarios.length > 0) {
|
|
107
|
+
summary.scenarios = scenarios;
|
|
108
|
+
summary.passed = scenarios.filter(scenario => scenario.status === 'passed').length;
|
|
109
|
+
summary.failed = scenarios.filter(scenario => scenario.status === 'failed').length;
|
|
110
|
+
summary.skipped = scenarios.filter(scenario => scenario.status === 'skipped').length;
|
|
111
|
+
}
|
|
112
|
+
if (calls.length > 0)
|
|
113
|
+
summary.calls = calls;
|
|
114
|
+
if (sources.length > 0)
|
|
115
|
+
summary.sources = sources;
|
|
116
|
+
if (text)
|
|
117
|
+
summary.text = text;
|
|
118
|
+
return summary;
|
|
119
|
+
}
|
|
120
|
+
const filledLanguages = (texts) => Object.entries(texts ?? {}).filter(([, text]) => String(text ?? '').trim().length > 0).map(([language]) => language);
|
|
121
|
+
/** Sunucu sayfasını özetler; kind/origin/includeExcluded yalnız bu sayfanın satırlarını süzer, total sunucunun süzgecinden gelir (41 §5) */
|
|
122
|
+
export function summarizeCapabilityPage(page, filter) {
|
|
123
|
+
const total = Number(page.meta?.totalData ?? page.totalData ?? 0);
|
|
124
|
+
const pageIndex = Number(page.pageIndex ?? 0);
|
|
125
|
+
const pageLength = Number(page.pageLength ?? 0);
|
|
126
|
+
const received = page.data ?? [];
|
|
127
|
+
const rows = received.filter(row => {
|
|
128
|
+
const kind = String(row.kind ?? '').toLowerCase();
|
|
129
|
+
if (filter.includeExcluded === false && kind === 'excluded')
|
|
130
|
+
return false;
|
|
131
|
+
if (filter.kind && kind !== filter.kind)
|
|
132
|
+
return false;
|
|
133
|
+
if (filter.origin === 'module' && !row.templateId)
|
|
134
|
+
return false;
|
|
135
|
+
if (filter.origin === 'own' && row.templateId)
|
|
136
|
+
return false;
|
|
137
|
+
return true;
|
|
138
|
+
});
|
|
139
|
+
return {
|
|
140
|
+
total,
|
|
141
|
+
pageIndex,
|
|
142
|
+
pageLength,
|
|
143
|
+
hasMore: (pageIndex + 1) * pageLength < total,
|
|
144
|
+
...(rows.length < received.length ? { hiddenOnPage: received.length - rows.length } : {}),
|
|
145
|
+
capabilities: rows.map(row => {
|
|
146
|
+
const hasDescription = String(row.description ?? '').trim().length > 0;
|
|
147
|
+
return filter.detail === true
|
|
148
|
+
? { ...row, kind: String(row.kind ?? '').toLowerCase(), hasDescription }
|
|
149
|
+
: { id: row.id, slug: row.slug, name: row.name, kind: String(row.kind ?? '').toLowerCase(), origin: row.templateId ? 'module' : 'own', moduleName: row.moduleName ?? null, exclusion: row.exclusion ?? null, hasDescription, descriptionLanguages: filledLanguages(row.descriptions) };
|
|
150
|
+
})
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/** describe gövdesini mevcut hâlin üstüne kurar: verilmeyen alan korunur, null temizler, dil haritaları dil dil birleşir (boş metin o dili siler) */
|
|
154
|
+
export function mergeCapabilityTexts(current, input) {
|
|
155
|
+
return {
|
|
156
|
+
assistantDescription: { ...(current.descriptions ?? {}), ...(input.assistantDescription ?? {}) },
|
|
157
|
+
assistantCaution: input.assistantCaution === null ? {} : { ...(current.cautions ?? {}), ...(input.assistantCaution ?? {}) },
|
|
158
|
+
recordPageId: input.recordPageId === undefined ? current.recordPageId ?? null : input.recordPageId,
|
|
159
|
+
recordQuery: input.recordQuery === undefined ? current.recordQuery ?? null : input.recordQuery,
|
|
160
|
+
formPageId: input.formPageId === undefined ? current.formPageId ?? null : input.formPageId,
|
|
161
|
+
formQuery: input.formQuery === undefined ? current.formQuery ?? null : input.formQuery,
|
|
162
|
+
hints: input.hints === undefined ? current.hints ?? null : input.hints,
|
|
163
|
+
kindOverride: input.kindOverride === undefined ? current.kindOverride ?? null : input.kindOverride
|
|
164
|
+
};
|
|
165
|
+
}
|
|
@@ -1958,6 +1958,37 @@ export const componentKnowledge = {
|
|
|
1958
1958
|
autofocus: 'Sayfa açılınca odak gruba gelir (kilitli/yükleniyor grupta ve tasarım kanvasında çalışmaz). Grubun tek alanı olmadığı için odak ilk kilitsiz kutuya düşer: seç-tümü çiziliyse o, yoksa ilk seçenek. Sayfada tek alana ver.'
|
|
1959
1959
|
}
|
|
1960
1960
|
},
|
|
1961
|
+
assistant: {
|
|
1962
|
+
summary: 'Uygulama asistanı (frontend ADR 0030): tasarımcının layout’a ya da sayfaya koyduğu bileşen. mode launcher (sağ altta düğme + çekmece), panel (gömülü) ya da inline (yalnız soru kutusu). Yetenekleri runtime’dan bir kez ister; asistan uygulamada kapalıysa (404) ya da oturum yoksa (401) HİÇBİR ŞEY çizmez. Cevaplar SSE ile akar: kayıt listesi bloğu (kayıt sayfasına Aç bağlantısı), yazma için onay kartı, ret rozeti.',
|
|
1963
|
+
behavior: [
|
|
1964
|
+
'HANGİ SAYFADA: layout’a konursa o layout’un bütün sayfalarında, sayfaya konursa yalnız o sayfada görünür; giriş ve hata sayfaları layout dışı olduğu için bileşensizdir — kabuk artık asistanı kendiliğinden eklemez (RuntimeShellSlots kaldırıldı, 2026-10-22).',
|
|
1965
|
+
"mode 'launcher': sağ altta (placement ile sol altta) yuvarlak düğme; tıklama ya da hotkey (varsayılan Ctrl+J) genişliği width px olan sabit çekmeceyi açar, Esc kapatır; çekmece açıkken kabuk `--assistant-panel-width` kadar daralır (dock 'push') ya da çekmece sayfanın üstünde açılır (dock 'overlay'; varsayılan 'auto' sayfaya 1024 px'ten az kalacaksa üstte açar); başlatıcı yerleşimde hücre kaplamaz (düğüm sarmalayıcısız çizilir, düğümün boyutu ve kabın flex/grid yeri etkisizdir), tasarımcıda yalnız düğmesi görünür. mode 'panel': düğümün kutusunu dolduran gömülü panel (yüksekliği düğümün yüksekliğidir). mode 'inline': yalnız soru kutusu, cevaplar kutunun ALTINA açılır (ana sayfa arama kutusu deseni).",
|
|
1966
|
+
'Studio önizlemesinde (kanvas) yalnız kabuk çizilir ve sohbet çalışmaz; soru kutusu kapalıdır, günlükte previewNote kelimesi görünür. Gerçek davranış yalnız runtime’da (/dev, /branch, /view) ölçülür.',
|
|
1967
|
+
"KAYIT LİSTESİ: backend `block` olayı (kind 'records') geldiğinde satırlar etiket + durum + tarih + 'Aç' bağlantısıyla çizilir; bağlantı backend’in verdiği href’tir (yetenek `recordPageId` ile eşlenmişse) ve openRecords 'new-tab' (varsayılan) yeni sekmede, 'same-tab' aynı sekmede açar. same-tab’de sohbet sekme içinde saklanır (persistConversation, sessionStorage) ve geri dönünce kaldığı yerden sürer.",
|
|
1968
|
+
'Varsayılan olarak beceri çipi, kaynak çipleri, araç durum satırları ve alt bilgi (model adı, “yetkin kadar görür”) KAPALIDIR; showSkillChip / showSources / showToolStatus / showFooter ile açılır.',
|
|
1969
|
+
'Onay kartı (yazma önerisi) önceki panelle aynıdır: Onayla kullanıcının kendi oturumuyla /m/{app}/{slug} çağırır, Alanları düzenle sonrası “Yeniden öner” olur, Vazgeç kartı kapatır, 30 dk sonra kart solar.',
|
|
1970
|
+
'Çocuk düğümler panel başlığının sağına yerleşir (isDroppable): tasarımcı kendi düğmelerini oraya koyar. inline modda başlık çizilmediği için çocuklar görünmez.',
|
|
1971
|
+
'Bütün metinler bileşenin kelimeleridir (compProp.assistant.<kelime>; title, placeholder, empty, send, openRecord, moreRecords, approve, reject …): düğümün texts ayarıyla ya da uygulama sözlüğüyle değişir, prop değildir.'
|
|
1972
|
+
],
|
|
1973
|
+
propNotes: {
|
|
1974
|
+
mode: "'launcher' (varsayılan) | 'panel' | 'inline'. placement, width, dock, hotkey ve openOnLoad yalnız launcher’da okunur (panelde de o koşulla görünür).",
|
|
1975
|
+
width: 'Çekmece genişliği px (280–900, varsayılan 400); kabuğun daralma payı da budur. 768px altında çekmece tam ekrandır.',
|
|
1976
|
+
dock: "'auto' (varsayılan) | 'push' | 'overlay' — açık çekmecenin yeri (frontend ADR 0034). push sayfayı width kadar iter; overlay sayfanın üstünde gölgeli açılır, sayfa genişliği değişmez; auto pencere − width < 1024 px ise overlay, değilse push (12 sütunlu ızgara itilince harf genişliğine iner). Yalnız launcher'da; mobil ikizde yok.",
|
|
1977
|
+
hotkey: "'Ctrl+J' biçimi: Ctrl/Cmd, Shift, Alt ile tek harf; boş bırakılırsa kısayol yok.",
|
|
1978
|
+
openRecords: "'new-tab' (varsayılan) | 'same-tab' — kayıt listesindeki Aç bağlantısının hedefi.",
|
|
1979
|
+
pageContext: "Sayfa bağlamı (frontend ADR 0032, ASI-019): ekranda seçili değerlere BAĞLANAN nesne, ör. set_binding { prop: 'pageContext', expression: '{{ ({ company_id: state.app.selectedCompanyId }) }}' }. Ad bilerek 'context' değil: renderer her bileşene kendi context'ini ({ children }) verir. Her soruda o anki değer isteğe gider; yalnız metin/sayı/mantıksal değerler, anahtar ^[a-z][a-z0-9_]{0,39}$, en çok 10 anahtar, değer 200 karakterde kesilir — geçmeyen anahtar sessizce düşer. Model bunu araç parametresi için kullanır (firmayı yeniden sormaz); yetki vermez.",
|
|
1980
|
+
persistConversation: 'Açıkken (varsayılan) sohbet sekme içinde saklanır (sessionStorage, düğüm başına anahtar); kapalıyken her sayfa yükü boş başlar.',
|
|
1981
|
+
accent: 'Vurgu rengi (başlatıcı, gönder düğmesi, kullanıcı balonu, Aç bağlantısı); boşsa --ng-accent-primary.',
|
|
1982
|
+
bubbleUser: 'Kullanıcı balonunun zemini; boşsa accent.',
|
|
1983
|
+
bubbleAssistant: 'Asistan balonunun zemini; boşsa tema yüzeyi.',
|
|
1984
|
+
radius: 'Balon, giriş kutusu ve kayıt satırı köşe yarıçapı (tema kademesi ya da CSS uzunluğu); boşsa bileşenin 8–12px tabanı.',
|
|
1985
|
+
density: "'comfortable' (varsayılan) | 'compact' — mesajlar arası boşluk.",
|
|
1986
|
+
surfaceStyle: NODE_SURFACE_NOTE,
|
|
1987
|
+
typographyStyle: STYLE_GROUP_NOTES.typographyStyle + ' ' + STYLE_GROUP_NOTES.colorTokens,
|
|
1988
|
+
style: "Köke uygulanır. Parçalar: '&__launcher', '&__panel', '&__header', '&__log', '&__message', '&__records', '&__composer', '&__send', '&__footer'.",
|
|
1989
|
+
isHidden: 'Ortak katman görünürlüğü: true iken bileşen DOM’a girmez.'
|
|
1990
|
+
}
|
|
1991
|
+
},
|
|
1961
1992
|
emptystate: {
|
|
1962
1993
|
summary: 'Boş ekran/liste bloğu: görsel ya da ikon + başlık + açıklama + aksiyon düğmeleri. Olayları mount ve actionClick.',
|
|
1963
1994
|
behavior: [
|
|
@@ -5,7 +5,7 @@ export const componentEventContracts = {
|
|
|
5
5
|
mount: { payload: {}, reads: 'self.filters · self.sortField · self.sortType · self.offset · self.limit' },
|
|
6
6
|
save: {
|
|
7
7
|
payload: {},
|
|
8
|
-
reads: "self.changeset = { inserted:[{ rowId, values }], updated:[{ rowId, id, patch }], deleted:[{ rowId, id }] } (komponent olaydan ÖNCE yazar). Bağlı tabloda (rows connectData) yazmayı runtime bağlantısı yapar, akış ardından koşar; hatalar self.rowErrors [{ rowId, field?, message }]. İşlemin bağlamı da olaydan ÖNCE yazılır (2026-09-21): self.operation (rowAdd|rowEdit|cellEdit|rowDelete|bulkEdit|bulkDelete|batchSave), self.editedRows (tam satırlar), self.deletedRows (silinen tam satırlar), self.editedCell ({ rowId, field, value, previousValue }, yalnız cellEdit), self.bulkPatch (yalnız bulkEdit). Her işlem için ayrı olay yoktur; tek save akışı self.operation ile ayırır"
|
|
8
|
+
reads: "self.changeset = { inserted:[{ rowId, values }], updated:[{ rowId, id, patch }], deleted:[{ rowId, id }] } (komponent olaydan ÖNCE yazar). Bağlı tabloda (rows connectData) yazmayı runtime bağlantısı yapar, akış ardından koşar; hatalar self.rowErrors [{ rowId, field?, message }]. İşlemin bağlamı da olaydan ÖNCE yazılır (2026-09-21): self.operation (rowAdd|rowEdit|cellEdit|rowDelete|bulkEdit|bulkDelete|batchSave|rowReorder), self.editedRows (tam satırlar), self.deletedRows (silinen tam satırlar), self.editedCell ({ rowId, field, value, previousValue }, yalnız cellEdit), self.bulkPatch (yalnız bulkEdit). Her işlem için ayrı olay yoktur; tek save akışı self.operation ile ayırır"
|
|
9
9
|
},
|
|
10
10
|
discard: { payload: {}, reads: 'self.discardedRowIds (vazgeçilen taslak satır kimlikleri)' },
|
|
11
11
|
rowClick: { payload: {}, reads: 'self.selectedRow (tıklanan satırın sunucu satırı; kaydedilmemiş yeni satırda taslak değerleri). Veri hücresine tıklamada yayılır — onay kutusu, ✏/🗑 ve aksiyon düğmeleri tetiklemez; çift tık önce iki rowClick üretir' },
|
|
@@ -475,6 +475,9 @@ export const componentEventContracts = {
|
|
|
475
475
|
retry: NO_DATA
|
|
476
476
|
},
|
|
477
477
|
checkboxgroup: { mount: NO_DATA, change: valueChange('string[] (seçili değerler)'), focus: NO_DATA, blur: NO_DATA },
|
|
478
|
+
assistant: {
|
|
479
|
+
mount: NO_DATA
|
|
480
|
+
},
|
|
478
481
|
emptystate: {
|
|
479
482
|
mount: NO_DATA,
|
|
480
483
|
actionClick: { payload: { text: 'string (aksiyonun metni)', index: 'number' }, reads: "event.data.text / event.data.index (2026-10-01, QA ES-005; self.text / self.index de yazılır). Aksiyonun kendi flow'u varsa tıklamada YALNIZ o akış koşar, bu olaya bağlı akış kendi flow'u olmayan aksiyonlarda koşar; index GÖRÜNEN aksiyonların sırasıdır (isHidden olanlar elenmiş). href dolu satırda olay YİNE yayılır ama aynı sekmede gezinme sayfayı değiştirir ve olayın sayfa-içi sonucu görünmeden kaybolur — ikisini birlikte görmek için satırda isNewTabLink aç" }
|
|
@@ -350,8 +350,9 @@ export const mobileComponentKnowledge = {
|
|
|
350
350
|
'GRUPLAMA bu bileşende YOKTUR: başlıklar altında liste (türe/zamana göre görevler) `grouped-list`tir — sayaç başlık şablonunda `Section.count`, toplu eylem başlıktaki bir `button` (akışı `Section.items` okur).',
|
|
351
351
|
'Düşenler: `cardFill` (web ızgarasında kartı en uzun kartın boyuna uzatır; mobil satır içeriğinin boyundadır).',
|
|
352
352
|
'Çoklu seçim (MOBİLE ÖZGÜ): `selectionMode: multiple` satır başına onay kutusu çizer, dokunuş seçimi değiştirir ve `itemPress`/`change`/`select` YAYILMAZ; `selectionChange { selectedIndices, count }` yayılır. Seçim düğüme yazılır: `{{ <liste>.selectedItems.length }} seçildi`, `<liste>.selectedIndices`. `longPressSelects: true` uzun basışla kipi açar (basılan satır seçili). Kipi kapatmak: setter ile `selectionMode: \'single\'` + `selectedIndices: []` + `selectedItems: []`. Seçim başlığı ayrı bir `app-bar`dır, iki çubuk `isHidden` bağıyla yer değiştirir.',
|
|
353
|
-
'`swipeActions` (eylemin `icon`u etiketin üstünde glif olarak çizilir) ve `itemPressable` MOBİLE ÖZGÜDÜR (webde yok). Jest RN `Animated` + `PanResponder` ile kurulur
|
|
353
|
+
'`swipeActions` (eylemin `icon`u etiketin üstünde glif olarak çizilir) ve `itemPressable` MOBİLE ÖZGÜDÜR (webde yok). Jest RN `Animated` + `PanResponder` ile kurulur (`gesture-handler` kullanılmaz) ve yalnız YATAY harekette yakalanır, böylece dikey kaydırma kaba kalır. Tasarım kipinde satırlar şablon olarak çizildiği için jest hiç bağlanmaz.',
|
|
354
354
|
'`swipe` ve `itemPress` yükleri satırı `row` alanında TAŞIR; akış ifadesindeki `Row`/`RowIndex` bağı ayrı bir konudur (ARC-121).',
|
|
355
|
+
'SÜRÜKLE-SIRALA (MOBİLE ÖZGÜ, 2026-10-27): `reorderable: true` satır sonuna bir tutamaç (⠿) çizer; tutamaçtan sürüklenen satırı kardeşler kayarak yer açar, bırakınca `reorder { fromIndex, toIndex, row, patches }` yayılır. YALNIZ dikey ve tek sütunlu listede (yatay şerit ya da `columnCount` > 1 iken tutamaç çizilmez — MOB-035). `orderField` verilirse satırlar o sayı alanına göre çizilir (boşlar sonda) ve `patches` o alana yazılacak 1 tabanlı yeni sıraları taşır. Liste bırakılan sırayı FARKLI satırlar gelene kadar tutar: aynı satırlar yeniden okunursa (yenileme, aynı yanıt) sıra bozulmaz; kalıcılık akışın işidir ve servis hatasında liste eski sıraya kendiliğinden DÖNMEZ (`platform_guide(\'page\')` §Kural 1g-5). Kenar bandında sayfa kendiliğinden kayar; modal/alt sayfa içinde kaymaz. Tasarım kipinde tutamaç çizilir ama sürüklenmez. Tutamaç gövdeden ayrıdır: gövdedeki dokunuş `itemPress`, yatay kaydırma `swipe` olarak kalır. Erişilebilirlik: tutamaçta "yukarı taşı / aşağı taşı" eylemleri. Tutamacın rol adresi `<elementId>__row-<i>__handle` — yalnız çalışma anında; tasarım ve Düzenle kipinde kimlik basılmaz.',
|
|
355
356
|
'Düşenler: `autoRowSize` (mobil satır her zaman içeriğinin boyundadır), `size` (mobil kart hücresi çocuklarını büyütmez; 2026-09-30), `cardFill` (kart hücre boyuna gerilmez, içeriğinin boyundadır; 2026-10-14).'
|
|
356
357
|
],
|
|
357
358
|
propNotes: {
|
|
@@ -362,7 +363,9 @@ export const mobileComponentKnowledge = {
|
|
|
362
363
|
snap: '`none` · `item` — kaydırma kart + aralık adımına oturur.',
|
|
363
364
|
views: "Sabit iki görünüm: 'item' (satır şablonu) ve 'empty' (boş görünüm). Değer YAZILMAZ; görünümler children'daki iki view düğümüdür, başka şekil 422 `fixed-views`.",
|
|
364
365
|
selectionMode: '`single` (varsayılan, bugünkü `selectedIndex`) · `multiple` (onay kutulu çoklu seçim, `selectionChange`) · `none` (seçim izlenmez).',
|
|
365
|
-
longPressSelects: 'Uzun basış çoklu seçim kipini açar ve basılan satırı seçer.'
|
|
366
|
+
longPressSelects: 'Uzun basış çoklu seçim kipini açar ve basılan satırı seçer.',
|
|
367
|
+
reorderable: 'Tutamaçtan sürükleyerek sıralama (yalnız dikey, tek sütun); bırakınca `reorder` yayılır.',
|
|
368
|
+
orderField: "Sırayı tutan sayı alanının adı (ör. 'sira'); satırlar buna göre çizilir, `reorder.patches[].value` buraya yazılacak sıradır. Boşsa `patches` boş gelir."
|
|
366
369
|
}
|
|
367
370
|
},
|
|
368
371
|
modal: {
|
|
@@ -609,6 +612,21 @@ export const mobileComponentKnowledge = {
|
|
|
609
612
|
peek: 'Komşu sayfaların kenardan görünen payı (dp); en çok genişliğin dörtte biri, sayfalar araya küçük boşluk alır. 0 (varsayılan) tam genişlik sayfa.'
|
|
610
613
|
}
|
|
611
614
|
},
|
|
615
|
+
assistant: {
|
|
616
|
+
summary: "Uygulama asistanı (web ikizi, frontend ADR 0033): sohbet sözleşmesi, durum makinesi ve kelimeler web paketiyle ortak; mobil uygulamada `application` ile web ikizinin yayındaki asistanına bağlanır.",
|
|
617
|
+
differences: [
|
|
618
|
+
"`application` mobile özgü: cevaplayan asistanın uygulaması. Mobil uygulamaların kendi ucu yoktur; web ikizinin slug'ı yazılır (`ik-mobil` → `ik`), boşsa sayfanın uygulaması. İstekler sayfanın kendi başlıklarıyla gider (uygulama jetonu `X-Flow-Cookie`, bearer, ortam şeridi).",
|
|
619
|
+
'`launcher` (varsayılan): `placement` köşesinde yüzen düğme (ekran katmanı, `fab` ile aynı yerleşim) tam ekran sohbet katmanını açar; `panel` ve `inline` sohbeti hücresine gömer. Kanvasta yalnız kabuk çizilir, düğme basılamaz.',
|
|
620
|
+
"Kayıt bloğu satırları ve menü yolu metin olarak görünür; 'Aç' ve 'Sayfaya git' düğmesi yoktur (backend web rotası üretir). Sohbet uygulama oturumunda bellekte kalır. Akışlı gövde vermeyen runtime'da cevap sonda bir kerede görünür.",
|
|
621
|
+
'Düşenler: `width`, `dock`, `hotkey`, `openRecords`, `persistConversation`, `showFooter`, `bubbleUser`, `bubbleAssistant`, `density`, `surfaceStyle`, `typographyStyle`.',
|
|
622
|
+
WEB_WORDS_NOTE
|
|
623
|
+
],
|
|
624
|
+
propNotes: {
|
|
625
|
+
application: "Web ikizinin uygulama slug'ı (`ik`, `jira`); asistan ayarı, yetki, yayın ve senaryolar o uygulamanındır.",
|
|
626
|
+
pageContext: "Web'deki gibi: ekranda seçili değerlere bağlanır (`{{ ({ company_id: … }) }}`), her soruyla gider.",
|
|
627
|
+
placement: "`bottom-right` (varsayılan) · `bottom-left`. Uygulamanın bir sayfasında aynı köşede `fab` varsa öbür köşeyi seçin: ikisi aynı ekran katmanında üst üste biner ve fab'ın bir kısmına dokunmak asistanı açar (ölçüldü 2026-10-24, jira-mobil). Köşe uygulamanın bütün sayfalarında aynı kalmalı."
|
|
628
|
+
}
|
|
629
|
+
},
|
|
612
630
|
fab: {
|
|
613
631
|
summary: 'Yüzen eylem düğmesi (FlutterFlow FloatingActionButton): ekranın alt köşesinde durur, sayfa kaydırılırken yerinde kalır. Web`de karşılığı YOKTUR.',
|
|
614
632
|
differences: [
|
|
@@ -982,7 +1000,7 @@ export const mobileComponentKnowledge = {
|
|
|
982
1000
|
'`variant: table` (varsayılan) yatay kaydırılan tablodur: başlık satırı, sıralanabilir kolonda başlığa dokunuş (`<elementId>__sort-<kolon>`, yön oku başlıkta), eylem sütunu satırın sonunda; dikey kaydırma sayfanındır, tablonun üstünden başlayan dikey kaydırma sayfayı kaydırır; kolonların toplamı çerçeveden darsa kolonlar web gibi çerçeveye yayılır (`table-layout: fixed`). `variant: cards` satır başına kart çizer: ilk görünür görsel kolon kartın başına, ilk görsel olmayan kolon başlığa, kalanlar etiket-değer satırlarına; kart kipinde sıralama başlığı yoktur (sıralama akışla yazılır).',
|
|
983
1001
|
'Eylemler web paletiyle (`solid` birincil, `information` kenarlı, `transparent` hayalet, `error`/`danger` yıkıcı, `warning`, `dark`): `actionType: button` satırda düğmeler (`<elementId>__action-<satır>-<sıra>`), `dropdown` ⋯ düğmesi (`<elementId>__more-<satır>`) alttan açılan eylem sayfası (`<elementId>__sheet-action-<sıra>`; cihazda `modal` ile aynı katman). Satırın `_actions` dizisi web gibi eylemi ezer, `isHidden` gizler, `isDisabled` kilitler.',
|
|
984
1002
|
'Sayfalama (`isPaginated`): ‹ `1–10 / 57` › çubuğu (`<elementId>__page-prev`, `__page-next`); metin dil bağımsızdır. Web`in sayfa numarası kutusu ve sayfa başına satır seçimi yok (`limit` panelden ya da akıştan; düğümde hiç yazılmamışsa web gibi 10). Başlık doluyken yanındaki yenile düğmesi (`<elementId>__refresh`) `mount`u koşturur; `showRefresh: false` düğmeyi kaldırır.',
|
|
985
|
-
'Düşenler: `style`, `actionsPinned`, `advancedFilter`, `filterPosition`, `showFilterSummary`, `showExport`, `showColumnManager`, `toolsPosition`, `footerPosition`, `stickyHeader`, `stickyFooter`, `paginationPosition`, `addRowShortcut`, `canReorderRows`, `orderField`, `revealEntity`, `rowDoubleClick`, `cellSelectionChange`; kolonların `relationTypeField` ve `relationTargets` alanı. Gelişmiş filtre oluşturucu, dışa aktarma, kolon menüsü, sürükle-sırala ve korumalı alanı gösterme telefonda yok; uzun veri için yol sayfalama ya da sonsuz kaydırmadır (satır sanallaştırma yok).',
|
|
1003
|
+
'Düşenler: `style`, `actionsPinned`, `advancedFilter`, `filterPosition`, `showFilterSummary`, `showExport`, `showColumnManager`, `toolsPosition`, `footerPosition`, `stickyHeader`, `stickyFooter`, `paginationPosition`, `addRowShortcut`, `canReorderRows`, `orderField`, `revealEntity`, `rowDoubleClick`, `cellSelectionChange`; kolonların `relationTypeField` ve `relationTargets` alanı. Gelişmiş filtre oluşturucu, dışa aktarma, kolon menüsü, sürükle-sırala ve korumalı alanı gösterme telefonda yok (sıralanabilir liste: `repeater` `reorderable`); uzun veri için yol sayfalama ya da sonsuz kaydırmadır (satır sanallaştırma yok).',
|
|
986
1004
|
'Telefonda filtre (plan mobil-tasarimci-kabuk-ekranlari 34): `isFilterable` kolonlar (bağlı tabloda sunucunun kabiliyeti son sözü söyler) üstte çip şerididir (`<elementId>__chip-<kolon>`, başta rozetli `__filters`: etkin çip sayısı, ✕ hepsini temizler). Çipe dokunmak alt sayfa açar: metin kolonunda işleç (sunucunun açtıkları) + değer, seçenekli kolonda liste (`in` açıksa çoklu), anahtarda Evet/Hayır, ilişki kolonunda seçim bileşenlerinin lookup alt sayfası. Uygula `filters`a kök `and` tekil kural yazar (web filtre çubuğuyla aynı sözleşme) ve `offset: 0` ile `mount` koşturur; etkin çip mavi. Bağlı tabloda çipler bağlamanın `behavior.mobile.filterChips`ıyla açılır; `filterMode: none` her durumda kapatır.',
|
|
987
1005
|
'Telefonda düzenleme (34): bağlantının yazdığı `editMode`/`canEditRow`/`canAddRow`/`canDeleteRow`/`bulkActions` okunur. Satıra dokunmak `rowClick`i yayar (`selectedRow` sessiz yazılır) ve düzenlenebilir kolon varsa "Satırı düzenle" alt sayfasını açar (alan türüne göre denetim: metin/sayı/tarih girişi, anahtar, seçenek çipleri, ilişkide lookup). `canAddRow` + `addRowButton` ≠ `none` "Yeni satır" düğmesi (`__add-row`). `bulkActions` açıksa uzun basış seçim kipidir (`rowSelectionChange`, `selectedRows`): çubukta Toplu düzenle (tek alan, bütün seçililere) ve Sil. Bağlamanın `behavior.mobile.swipeDelete`ı kartı kaydırınca Sil (onaylı) açar. Kayıt `save` olayıyla gider (changeset + `operation`, web f-22 ile aynı); `row`/`cell` kipi anında, `batch` alt çubuktaki Kaydet ile (`discard` Vazgeç). Kısmi başarıda hatalı satır kırmızı çerçeve + satır hatası, özet şeridi "2 satır yazıldı, 1 düşmedi" (`__save-outcome`); düşen satırlar seçili kalır.',
|
|
988
1006
|
'Kart alanları ve sayfa (34): `variant: cards` iken kart bağlamanın `behavior.mobileCard`ından çizilir (başlık, sıralı etiket-değer alanları, sağ değer); eşleme yoksa ilk görsel/ilk metin kuralı. `paginationMode: infinite` ya da bağlamanın `behavior.mobile.infiniteScroll`ı (yazılmamışsa açık) sayfaları birleştirir: sayfa kaydırıcısı sona yaklaşınca sonraki `offset` kendiliğinden istenir, yeni sayfa sona eklenir, altta "Yükleniyor…"; sayfa çubuğu çizilmez (sayfa kaydırıcısı olmayan kapta "Daha fazla" `__load-more`). `behavior.mobile.pullToRefresh` sayfayı aşağı çekince listeyi yeniler (web önizlemede çekme yok: başlıkta yenile düğmesi). `showRefresh: false` yenile düğmesini kaldırır. `showRecordSummary: false` kayıt aralığı metnini kaldırır. Bunların ve yukarıdaki çip/düzenleme davranışlarının hepsi yalnız `variant: cards` kipindedir: `variant: table` salt okunur ızgaradır (dokunuş, düzenleme, seçim, çip yok).',
|
|
@@ -295,6 +295,15 @@ export const mobileComponentEventContracts = {
|
|
|
295
295
|
count: 'number — seçili satır sayısı'
|
|
296
296
|
},
|
|
297
297
|
reads: 'yalnız selectionMode: multiple (ya da longPressSelects ile açılınca); akış event.data.selectedIndices / event.data.count okur, düğümün selectedIndices/selectedItems değeri de yazılır'
|
|
298
|
+
},
|
|
299
|
+
reorder: {
|
|
300
|
+
payload: {
|
|
301
|
+
fromIndex: 'number — satırın bırakılmadan önceki sırası (ekrandaki, 0 tabanlı)',
|
|
302
|
+
toIndex: 'number — bırakıldığı sıra',
|
|
303
|
+
row: 'object — taşınan satırın verisi',
|
|
304
|
+
patches: "Array<{ id, value }> — sırası değişen satırlar: id satırın _id'si (yoksa id'si), value orderField'a yazılacak yeni 1 tabanlı adım; eş değerli satırlar tek adımdır (aynı value), iki eş satırın arasına bırakılan satır o adıma katılır. Yalnız değeri değişenler gelir"
|
|
305
|
+
},
|
|
306
|
+
reads: "self'e yazılmaz — event.data.patches ile oku. Yalnız reorderable açık, dikey ve tek sütunlu listede; satır kendi yerine bırakılırsa olay yayılmaz. orderField yoksa ya da bir satırda _id/id yoksa patches BOŞ dizidir (sıra yalnız ekranda tutulur). Seçim varsa düğümün selectedIndices/selectedItems/selectedIndex değeri yeni sıraya taşınarak yazılır"
|
|
298
307
|
}
|
|
299
308
|
}
|
|
300
309
|
};
|
|
@@ -82,6 +82,20 @@ export const SCREENSHOT_CAPTURE_DEVICES = {
|
|
|
82
82
|
tablet7: { width: 600, height: 960, scale: 2 },
|
|
83
83
|
tablet10: { width: 800, height: 1280, scale: 2 }
|
|
84
84
|
};
|
|
85
|
+
/**
|
|
86
|
+
* Pazarlama karesinin cihaz çerçevesi (`mobile_asset_preview`, ADR 0004). Tuval mağaza ölçüsüdür
|
|
87
|
+
* (`SCREENSHOT_CAPTURE_DEVICES` × scale), çerçeve CSS ile çizilir: Apple'ın cihaz görselleri yeniden
|
|
88
|
+
* dağıtılamaz. Cihaz sınıfları arasındaki fark yalnız bu satırlardır, kodda dallanma yoktur.
|
|
89
|
+
*/
|
|
90
|
+
export const STORE_FRAME_LAYOUTS = {
|
|
91
|
+
// Gerçek ekran köşesi ~0.125'tir; runtime karesinde alt güvenli alan olmadığı için o köşe sekme çubuğu
|
|
92
|
+
// etiketlerini kırpıyordu (2026-10-26, mobiltest `ana`) — köşe biraz daraltıldı.
|
|
93
|
+
iphone69: { deviceWidth: 0.78, bezel: 0.03, radius: 0.12, cutout: 'island' },
|
|
94
|
+
ipad13: { deviceWidth: 0.8, bezel: 0.03, radius: 0.05, cutout: 'none' },
|
|
95
|
+
phone: { deviceWidth: 0.76, bezel: 0.028, radius: 0.11, cutout: 'punch' },
|
|
96
|
+
tablet7: { deviceWidth: 0.8, bezel: 0.035, radius: 0.055, cutout: 'none' },
|
|
97
|
+
tablet10: { deviceWidth: 0.8, bezel: 0.03, radius: 0.05, cutout: 'none' }
|
|
98
|
+
};
|
|
85
99
|
/** Beyan anahtarları (`MobileStoreSettings.*Declaration`); iOS appPrivacy, ötekiler Android */
|
|
86
100
|
export const DECLARATION_KEYS = ['appPrivacy', 'dataSafety', 'contentRating', 'targetAudience', 'appAccess'];
|
|
87
101
|
/**
|
|
@@ -19,7 +19,7 @@ export const MOTION_PRESS = ['none', 'scale', 'opacity'];
|
|
|
19
19
|
* taşınan), kabukları çalışma anında boş kutudur — frontend `catalog-schema.ts` `DRAWN_OUTSIDE_SHELL`. Katalogla
|
|
20
20
|
* kilitli (`test/motion-resolve.test.ts`).
|
|
21
21
|
*/
|
|
22
|
-
export const MOTION_OUTSIDE_SHELL = ['drawer', 'fab', 'floating-container', 'modal', 'photo-viewer', 'toast'];
|
|
22
|
+
export const MOTION_OUTSIDE_SHELL = ['assistant', 'drawer', 'fab', 'floating-container', 'modal', 'photo-viewer', 'toast'];
|
|
23
23
|
/** ms cinsinden kabul aralıkları. */
|
|
24
24
|
export const MOTION_RANGES = {
|
|
25
25
|
duration: [100, 2000],
|
|
@@ -67,6 +67,8 @@ const TEMPLATE_VALUE = /\{\{[\s\S]*\}\}/;
|
|
|
67
67
|
* düşer (koşu 37 K37-c, `var(--ng-surface)`). Web profili bu kuralı koşmaz.
|
|
68
68
|
*/
|
|
69
69
|
const STYLE_GROUP_PROPS = new Set(['surfaceStyle', 'typographyStyle', 'headerStyle']);
|
|
70
|
+
// MOB-035'in kararını veren liste prop'ları: birine tek prop yazımı kayıtlı ağacı okutur.
|
|
71
|
+
const REORDER_PROPS = new Set(['reorderable', 'direction', 'columnCount']);
|
|
70
72
|
const UNRESOLVED_TOKEN = /var\(\s*(--ng-[\w-]+)\s*\)/g;
|
|
71
73
|
let themeVarNames;
|
|
72
74
|
function nearestThemeVar(name) {
|
|
@@ -115,6 +117,8 @@ export function profileFindings(profile, input) {
|
|
|
115
117
|
const namelessIconButtons = [];
|
|
116
118
|
const unknownTokens = [];
|
|
117
119
|
const motionSkips = [];
|
|
120
|
+
const flatReorder = [];
|
|
121
|
+
const repeaterProps = new Map();
|
|
118
122
|
const hit = (map, key, target) => map.set(key, [...(map.get(key) ?? []), target]);
|
|
119
123
|
const inspectTokens = (target, value) => {
|
|
120
124
|
for (const name of unknownThemeTokens(value)) {
|
|
@@ -205,6 +209,8 @@ export function profileFindings(profile, input) {
|
|
|
205
209
|
inspectMotion(target, props.motionStyle);
|
|
206
210
|
for (const group of STYLE_GROUP_PROPS)
|
|
207
211
|
inspectTokens(target, props[group]);
|
|
212
|
+
if (type === 'repeater')
|
|
213
|
+
repeaterProps.set(target, props);
|
|
208
214
|
if (type === 'image' && !imageHasDefiniteHeight(asRecord(node.appearance)))
|
|
209
215
|
floorImages.push(target);
|
|
210
216
|
if (type === 'button' && propValue(props.size) === 'icon' && !String(propValue(props.ariaLabel) ?? '').trim() && !String(propValue(props.text) ?? '').trim()) {
|
|
@@ -230,7 +236,12 @@ export function profileFindings(profile, input) {
|
|
|
230
236
|
};
|
|
231
237
|
walk(input.nodes, inspectNode);
|
|
232
238
|
const savedTypes = new Map();
|
|
233
|
-
|
|
239
|
+
const savedRepeaterProps = new Map();
|
|
240
|
+
walk(input.savedNodes, node => {
|
|
241
|
+
savedTypes.set(label(node), String(node.type ?? ''));
|
|
242
|
+
if (node.type === 'repeater')
|
|
243
|
+
savedRepeaterProps.set(label(node), asRecord(node.props) ?? {});
|
|
244
|
+
});
|
|
234
245
|
// Toplu yamanın zarfı açılır: içindeki op'lar tek tek yazılmış gibi denetlenir (TOOL-057).
|
|
235
246
|
const ops = flattenBatchOps(input.ops ?? []);
|
|
236
247
|
for (const op of ops) {
|
|
@@ -258,6 +269,9 @@ export function profileFindings(profile, input) {
|
|
|
258
269
|
inspectMotion(target, op.value);
|
|
259
270
|
if (STYLE_GROUP_PROPS.has(prop))
|
|
260
271
|
inspectTokens(target, op.value);
|
|
272
|
+
const listProps = REORDER_PROPS.has(prop) ? repeaterProps.get(target) ?? savedRepeaterProps.get(target) : undefined;
|
|
273
|
+
if (listProps)
|
|
274
|
+
repeaterProps.set(target, { ...listProps, [prop]: op.value });
|
|
261
275
|
// MOB-011: var olan düğümün tipi op'ta yok — tip biliniyorsa (aynı yazımda eklendi) ona, yoksa prop adına bakılır.
|
|
262
276
|
const knownType = nodeTypes.get(target);
|
|
263
277
|
const imageProp = knownType === undefined ? IMAGE_SOURCE_PROP_NAMES.has(prop) : (IMAGE_SOURCE_PROPS[knownType] ?? []).includes(prop);
|
|
@@ -266,6 +280,15 @@ export function profileFindings(profile, input) {
|
|
|
266
280
|
fileIdSources.push({ target: `${target}.${prop}`, expression });
|
|
267
281
|
}
|
|
268
282
|
}
|
|
283
|
+
// MOB-035: sıralama yalnız dikey, tek sütunlu listededir (frontend `reorderEnabled`); yatay şeritte ve ızgarada
|
|
284
|
+
// `reorderable` yazım kabul edilir ama tutamaç çizilmez — sessiz başarı. Denetlenen, yazımdan SONRAKİ prop'lardır:
|
|
285
|
+
// eklenen düğümün kendi prop'ları, var olan düğüme tek prop yazımında kayıtlı prop'larla birleşimi. Dokunulmayan
|
|
286
|
+
// kayıtlı liste denetlenmez. Sütun sayısı çizerinki gibi tam sayıya kesilir (`repeaterColumnCount`: 1.5 tek sütundur).
|
|
287
|
+
for (const [target, props] of repeaterProps) {
|
|
288
|
+
const columns = Math.trunc(Number(propValue(props.columnCount)));
|
|
289
|
+
if (propValue(props.reorderable) === true && (propValue(props.direction) === 'horizontal' || (Number.isFinite(columns) && columns > 1)))
|
|
290
|
+
flatReorder.push(target);
|
|
291
|
+
}
|
|
269
292
|
if (blockNodes.length > 0 || gridNodes.length > 0) {
|
|
270
293
|
findings.push({
|
|
271
294
|
code: MOBILE_RULES['MOB-001'].id,
|
|
@@ -302,6 +325,15 @@ export function profileFindings(profile, input) {
|
|
|
302
325
|
guide: "platform_guide('page') §Kural 1g-3"
|
|
303
326
|
});
|
|
304
327
|
}
|
|
328
|
+
if (flatReorder.length > 0) {
|
|
329
|
+
findings.push({
|
|
330
|
+
code: MOBILE_RULES['MOB-035'].id,
|
|
331
|
+
severity: 'warn',
|
|
332
|
+
target: flatReorder.join(', '),
|
|
333
|
+
message: `reorderable yalnız dikey, tek sütunlu listede çalışır — yatay şeritte (direction: horizontal) ve çok sütunda (columnCount > 1) tutamaç çizilmez, satırlar sürüklenmez. Sıralama gerekiyorsa listeyi dikey ve tek sütun yapın. ${NOT_BLOCKING}`,
|
|
334
|
+
guide: "platform_guide('page') §Kural 1g-5"
|
|
335
|
+
});
|
|
336
|
+
}
|
|
305
337
|
if (motionSkips.length > 0) {
|
|
306
338
|
findings.push({
|
|
307
339
|
code: MOBILE_RULES['MOB-028'].id,
|
|
@@ -483,12 +515,13 @@ function walkSteps(value, visit) {
|
|
|
483
515
|
}
|
|
484
516
|
/**
|
|
485
517
|
* Denetim bu yama için kayıtlı ağacı ister mi (`ProfileLintInput.savedNodes`): `style` yazımında hedefin tipi (belge
|
|
486
|
-
* CSS'i taşıyan düğüm mü — MOB-002), `heroTag` yazımında sayfanın öteki etiketleri (MOB-030)
|
|
518
|
+
* CSS'i taşıyan düğüm mü — MOB-002), `heroTag` yazımında sayfanın öteki etiketleri (MOB-030), sıralama prop'u yazımında
|
|
519
|
+
* listenin öteki prop'ları (MOB-035). Çağıran ağacı yalnız bu
|
|
487
520
|
* durumda okur. Denetimle AYNI op listesine bakar (zarf açılır): 2026-10-10'da denetim zarfı açıp çağıran açmayınca
|
|
488
521
|
* kayıtlı `html` düğümünün belge CSS'i zarf içinde yanlış MOB-002 aldı (regresyon `test/batch-profile-lint.test.ts`).
|
|
489
522
|
*/
|
|
490
523
|
export function profileLintReadsSavedNodes(ops) {
|
|
491
|
-
return writesHeroTag(ops) || flattenBatchOps(ops).some(op => (op.op === 'replace_prop' || op.op === 'set_binding') && op.prop === 'style');
|
|
524
|
+
return writesHeroTag(ops) || flattenBatchOps(ops).some(op => (op.op === 'replace_prop' || op.op === 'set_binding') && (op.prop === 'style' || REORDER_PROPS.has(String(op.prop))));
|
|
492
525
|
}
|
|
493
526
|
/** Bulguları `layoutWarnings[]` satırlarına çevirir. */
|
|
494
527
|
export function findingLines(findings) {
|
|
@@ -133,6 +133,12 @@ export const MOBILE_RULES = {
|
|
|
133
133
|
sourceRef: 'zeyrek-frontend/packages/ng-apps-ui-native/src/motion/hero-tag.ts (kural; ayna heroTag.ts); zeyrek-frontend/docs/adr/0022-hero-yolu.md; simülatör ölçümü 2026-10-10',
|
|
134
134
|
summary: "Aynı sayfada aynı statik heroTag'i birden çok düğüm taşıyor — sayfa yakınlaşmanın hedefi olduğunda ilk çizilen hizalanır; kaynak sayfada sorun değildir"
|
|
135
135
|
},
|
|
136
|
+
'MOB-035': {
|
|
137
|
+
id: 'MOB-035',
|
|
138
|
+
severity: 'warn',
|
|
139
|
+
sourceRef: 'zeyrek-frontend/packages/ng-apps-ui-native/src/components/repeater/repeater-reorder-logic.ts (reorderEnabled; plans/mobil-jest-katmani faz 02)',
|
|
140
|
+
summary: 'Sıralanabilir liste (`reorderable`) yatay şeritte ya da çok sütunda — çizer tutamaç çizmez, sürükleme yok'
|
|
141
|
+
},
|
|
136
142
|
'MOB-033': {
|
|
137
143
|
id: 'MOB-033',
|
|
138
144
|
severity: 'warn',
|
|
@@ -550,6 +550,13 @@ export const TYPED_STYLE_COMPONENTS = {
|
|
|
550
550
|
ext: { overflowTarget: ROOT_TARGET },
|
|
551
551
|
omit: { surfaceStyle: TRANSFORM_FIELDS }
|
|
552
552
|
},
|
|
553
|
+
assistant: {
|
|
554
|
+
// Çekmece / panel kökü: yüzey ve yazı grubu panelin köküne iner (frontend
|
|
555
|
+
// `Assistant.svelte`, composeTypedStyle ile aynı derleyici).
|
|
556
|
+
groups: ['surfaceStyle', 'typographyStyle'],
|
|
557
|
+
ext: { overflowTarget: ROOT_TARGET },
|
|
558
|
+
omit: { surfaceStyle: TRANSFORM_FIELDS }
|
|
559
|
+
},
|
|
553
560
|
drawer: {
|
|
554
561
|
// Panel portal ile düğüm kutusunun DIŞINA çizilir; yüzey panelin KÖKÜNDE
|
|
555
562
|
// derlenir, taşma da panelin köküne iner (drawer ölçümü, 2026-10-05).
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import { INTERNAL_ROLE_SEPARATOR } from '../../knowledge/componentCatalog.js';
|
|
2
2
|
import { studioWebTarget } from './targets/studio-web.js';
|
|
3
|
+
/**
|
|
4
|
+
* `swipe` jestinin adım sayısı ve aralığı: react-native-web yanıtlayıcısı tek seferde atılan hareketi jest saymaz
|
|
5
|
+
* (`ui-native.md` §flip-card ve swipeable-stack); 12 × 16 ms oyun alanında tutamaç sıralamasını sürdü (g-02, 2026-10-27).
|
|
6
|
+
*/
|
|
7
|
+
export const SWIPE_STEPS = 12;
|
|
8
|
+
export const SWIPE_INTERVAL_MS = 16;
|
|
9
|
+
/**
|
|
10
|
+
* `to` verilen jest hedefin içinde, hareket yönündeki kenarına bu kadar (CSS pikseli) kala biter. Merkezde bitseydi
|
|
11
|
+
* sürüklenen satırın merkezi hedefinkine EŞİT olurdu; çizer kardeşi kesin `<` ile saydığından (`reorderTarget`) aşağı
|
|
12
|
+
* taşıma bir satır eksik inerdi (inceleme bulgusu 2, oyun alanında `row-0 → row-2` → `reorder 0→1`).
|
|
13
|
+
*/
|
|
14
|
+
export const SWIPE_EDGE_INSET = 2;
|
|
3
15
|
/**
|
|
4
16
|
* G-24 — gateway sayfayı kurabiliyor, okuyabiliyor ve çizdirebiliyordu ama üzerinde OLAY
|
|
5
17
|
* TETİKLEYEMİYORDU: "filtre değişince veri yenileniyor" iddiası yalnız şema okumasıyla
|
|
@@ -273,6 +285,61 @@ export function actionScript(action, captureTarget = studioWebTarget) {
|
|
|
273
285
|
return { found: true, applied: true, requests: count };
|
|
274
286
|
})()`;
|
|
275
287
|
}
|
|
288
|
+
// g-02 (plans/mobil-jest-katmani) — mobil jestler (tutamaçtan sıralama, satır kaydırma, deste, kaydırıcı) işaretçiyi
|
|
289
|
+
// dinler; HTML5 `drag` onları süremez. Gerçek tarayıcı gibi her anda pointer + mouse birlikte atılır: oyun alanında
|
|
290
|
+
// yalnız `pointer*` react-native-web yanıtlayıcısını SÜRMEDİ, `mouse*` sürdü (ölçüldü 2026-10-27). Hareket olayı
|
|
291
|
+
// parmağın altındaki öğeye gider; basış kaynağa.
|
|
292
|
+
if (action.action === 'swipe') {
|
|
293
|
+
const destination = action.to === undefined ? 'null' : JSON.stringify(`[data-element-id="${action.to}"]`);
|
|
294
|
+
const webNote = captureTarget.name === 'studio-mobile'
|
|
295
|
+
? ''
|
|
296
|
+
: "swipe dokunmatik jesttir: web sayfasında yalnız işaretçiyi dinleyen öğeyi sürer — HTML5 sürükle-bırak (kanban, takvim) için 'drag'";
|
|
297
|
+
return `(function () {
|
|
298
|
+
var count = performance.getEntriesByType('resource').length;
|
|
299
|
+
var node = document.querySelector(${selector});
|
|
300
|
+
if (!node) return { found: false, applied: false, requests: count };
|
|
301
|
+
var center = function (element) { var rect = element.getBoundingClientRect(); return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }; };
|
|
302
|
+
var from = center(node);
|
|
303
|
+
var to;
|
|
304
|
+
var destination = ${destination};
|
|
305
|
+
if (destination !== null) {
|
|
306
|
+
var goal = document.querySelector(destination);
|
|
307
|
+
if (!goal) return { found: false, applied: false, note: "jest kaynağı bulundu ama bitiş hedefi ('to') sayfada yok: " + ${JSON.stringify(String(action.to ?? ''))} + " — satır <elementId>__row-<i>, tutamaç <elementId>__row-<i>__handle", requests: count };
|
|
308
|
+
var box = goal.getBoundingClientRect();
|
|
309
|
+
var middle = center(goal);
|
|
310
|
+
var edge = function (start, mid, low, high) { return mid > start ? high - ${SWIPE_EDGE_INSET} : mid < start ? low + ${SWIPE_EDGE_INSET} : mid; };
|
|
311
|
+
to = { x: edge(from.x, middle.x, box.left, box.left + box.width), y: edge(from.y, middle.y, box.top, box.top + box.height) };
|
|
312
|
+
} else {
|
|
313
|
+
to = { x: from.x + ${Number(action.dx ?? 0)}, y: from.y + ${Number(action.dy ?? 0)} };
|
|
314
|
+
}
|
|
315
|
+
var fire = function (type, at, element) {
|
|
316
|
+
var init = { bubbles: true, cancelable: true, composed: true, clientX: at.x, clientY: at.y, screenX: at.x, screenY: at.y, button: 0, buttons: /up$/.test(type) ? 0 : 1, pointerId: 1, pointerType: 'mouse', isPrimary: true };
|
|
317
|
+
element.dispatchEvent(type.indexOf('pointer') === 0 ? new PointerEvent(type, init) : new MouseEvent(type, init));
|
|
318
|
+
};
|
|
319
|
+
var under = function (at) { return document.elementFromPoint(at.x, at.y) || node; };
|
|
320
|
+
var finish = function (result) { if (${JSON.stringify(webNote)}) result.note = ${JSON.stringify(webNote)}; return result; };
|
|
321
|
+
return new Promise(function (resolve) {
|
|
322
|
+
fire('pointerdown', from, node);
|
|
323
|
+
fire('mousedown', from, node);
|
|
324
|
+
var step = 0;
|
|
325
|
+
var move = function () {
|
|
326
|
+
step += 1;
|
|
327
|
+
var at = { x: from.x + ((to.x - from.x) * step) / ${SWIPE_STEPS}, y: from.y + ((to.y - from.y) * step) / ${SWIPE_STEPS} };
|
|
328
|
+
var element = under(at);
|
|
329
|
+
fire('pointermove', at, element);
|
|
330
|
+
fire('mousemove', at, element);
|
|
331
|
+
if (step < ${SWIPE_STEPS}) { setTimeout(move, ${SWIPE_INTERVAL_MS}); return; }
|
|
332
|
+
setTimeout(function () {
|
|
333
|
+
var end = under(to);
|
|
334
|
+
fire('pointerup', to, end);
|
|
335
|
+
fire('mouseup', to, end);
|
|
336
|
+
resolve(finish({ found: true, applied: true, requests: count }));
|
|
337
|
+
}, ${SWIPE_INTERVAL_MS});
|
|
338
|
+
};
|
|
339
|
+
setTimeout(move, ${SWIPE_INTERVAL_MS});
|
|
340
|
+
});
|
|
341
|
+
})()`;
|
|
342
|
+
}
|
|
276
343
|
const target = String(action.target ?? '');
|
|
277
344
|
const root = rootElementId(target);
|
|
278
345
|
// G-14 — `applied` yalnız "DOM'a yazıldı" demekti: tarih seçici olayı dinlemiyordu, kutu dolu
|