@appaflytech/zeyrek-ai-mcp 0.0.41 → 0.0.43
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/dist/knowledge/componentCatalog.js +27 -97
- package/dist/knowledge/componentEvents.js +1 -18
- package/dist/knowledge/mobileComponentCatalog.js +114 -15
- package/dist/knowledge/mobileComponentEvents.js +41 -0
- package/dist/knowledge/motionResolve.js +252 -0
- package/dist/knowledge/normalize.js +87 -59
- package/dist/knowledge/pageDiscover.js +2 -2
- package/dist/knowledge/platform/batchOps.js +70 -0
- package/dist/knowledge/platform/heroTag.js +153 -0
- package/dist/knowledge/platform/lint.js +128 -6
- package/dist/knowledge/platform/mobile.js +1 -1
- package/dist/knowledge/platform/mobileLayoutLint.js +8 -1
- package/dist/knowledge/platform/mobileRules.js +18 -0
- package/dist/knowledge/platform/pageTransition.js +57 -0
- package/dist/knowledge/platform/web.js +1 -1
- package/dist/knowledge/rules.js +52 -1
- package/dist/knowledge/styleResolveNative.js +88 -4
- package/dist/platform/capture/actions.js +4 -1
- package/dist/platform/capture/capture.js +10 -13
- package/dist/platform/capture/persistent-browser.js +130 -0
- package/dist/resources/examples/flow-list-to-form.json +8 -10
- package/dist/resources/examples/flow-load-table.json +1 -1
- package/dist/resources/examples/mobile-list-detail-form.json +4 -3
- package/dist/resources/examples/mobile-scan-code.json +285 -0
- package/dist/resources/guides/component.md +3 -3
- package/dist/resources/guides/expressions.md +2 -2
- package/dist/resources/guides/merge.md +98 -18
- package/dist/resources/guides/microservice.md +2 -2
- package/dist/resources/guides/mobile-layout.md +2 -1
- package/dist/resources/guides/mobile-publish.md +1 -0
- package/dist/resources/guides/mobile-steps.md +67 -0
- package/dist/resources/guides/mobile.md +27 -55
- package/dist/resources/guides/page.md +182 -121
- package/dist/resources/guides/translation.md +26 -0
- package/dist/resources/schema-fingerprint.json +2 -2
- package/dist/resources/schemas/ResolveMergeConflictsCommand.json +30 -1
- package/dist/tools/activities.js +3 -1
- package/dist/tools/applications.js +16 -2
- package/dist/tools/components.js +12 -6
- package/dist/tools/designSchemaPatch.js +18 -13
- package/dist/tools/layouts.js +7 -2
- package/dist/tools/merges.js +29 -9
- package/dist/tools/pages.js +129 -127
- package/dist/tools/platformInfo.js +3 -2
- package/dist/tools/profileWrite.js +2 -2
- package/dist/tools/schemaOps.js +132 -34
- package/dist/tools/screenWrite.js +1 -1
- package/dist/tools/screens.js +3 -3
- package/package.json +2 -2
|
@@ -2,6 +2,7 @@ import { PlatformApiError } from '../errors.js';
|
|
|
2
2
|
import { parseDomSummary } from '../domSummary.js';
|
|
3
3
|
import { INTERNAL_ROLE_SEPARATOR, knownInternalRoles } from '../../knowledge/componentCatalog.js';
|
|
4
4
|
import { logStep } from '../../observability/trace.js';
|
|
5
|
+
import { PersistentScreenshotBrowser, persistentContextOpener, screenshotProfileRoot } from './persistent-browser.js';
|
|
5
6
|
import { actionScript, activeTabScript, rootElementId, runtimePropsScript } from './actions.js';
|
|
6
7
|
import { domSummaryScript } from './summary.js';
|
|
7
8
|
import { createPageConsoleLog, describePageFailure, isExpressionEcho, parseRenderErrors, renderErrorsScript, splitExpressionErrors } from './console-log.js';
|
|
@@ -280,22 +281,18 @@ export class StudioScreenshotter {
|
|
|
280
281
|
catch {
|
|
281
282
|
throw new PlatformApiError('playwright-core paketi yüklü değil — zeyrek-mcp dizininde `npm install playwright-core` çalıştırın, ardından tarayıcı için `npx playwright-core install chromium`.');
|
|
282
283
|
}
|
|
283
|
-
logStep('tarayıcı →',
|
|
284
|
+
logStep('tarayıcı →', `chromium başlatılıyor (ilk çağrı — sonraki çağrılar önbellekteki tarayıcıyı kullanır) · profil ${screenshotProfileRoot()}`);
|
|
285
|
+
const open = persistentContextOpener(chromium, this.config.screenshotBrowserPath ? { executablePath: this.config.screenshotBrowserPath } : {});
|
|
286
|
+
const browser = new PersistentScreenshotBrowser(open);
|
|
284
287
|
try {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
...(this.config.screenshotBrowserPath ? { executablePath: this.config.screenshotBrowserPath } : {})
|
|
288
|
-
});
|
|
288
|
+
// İlk bağlam burada açılır ki başlatma hatası eskisi gibi `launch()`'tan dönsün.
|
|
289
|
+
await (await browser.newContext({ viewport: { width: 1280, height: 720 }, deviceScaleFactor: 1 })).close();
|
|
289
290
|
}
|
|
290
291
|
catch (firstError) {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
}
|
|
294
|
-
catch {
|
|
295
|
-
const message = firstError instanceof Error ? firstError.message : String(firstError);
|
|
296
|
-
throw new PlatformApiError(`Chromium başlatılamadı: ${message}. \`npx playwright-core install chromium\` ile indirin; alternatif olarak sistemdeki tarayıcının yolunu SCREENSHOT_BROWSER_PATH ile verin (alpine imajında \`apk add chromium\` + /usr/bin/chromium-browser).`);
|
|
297
|
-
}
|
|
292
|
+
const message = firstError instanceof Error ? firstError.message : String(firstError);
|
|
293
|
+
throw new PlatformApiError(`Chromium başlatılamadı: ${message}. \`npx playwright-core install chromium\` ile indirin; alternatif olarak sistemdeki tarayıcının yolunu SCREENSHOT_BROWSER_PATH ile verin (alpine imajında \`apk add chromium\` + /usr/bin/chromium-browser).`);
|
|
298
294
|
}
|
|
295
|
+
return browser;
|
|
299
296
|
}
|
|
300
297
|
/**
|
|
301
298
|
* Bekleme stratejisi bilinçli olarak `networkidle` DEĞİL: Studio'nun kök layout'u
|
|
@@ -331,7 +328,7 @@ export class StudioScreenshotter {
|
|
|
331
328
|
before.note = action.target === '__tabs' || action.target.startsWith(`__tabs${INTERNAL_ROLE_SEPARATOR}`)
|
|
332
329
|
? `'${action.target}' sayfada yok: bu sayfa sekme çubuğunda değil ya da uygulamada sekmeler kapalı (application_get → properties.mobile.tabs). Sekme öğesinin kimliği __tabs__item-<sayfa slug'ı>.`
|
|
333
330
|
: action.target.includes(INTERNAL_ROLE_SEPARATOR)
|
|
334
|
-
? `'${action.target}' sayfada yok. Bu bir İÇ ROL hedefi: biçim <elementId>__<rol> ve bilinen roller — ${knownInternalRoles().join(' · ')}.
|
|
331
|
+
? `'${action.target}' sayfada yok. Bu bir İÇ ROL hedefi: biçim <elementId>__<rol> ve bilinen roller — ${knownInternalRoles().join(' · ')}. Roller görünen metinden değil kayıttaki anahtardan türer (sekme key'i, kart id'si); hedef bileşenin rollerini component_get çıktısındaki internalRoles verir.`
|
|
335
332
|
: `'${action.target}' elementId'si sayfada yok. page_schema_get ile elementId'leri listeleyin; bir komponentin İÇ kontrolünü hedefliyorsanız <elementId>__<rol> biçimini kullanın (component_get çıktısındaki internalRoles).`;
|
|
336
333
|
}
|
|
337
334
|
// Olay akışı uç çağırıyor: yerleşme beklemeden sonraki istek sayısını ölçmek "istek gitti mi"
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { mkdir } from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
/**
|
|
5
|
+
* Tarayıcı SABİT bir profille açılır. `chromium.launch()` her başlatmada geçici bir profil
|
|
6
|
+
* kuruyordu ve Chrome her yeni profilde Windows parolasının boş olup olmadığını sınıyor; bu
|
|
7
|
+
* sınama Security log'a başarısız oturum açma (4625, LogonType 2, 0xc000006a, chrome.exe)
|
|
8
|
+
* olarak düşüyor ve 10 dakikada 10 çekim yerel hesabı kilitliyordu (0xc0000234).
|
|
9
|
+
*
|
|
10
|
+
* Profil yalnız bu araca aittir; kullanıcının gerçek Chrome profili (Google\Chrome\User Data)
|
|
11
|
+
* hiçbir koşulda kullanılmaz. Profil dizini aynı anda tek süreç tarafından açılabildiği için
|
|
12
|
+
* (iki MCP oturumu) dolu dizinde sıradaki SABİT yuvaya geçilir — yuva sayısı sınırlıdır, her
|
|
13
|
+
* yuvanın sınaması ömründe bir kez olur.
|
|
14
|
+
*/
|
|
15
|
+
export const screenshotProfileRoot = () => path.join(process.env.LOCALAPPDATA ?? path.join(os.homedir(), '.cache'), 'zeyrek-mcp');
|
|
16
|
+
const profileSlots = ['chrome-profile', 'chrome-profile-2', 'chrome-profile-3'];
|
|
17
|
+
/** Yuvaları sırayla dener; her yuvada önce verilen tarayıcı, sonra sistem Chrome'u (`channel: 'chrome'`). */
|
|
18
|
+
export function persistentContextOpener(chromium, primary) {
|
|
19
|
+
return async (deviceScaleFactor) => {
|
|
20
|
+
const root = screenshotProfileRoot();
|
|
21
|
+
let firstError;
|
|
22
|
+
for (const slot of profileSlots) {
|
|
23
|
+
const userDataDir = path.join(root, slot);
|
|
24
|
+
await mkdir(userDataDir, { recursive: true });
|
|
25
|
+
for (const launch of [primary, { channel: 'chrome' }]) {
|
|
26
|
+
try {
|
|
27
|
+
return await chromium.launchPersistentContext(userDataDir, { headless: true, deviceScaleFactor, ...launch });
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
firstError ??= error;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
throw firstError;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Kalıcı bağlamı `ScreenshotBrowser` gibi gösterir. Kalıcı bağlamda ikinci bağlam açılamaz;
|
|
39
|
+
* çekimler seri koştuğu için (`StudioScreenshotter` kuyruğu) her `newContext` aynı bağlamın
|
|
40
|
+
* üstünde bir oturum verir. `deviceScaleFactor` yalnız açılışta verilebildiği için değişince
|
|
41
|
+
* bağlam AYNI profille yeniden açılır.
|
|
42
|
+
*/
|
|
43
|
+
export class PersistentScreenshotBrowser {
|
|
44
|
+
open;
|
|
45
|
+
current;
|
|
46
|
+
constructor(open) {
|
|
47
|
+
this.open = open;
|
|
48
|
+
}
|
|
49
|
+
async newContext(options) {
|
|
50
|
+
if (this.current && this.current.deviceScaleFactor !== options.deviceScaleFactor) {
|
|
51
|
+
await this.close();
|
|
52
|
+
}
|
|
53
|
+
if (!this.current) {
|
|
54
|
+
const context = await this.open(options.deviceScaleFactor);
|
|
55
|
+
const entry = { context, deviceScaleFactor: options.deviceScaleFactor };
|
|
56
|
+
// Tarayıcı düşerse sonraki çekim yeniden açsın (eski `browser.newContext` hatası gibi).
|
|
57
|
+
context.on('close', () => {
|
|
58
|
+
if (this.current === entry)
|
|
59
|
+
this.current = undefined;
|
|
60
|
+
});
|
|
61
|
+
this.current = entry;
|
|
62
|
+
}
|
|
63
|
+
return new PersistentContextSession(this.current.context, options);
|
|
64
|
+
}
|
|
65
|
+
async close() {
|
|
66
|
+
const expired = this.current;
|
|
67
|
+
this.current = undefined;
|
|
68
|
+
await expired?.context.close();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Tek çekimlik oturum. Eski taze bağlamın yalıtımı korunur: başlık ve init script'i bağlama
|
|
73
|
+
* değil sayfaya yazılır (sonraki çekime sızmaz — `impersonated-user-id` dahil), kapanışta
|
|
74
|
+
* çerezler ve gezilen kökenlerin depoları (localStorage, IndexedDB, …) diskteki profilden silinir.
|
|
75
|
+
*/
|
|
76
|
+
class PersistentContextSession {
|
|
77
|
+
context;
|
|
78
|
+
options;
|
|
79
|
+
initScripts = [];
|
|
80
|
+
headers;
|
|
81
|
+
pages = [];
|
|
82
|
+
constructor(context, options) {
|
|
83
|
+
this.context = context;
|
|
84
|
+
this.options = options;
|
|
85
|
+
}
|
|
86
|
+
addCookies(cookies) {
|
|
87
|
+
return this.context.addCookies(cookies);
|
|
88
|
+
}
|
|
89
|
+
async addInitScript(script) {
|
|
90
|
+
this.initScripts.push(script);
|
|
91
|
+
}
|
|
92
|
+
async setExtraHTTPHeaders(headers) {
|
|
93
|
+
this.headers = headers;
|
|
94
|
+
}
|
|
95
|
+
async newPage() {
|
|
96
|
+
const page = await this.context.newPage();
|
|
97
|
+
this.pages.push(page);
|
|
98
|
+
await page.setViewportSize(this.options.viewport);
|
|
99
|
+
await page.emulateMedia({ reducedMotion: this.options.reducedMotion ?? 'no-preference' });
|
|
100
|
+
if (this.headers)
|
|
101
|
+
await page.setExtraHTTPHeaders(this.headers);
|
|
102
|
+
for (const script of this.initScripts)
|
|
103
|
+
await page.addInitScript(script);
|
|
104
|
+
return page;
|
|
105
|
+
}
|
|
106
|
+
async close() {
|
|
107
|
+
const origins = new Set();
|
|
108
|
+
for (const page of this.pages) {
|
|
109
|
+
try {
|
|
110
|
+
const origin = new URL(page.url()).origin;
|
|
111
|
+
if (origin.startsWith('http'))
|
|
112
|
+
origins.add(origin);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// about:blank vb.
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const probe = this.pages.find(page => !page.isClosed());
|
|
119
|
+
if (probe && origins.size > 0) {
|
|
120
|
+
const cdp = await this.context.newCDPSession(probe).catch(() => undefined);
|
|
121
|
+
for (const origin of origins) {
|
|
122
|
+
await cdp?.send('Storage.clearDataForOrigin', { origin, storageTypes: 'all' }).catch(() => undefined);
|
|
123
|
+
}
|
|
124
|
+
await cdp?.detach().catch(() => undefined);
|
|
125
|
+
}
|
|
126
|
+
await this.context.clearCookies().catch(() => undefined);
|
|
127
|
+
for (const page of this.pages)
|
|
128
|
+
await page.close().catch(() => undefined);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_meta": {
|
|
3
3
|
"synthetic": false,
|
|
4
|
-
"note": "Liste sayfasından kayıt sayfasına geçiş: satıra çift tıkla → edit (?id=), 'Yeni' butonu → create (parametresiz). Tek yeni kavram redirect adımıdır; query alanı SABİT DEĞİL İFADEDİR.",
|
|
5
|
-
"harvestedFrom": "qm projesi '[SY] Departmanlar' sayfası (table-doubleClick + button-click akışları), 2026-08-17; ayrıntı: zeyrek-mcp/docs/18-sayfa-arketipleri.md §2",
|
|
4
|
+
"note": "Liste sayfasından kayıt sayfasına geçiş: satıra çift tıkla → edit (?id=), 'Yeni' butonu → create (parametresiz). Tek yeni kavram redirect adımıdır; query alanı SABİT DEĞİL İFADEDİR. Tablo `datatable`'dır: olay `rowDoubleClick`, liste tek `mount` akışıyla dolar.",
|
|
5
|
+
"harvestedFrom": "qm projesi '[SY] Departmanlar' sayfası (table-doubleClick + button-click akışları), 2026-08-17; ayrıntı: zeyrek-mcp/docs/18-sayfa-arketipleri.md §2. 2026-10-13'te kaldırılan `table` bileşeninden `datatable`'a taşındı (olay adları ve bağ sayısı değişti; redirect adımları aynı).",
|
|
6
6
|
"pairsWith": "page-form-upsert (geçilen sayfa), flow-load-table (listenin kendisini dolduran akış)"
|
|
7
7
|
},
|
|
8
8
|
"_rules": {
|
|
9
9
|
"queryIsExpression": "redirect.args.query bir İFADEDİR, sabit string değil: template literal (`id=${…}`) ve koşullu ifade yazılabilir. Sayfalar arası parametre geçmenin yolu budur; hedef sayfa Page.queryParams.<ad> ile okur. variable_create gerekmez.",
|
|
10
|
-
"selfAndNames": "İfade içinde `self` tetikleyen komponenttir (self.selectedRow.id). Komponente adıyla da erişilir (
|
|
10
|
+
"selfAndNames": "İfade içinde `self` tetikleyen komponenttir (self.selectedRow.id). Komponente adıyla da erişilir (records_table?.selectedRow?.id) — addaki '-' silinir. Sözlük: platform_guide('expressions') §2.",
|
|
11
11
|
"pageIdAndPath": "redirect args'ında pageId (bağlayıcı referans) ve path (slug) birlikte verilir; sabit URL yazma — slug değişince pageId referansı ayakta kalır.",
|
|
12
|
-
"
|
|
12
|
+
"singleMountFlow": "Tablo `datatable`'dır ve liste TEK `mount` akışıyla dolar: kullanıcı süzünce/sıralayınca/sayfa değiştirince komponent kendi filters/sortField/sortType/offset prop'larını yazar ve `mount`'u yeniden tetikler (Kural 1e) — pagination/filter/sort diye ayrı olay YOKTUR, bağlamaya çalışmak PAGE-008 ile reddedilir. Kaydı açan olay `rowDoubleClick`'tir (tek tık için `rowClick`); ikisi de olaydan ÖNCE tıklanan satırı self.selectedRow'a yazar. Hangi olayların var olduğunu component_get ile doğrulayın; olay adları `on` öneki ALMAZ (button → click).",
|
|
13
|
+
"editModeAndDoubleClick": "editMode none DIŞINDAYKEN çift tık satırı ayrıca düzenlemeye açar; liste → form geçişi için tabloyu editMode: none bırakın (ya da geçişi satır aksiyonuyla kurun: actions[].flow).",
|
|
13
14
|
"newButton": "'Yeni' butonu ayrı bir komponenttir ve akışı parametresiz redirect'tir; hedef sayfa parametre görmediği için create kipine düşer."
|
|
14
15
|
},
|
|
15
16
|
"openRecordFlowCreateArgs": {
|
|
16
17
|
"host": "page",
|
|
17
18
|
"hostId": "<liste sayfası id>",
|
|
18
|
-
"name": "records_table -
|
|
19
|
-
"slug": "records-table-
|
|
19
|
+
"name": "records_table - rowDoubleClick",
|
|
20
|
+
"slug": "records-table-rowdoubleclick",
|
|
20
21
|
"steps": [
|
|
21
22
|
{
|
|
22
23
|
"stepId": "start", "name": "start", "type": "start", "kind": "normal", "args": {},
|
|
@@ -56,10 +57,7 @@
|
|
|
56
57
|
},
|
|
57
58
|
"schemaPatchOps": [
|
|
58
59
|
{ "op": "set_event", "target": "records_table", "event": "mount", "eventValue": "<liste yükleme flow id>" },
|
|
59
|
-
{ "op": "set_event", "target": "records_table", "event": "
|
|
60
|
-
{ "op": "set_event", "target": "records_table", "event": "pagination", "eventValue": "<liste yükleme flow id>" },
|
|
61
|
-
{ "op": "set_event", "target": "records_table", "event": "filter", "eventValue": "<liste yükleme flow id>" },
|
|
62
|
-
{ "op": "set_event", "target": "records_table", "event": "sort", "eventValue": "<liste yükleme flow id>" },
|
|
60
|
+
{ "op": "set_event", "target": "records_table", "event": "rowDoubleClick", "eventValue": "<openRecord flow id>" },
|
|
63
61
|
{ "op": "set_event", "target": "btn_new", "event": "click", "eventValue": "<newRecord flow id>" }
|
|
64
62
|
]
|
|
65
63
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_meta": {
|
|
3
3
|
"synthetic": true,
|
|
4
|
-
"note": "Komponentin mount olayına bağlanan flow tabloya veri yükler: yükleniyor bayrağı → microservice → satır eşleme + toplam kayıt. Bu dosyanın 2026-08-16 öncesi sürümü ÜÇ HATA taşıyordu ve ürettiği akışlar ne Studio'da çiziliyor ne runtime'da çalışıyordu; aşağıdaki şekil UI kodu okunarak düzeltildi (bkz. _rules).",
|
|
4
|
+
"note": "Komponentin mount olayına bağlanan flow tabloya (`datatable`) veri yükler: yükleniyor bayrağı → microservice → satır eşleme + toplam kayıt. Aynı akış süzme/sıralama/sayfa değişiminde de koşar: `datatable` kendi filters/sortField/sortType/offset prop'larını yazıp `mount`'u yeniden tetikler, bu yüzden adım parametreleri o prop'lardan okunur. Bu dosyanın 2026-08-16 öncesi sürümü ÜÇ HATA taşıyordu ve ürettiği akışlar ne Studio'da çiziliyor ne runtime'da çalışıyordu; aşağıdaki şekil UI kodu okunarak düzeltildi (bkz. _rules).",
|
|
5
5
|
"harvestedFrom": "kod doğrulaması: NgApps.UI packages/ng-apps-flow + apps/studio/src/features/event-actions (2026-08-16)"
|
|
6
6
|
},
|
|
7
7
|
"_rules": {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"synthetic": true,
|
|
4
4
|
"note": "Mobil uygulamada üç sayfalık liste → detay → form akışı (G6 koşusunun girdisi): 'gorevler' mount'ta görevleri yükleyip repeater'a yazar, satıra dokunmak 'detay'ı id ile açar, 'yeni' formu kaydedip toast gösterir ve listeye döner. Yerleşim flex (blocks yok), görünüm tipli gruplarla.",
|
|
5
5
|
"pairsWith": "mobile-app-shell-stack ya da mobile-app-shell-tab (kabuk), microservice-table-list (liste ucu), page-form-upsert (web'deki form ikizi)",
|
|
6
|
-
"sourceRef": "platform_guide('mobile') §Kurulum sıras
|
|
6
|
+
"sourceRef": "platform_guide('mobile') §Kurulum sırası; platform_guide('mobile-steps') §Adım sözlüğü; ngapps://examples/flow-list-to-form (redirect query sözleşmesi)"
|
|
7
7
|
},
|
|
8
8
|
"_rules": {
|
|
9
9
|
"order": "Önce kabuk ve sayfalar (mobile_app_create), sonra microservice'ler, sonra her sayfanın schemaPatchOps'u, en son akışlar (flow_create) ve bağları (set_event).",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"verify": "page_screenshot({ applicationSlug: 'saha', pageSlug: 'yeni', actions: [{ action: 'set', target: 'in_title', value: 'Sayaç okuma' }, { action: 'submit', target: 'frm_task' }] }) → submitVia 'bridge', flowRuns ≥ 1, son kare 'gorevler'.",
|
|
16
16
|
"propNames": "Prop adları mobil kataloğun adlarıdır (component_get): metin `text.content`, satırlar `repeater.rows`. Mobilde istemci tarafı form doğrulaması yok (`isRequired` prop'u yok) — zorunlu alanı kayıt akışı denetler. Kod bağı ifadeyi `ref`'te taşır: `{ type: 'code', ref: '{{ item.title }}' }` — `data`'ya yazılan ifade çizilmez (boş metin).",
|
|
17
17
|
"detailFromSteps": "Detay alanları uçtan gelen veriyi SETTER adımıyla alır (`fill_detail`, `value.type: 'storage'`): `{{ Steps.… }}` kod bağı ilk çizimde okunur ve `Steps`'e yazmak düğümü yeniden okutmaz — sayfa boş kalırdı (web `page-detail-profile` ile aynı desen).",
|
|
18
|
-
"mobileBehavior": "Çekerek yenile listenin değil onu saran `scroll-view`'ın işidir (`refreshable: true` + `refresh` olayı). `redirect.presentation` push | modal | replace alır; küme dışı değer MOB-008 uyarısı ve backend 422'sidir. Sekme hedefine giden redirect'te modal `navigate`'e düşer.",
|
|
18
|
+
"mobileBehavior": "Çekerek yenile listenin değil onu saran `scroll-view`'ın işidir (`refreshable: true` + `refresh` olayı). `redirect.presentation` push | modal | replace alır; küme dışı değer MOB-008 uyarısı ve backend 422'sidir. Sekme hedefine giden redirect'te modal `navigate`'e düşer. `redirect.transition` hedefin nasıl gireceğini söyler: default | none | fade | slide | slideUp (küme dışı değeri backend 422 ile reddeder); alan yoksa uygulamanın varsayılanı (`properties.mobile.transition`) geçerlidir. `modal` sunumda ve sekme hedefinde geçiş KULLANILMAZ: 'yeni' formunun kayıttan sonraki dönüşü (`go_list`) `fade` yazar ve bu yalnız yığın kabuğunda (mobile-app-shell-stack) oynar — sekme kabuğunda (mobile-app-shell-tab) 'gorevler' bir sekme sayfasıdır, sekme değişir ve geçiş oynamaz. Liste → detay adımı (`go_detail`) `modal` olduğu için geçiş taşımaz.",
|
|
19
19
|
"layoutContract": "Mizanpaj sözleşmesi platform_guide('mobile-layout'): başlık text type (fontSize değil), form direction:'column' + horizontalAlign:'stretch' (yönü yazılmamış form satırdır), aralık gap'ten. Sayfalar MOB-015…021'den sıfır bulguyla geçer."
|
|
20
20
|
},
|
|
21
21
|
"pages": [
|
|
@@ -681,7 +681,8 @@
|
|
|
681
681
|
"args": {
|
|
682
682
|
"pageId": "<gorevler sayfa id>",
|
|
683
683
|
"path": "gorevler",
|
|
684
|
-
"query": ""
|
|
684
|
+
"query": "",
|
|
685
|
+
"transition": "fade"
|
|
685
686
|
},
|
|
686
687
|
"branches": [
|
|
687
688
|
{
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_meta": {
|
|
3
|
+
"synthetic": true,
|
|
4
|
+
"note": "Mobil sayfada kod tarama (scan-code adımı, kök ADR 0017): 'Barkod okut' düğmesi tam ekran tarayıcıyı açar; okunan değer barkod alanına yazılır, kullanıcı vazgeçerse ya da kamera kullanılamıyorsa error dalı tek bir uyarı gösterir. Tek yeni kavram scan-code adımıdır ve iki dalı da bağlıdır. Sayfa mizanpaj sözleşmesine uyar (MOB-015…021 bulgusuz), akış FLOW-042'den temiz geçer (test/mobile-rules.test.ts, test/mobile-layout-lint.test.ts).",
|
|
5
|
+
"pairsWith": "mobile-app-shell-stack (kabuk), mobile-form-screen (alanın içinde durduğu form düzeni), mobile-list-detail-form (okunan değerle kayıt arama / kaydetme akışı)",
|
|
6
|
+
"sourceRef": "platform_guide('mobile-steps') §Adım sözlüğü — scan-code satırı; FLOW-042; FLOW-022 (metin şablonu)"
|
|
7
|
+
},
|
|
8
|
+
"_rules": {
|
|
9
|
+
"value": "args.value izinli kod türleridir: sözlükten virgüllü DÜZ METİN — ifade, dizi ya da büyük harf değil (FLOW-042). Boş bırakılırsa bütün türler okunur; ürün barkodu için daraltmak yanlış okumayı azaltır. upc_a yoktur: UPC-A etiketi ean13 adıyla ve başında 0 ile on üç hane döner. types / formats / barcodeTypes gibi anahtarlar okunmaz.",
|
|
10
|
+
"title": "args.title tarayıcı ekranındaki yönergedir ve METİN ŞABLONUDUR (FLOW-022): düz metin olduğu gibi çizilir, çeviri için \"{{ t('urun.barkod.okut') }}\" yazılır. Boşsa ekranın kendi yönergesi çizilir.",
|
|
11
|
+
"result": "Sonuç Steps.<adım adı>.data = { value, type, source }. source 'camera' ya da 'manual' (ekrandaki 'Elle gir'); elle girişte adım birden çok tür istiyorsa type null gelir — türe göre dallanacaksan null'u da ele al.",
|
|
12
|
+
"errorBranch": "Kullanıcının tarayıcıyı kapatması bir HATADIR. error dalı bağlı değilse akışın hata akışı (errorSteps) koşar; o da yoksa akış always ile sürer ve yazma adımı Steps.scan.data.value yerine null okur. error dalında Steps.<adım adı>.error = { status, message }: cancelled (kapattı) · denied (kamera izni yok) · unavailable (derleme kamerayı taşımıyor ya da tarayıcı zaten açık) · unsupported (web uygulaması) · invalid (sözlük dışı tür). Buradaki uyarı vazgeçmeyi öteki durumlardan ayırır; error dalı koşunca always KOŞMAZ.",
|
|
13
|
+
"setter": "Okunan değer setter ile alana yazılır (element + property 'value', değer kaynağı code: Steps.scan.data.value). Değeri bir uca gönderecekseniz setter yerine microservice adımının parametresine aynı ifadeyi yazın.",
|
|
14
|
+
"placement": "Tarama düğmesi SAYFADA durur. Özel sekme çubuğunun (tabs.style: custom) bileşenindeki bir düğmeden başlatılan tarama sonuç vermez: tarayıcı açılınca çubuk sökülür ve akışı durur (bilinen açık — koddan okundu, cihazda ölçülmedi: zeyrek-frontend/docs/issues.md ARC-298).",
|
|
15
|
+
"focus": "Tarayıcı kapanınca sayfanın focus akışları koşar (başka ekrandan dönüş bir odaktır) — taramanın devamından SONRA. Odakta alanı sıfırlayan bir akış varsa okunan değeri ezer.",
|
|
16
|
+
"preview": "Önizleme (/dev, page_screenshot) kamera açmaz, telefon çerçevesinin içinde 'kod gir' penceresi çizer. page_screenshot ile sürmek için captureUnready: true ŞARTTIR — akış pencereyi beklerken 'koşuyor' sayılır ve araç onsuz 'akış hâlâ koşuyor' ile düşer: actions [click btn_tara, set __scan-value, click __scan-type-<tür> (çok türlü adımda), click __scan-confirm]; error dalı için __scan-cancel. Pencere açıkken her eylem bekleme bütçesini (15 sn) doldurur. Ölçüldü 2026-10-12: onaydan sonra in_barkod value=8690000000017, txt_kaynak 'ean13 · elle girildi'; vazgeçince runtimeErrors code 'cancelled'.",
|
|
17
|
+
"build": "Kamera izni ve modülü mağaza derlemesine kendiliğinden girer: adım production'daki bir akıştaysa derleme kamerayı taşır. Kamerayı taşımayan derleme (adım derlemeden SONRA deploy edildiyse) unavailable döner; 0.5.0'dan eski kabuk adımı hiç tanımaz. İkisinde de kullanıcıya yeni sürüm gerekir — yayın ön kontrolünün P-14 satırı söyler (platform_guide('mobile-publish'))."
|
|
18
|
+
},
|
|
19
|
+
"pages": [
|
|
20
|
+
{
|
|
21
|
+
"pageCreateArgs": {
|
|
22
|
+
"name": "Ürün sorgula",
|
|
23
|
+
"slug": "urun-sorgula"
|
|
24
|
+
},
|
|
25
|
+
"schemaPatchOps": [
|
|
26
|
+
{
|
|
27
|
+
"op": "add_component",
|
|
28
|
+
"parent": "page_container",
|
|
29
|
+
"component": {
|
|
30
|
+
"elementId": "txt_baslik",
|
|
31
|
+
"name": "txt_baslik",
|
|
32
|
+
"type": "text",
|
|
33
|
+
"kind": "component",
|
|
34
|
+
"props": {
|
|
35
|
+
"content": {
|
|
36
|
+
"type": "static",
|
|
37
|
+
"ref": null,
|
|
38
|
+
"data": "Ürün sorgula"
|
|
39
|
+
},
|
|
40
|
+
"type": {
|
|
41
|
+
"type": "static",
|
|
42
|
+
"ref": null,
|
|
43
|
+
"data": "h2"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"appearance": {
|
|
47
|
+
"marginType": "none",
|
|
48
|
+
"widthType": "fill"
|
|
49
|
+
},
|
|
50
|
+
"blocks": [],
|
|
51
|
+
"events": [],
|
|
52
|
+
"validations": [],
|
|
53
|
+
"stateBindings": [],
|
|
54
|
+
"children": []
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"op": "add_component",
|
|
59
|
+
"parent": "page_container",
|
|
60
|
+
"component": {
|
|
61
|
+
"elementId": "grp_barkod",
|
|
62
|
+
"name": "grp_barkod",
|
|
63
|
+
"type": "container",
|
|
64
|
+
"kind": "component",
|
|
65
|
+
"props": {
|
|
66
|
+
"direction": {
|
|
67
|
+
"type": "static",
|
|
68
|
+
"ref": null,
|
|
69
|
+
"data": "column"
|
|
70
|
+
},
|
|
71
|
+
"horizontalAlign": {
|
|
72
|
+
"type": "static",
|
|
73
|
+
"ref": null,
|
|
74
|
+
"data": "stretch"
|
|
75
|
+
},
|
|
76
|
+
"gap": {
|
|
77
|
+
"type": "static",
|
|
78
|
+
"ref": null,
|
|
79
|
+
"data": "medium"
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"appearance": {
|
|
83
|
+
"marginType": "none",
|
|
84
|
+
"widthType": "fill",
|
|
85
|
+
"paddingType": "none"
|
|
86
|
+
},
|
|
87
|
+
"blocks": [],
|
|
88
|
+
"events": [],
|
|
89
|
+
"validations": [],
|
|
90
|
+
"stateBindings": [],
|
|
91
|
+
"children": [
|
|
92
|
+
{
|
|
93
|
+
"elementId": "in_barkod",
|
|
94
|
+
"name": "in_barkod",
|
|
95
|
+
"type": "textinput",
|
|
96
|
+
"kind": "component",
|
|
97
|
+
"props": {
|
|
98
|
+
"label": {
|
|
99
|
+
"type": "static",
|
|
100
|
+
"ref": null,
|
|
101
|
+
"data": "Barkod"
|
|
102
|
+
},
|
|
103
|
+
"placeholder": {
|
|
104
|
+
"type": "static",
|
|
105
|
+
"ref": null,
|
|
106
|
+
"data": "Okut ya da yaz"
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"appearance": {
|
|
110
|
+
"marginType": "none",
|
|
111
|
+
"widthType": "fill"
|
|
112
|
+
},
|
|
113
|
+
"blocks": [],
|
|
114
|
+
"events": [],
|
|
115
|
+
"validations": [],
|
|
116
|
+
"stateBindings": [],
|
|
117
|
+
"children": []
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"elementId": "txt_kaynak",
|
|
121
|
+
"name": "txt_kaynak",
|
|
122
|
+
"type": "text",
|
|
123
|
+
"kind": "component",
|
|
124
|
+
"props": {
|
|
125
|
+
"content": {
|
|
126
|
+
"type": "static",
|
|
127
|
+
"ref": null,
|
|
128
|
+
"data": ""
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
"appearance": {
|
|
132
|
+
"marginType": "none",
|
|
133
|
+
"widthType": "fill"
|
|
134
|
+
},
|
|
135
|
+
"blocks": [],
|
|
136
|
+
"events": [],
|
|
137
|
+
"validations": [],
|
|
138
|
+
"stateBindings": [],
|
|
139
|
+
"children": []
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"elementId": "btn_tara",
|
|
143
|
+
"name": "btn_tara",
|
|
144
|
+
"type": "button",
|
|
145
|
+
"kind": "component",
|
|
146
|
+
"props": {
|
|
147
|
+
"text": {
|
|
148
|
+
"type": "static",
|
|
149
|
+
"ref": null,
|
|
150
|
+
"data": "Barkod okut"
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
"appearance": {
|
|
154
|
+
"marginType": "none",
|
|
155
|
+
"widthType": "fill"
|
|
156
|
+
},
|
|
157
|
+
"blocks": [],
|
|
158
|
+
"events": [],
|
|
159
|
+
"validations": [],
|
|
160
|
+
"stateBindings": [],
|
|
161
|
+
"children": []
|
|
162
|
+
}
|
|
163
|
+
]
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
],
|
|
167
|
+
"scanFlowCreateArgs": {
|
|
168
|
+
"host": "page",
|
|
169
|
+
"hostId": "<urun-sorgula sayfa id>",
|
|
170
|
+
"name": "barkod okut",
|
|
171
|
+
"slug": "barkod-okut",
|
|
172
|
+
"steps": [
|
|
173
|
+
{
|
|
174
|
+
"stepId": "start",
|
|
175
|
+
"name": "start",
|
|
176
|
+
"type": "start",
|
|
177
|
+
"kind": "normal",
|
|
178
|
+
"args": {},
|
|
179
|
+
"branches": [
|
|
180
|
+
{
|
|
181
|
+
"id": "<GUID>",
|
|
182
|
+
"name": "always",
|
|
183
|
+
"nextStepId": "scan"
|
|
184
|
+
}
|
|
185
|
+
]
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"stepId": "scan",
|
|
189
|
+
"name": "scan",
|
|
190
|
+
"type": "scan-code",
|
|
191
|
+
"kind": "normal",
|
|
192
|
+
"args": {
|
|
193
|
+
"value": "ean13,ean8,qr",
|
|
194
|
+
"title": "Ürünün barkodunu okut"
|
|
195
|
+
},
|
|
196
|
+
"branches": [
|
|
197
|
+
{
|
|
198
|
+
"id": "<GUID>",
|
|
199
|
+
"name": "always",
|
|
200
|
+
"nextStepId": "write_code"
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
"id": "<GUID>",
|
|
204
|
+
"name": "error",
|
|
205
|
+
"nextStepId": "warn"
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"stepId": "write_code",
|
|
211
|
+
"name": "write_code",
|
|
212
|
+
"type": "setter",
|
|
213
|
+
"kind": "normal",
|
|
214
|
+
"args": {
|
|
215
|
+
"iteratees": [
|
|
216
|
+
{
|
|
217
|
+
"id": "<GUID>",
|
|
218
|
+
"type": "change",
|
|
219
|
+
"source": "component",
|
|
220
|
+
"element": "in_barkod",
|
|
221
|
+
"property": "value",
|
|
222
|
+
"mapping": {
|
|
223
|
+
"source": "",
|
|
224
|
+
"mappings": {}
|
|
225
|
+
},
|
|
226
|
+
"value": {
|
|
227
|
+
"type": "code",
|
|
228
|
+
"ref": "Steps.scan.data.value"
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
"id": "<GUID>",
|
|
233
|
+
"type": "change",
|
|
234
|
+
"source": "component",
|
|
235
|
+
"element": "txt_kaynak",
|
|
236
|
+
"property": "content",
|
|
237
|
+
"mapping": {
|
|
238
|
+
"source": "",
|
|
239
|
+
"mappings": {}
|
|
240
|
+
},
|
|
241
|
+
"value": {
|
|
242
|
+
"type": "code",
|
|
243
|
+
"ref": "`${Steps.scan.data.type ?? 'tür bilinmiyor'} · ${Steps.scan.data.source === 'camera' ? 'kameradan' : 'elle girildi'}`"
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
]
|
|
247
|
+
},
|
|
248
|
+
"branches": [
|
|
249
|
+
{
|
|
250
|
+
"id": "<GUID>",
|
|
251
|
+
"name": "always",
|
|
252
|
+
"nextStepId": null
|
|
253
|
+
}
|
|
254
|
+
]
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
"stepId": "warn",
|
|
258
|
+
"name": "warn",
|
|
259
|
+
"type": "alert",
|
|
260
|
+
"kind": "normal",
|
|
261
|
+
"args": {
|
|
262
|
+
"message": "{{ Steps.scan.error.status === 'cancelled' ? 'Kod okunmadı' : 'Kamera kullanılamıyor; barkodu elle yazabilirsin' }}",
|
|
263
|
+
"alertType": "info"
|
|
264
|
+
},
|
|
265
|
+
"branches": [
|
|
266
|
+
{
|
|
267
|
+
"id": "<GUID>",
|
|
268
|
+
"name": "always",
|
|
269
|
+
"nextStepId": null
|
|
270
|
+
}
|
|
271
|
+
]
|
|
272
|
+
}
|
|
273
|
+
]
|
|
274
|
+
},
|
|
275
|
+
"bindOps": [
|
|
276
|
+
{
|
|
277
|
+
"op": "set_event",
|
|
278
|
+
"target": "btn_tara",
|
|
279
|
+
"event": "click",
|
|
280
|
+
"eventValue": "<barkod okut akış id>"
|
|
281
|
+
}
|
|
282
|
+
]
|
|
283
|
+
}
|
|
284
|
+
]
|
|
285
|
+
}
|
|
@@ -88,7 +88,7 @@ arayüzdür**, yetkiyi backend her istekte yeniden denetler.
|
|
|
88
88
|
|
|
89
89
|
- **Satır metası.** Table-source uçtan dönen satırda korumalı alan varsa
|
|
90
90
|
`__protected: { <alanSlug>: 'masked' | 'plain' }` bulunur; `hidden` alan satırda hiç yoktur.
|
|
91
|
-
`
|
|
91
|
+
`datatable` · `detailview` · `text` (`record` + `recordField` ile) `masked` alanı
|
|
92
92
|
kilitli maske olarak çizer; "Göster" → gerekçe → değer 30 sn görünür, sonra yeniden maskelenir.
|
|
93
93
|
"Göster" yalnız erişim özeti yüklüyken çıkar: `off`'ta (basit proje hep `off`) hiç çıkmaz —
|
|
94
94
|
backend her reveal'i reddeder; `shadow`'da çıkar — backend reveal'i yine denetler, yetkisiz
|
|
@@ -109,7 +109,7 @@ arayüzdür**, yetkiyi backend her istekte yeniden denetler.
|
|
|
109
109
|
maskeli okumak yazmayı kapatmaz. Maskeli hücre düzenlemeye **boş** açılır, yazılan değer gider, boş bırakılan
|
|
110
110
|
maske gitmez; hücre seçimine girmez, toplu düzenleme maskeli kolonu sunmaz. Korumalı alanı güncellemek için
|
|
111
111
|
form kurmak zorunlu değildir. Eski değeri görerek düzeltmek gerekiyorsa önce "Göster" (30 sn, editöre ön
|
|
112
|
-
doldurulmaz). Bağlı olmayan tabloda
|
|
112
|
+
doldurulmaz). Bağlı olmayan tabloda maskeli hücre düzenlenmez.
|
|
113
113
|
- **Boş değerin gönderilmemesi isteğin kuruluşuna bağlıdır.** Microservice çağrısının parametre
|
|
114
114
|
bağı boş (`null`/`''`) değeri gövdeye koymaz → saklı değer korunur. Gövdeyi akış kodunda ya da
|
|
115
115
|
setter göreviyle elle kuruyorsan (`{ tckn: TextInput1.value }`) `null` gider: `canWrite` yoksa
|
|
@@ -278,7 +278,7 @@ Yol joker olabilir: `series[].data` kaydı `series.1.data` prop'unu kapsar. Bağ
|
|
|
278
278
|
|
|
279
279
|
## Komponent seçimi
|
|
280
280
|
|
|
281
|
-
"Hangi işe hangi komponent" tablosu ve `
|
|
281
|
+
"Hangi işe hangi komponent" tablosu ve `datatable` kurulumu:
|
|
282
282
|
`platform_guide('page')` Kural 1e ve 7e. Kaçınılacak tek şey yarım kurulumdur (süzme
|
|
283
283
|
arayüzü açık, arkası statik uç). Hazır komponentlerin hiçbiri işi karşılamıyorsa `html`
|
|
284
284
|
kaçış kapısıdır — yeteneklerini `platform_guide('html')` anlatır; "platform bunu yapamaz"
|
|
@@ -97,8 +97,8 @@ dalından devam eder.
|
|
|
97
97
|
|
|
98
98
|
| Global | İçerik |
|
|
99
99
|
|---|---|
|
|
100
|
-
| `Page` | `Page.user`, `Page.variables`, `Page.components`, `Page.properties`, `Page.queryParams`, `Page.authorized(roles)` |
|
|
101
|
-
| **Komponent adları** | Her komponent adıyla doğrudan değişkendir: `specialties_table.rows = data` prop'u yazar ve UI'ı günceller; `setter` adımı gibi bileşenin `change` akışını **koşturmaz** — `change` akışı yalnız kullanıcı etkileşiminde koşar (2026-10-08; öncesindeki runtime her kod yazımında `change` akışını koştururdu). Addaki `-` silinir: `tickets-table` → `ticketstable` |
|
|
100
|
+
| `Page` | `Page.user`, `Page.variables`, `Page.components`, `Page.properties`, `Page.queryParams`, `Page.authorized(roles)`, `Page.language` (etkin dil kodu, salt okunur), `Page.setLanguage(kod)` (dili değiştirir: seçim saklanır, bütün bağlar yeni dilde çözülür; projede olmayan kod yok sayılır — 2026-10-14 frontend, uzak kurulumda dağıtımdan sonra; ARC-097/098) |
|
|
101
|
+
| **Komponent adları** | Her komponent adıyla doğrudan değişkendir: `specialties_table.rows = data` prop'u yazar ve UI'ı günceller. Çeviriye bağlı prop **ekrandaki metinle** okunur, iç içe dizilerdekiler dahil (`menu.navigations[0].title`, `drawer.actions[0].text`); okunan diziyi aynı metinle geri yazmak çeviri bağını korur (2026-10-14 frontend, uzak kurulumda dağıtımdan sonra — öncesinde okuma `null` verirdi); `setter` adımı gibi bileşenin `change` akışını **koşturmaz** — `change` akışı yalnız kullanıcı etkileşiminde koşar (2026-10-08; öncesindeki runtime her kod yazımında `change` akışını koştururdu). Addaki `-` silinir: `tickets-table` → `ticketstable` |
|
|
102
102
|
| `state.page` / `state.app` | Sayfa/uygulama state'i (oku/yaz): `state.page.selectedId = row.id`. **Bellekte yaşar** — sayfa her açıldığında tanımlı `value`'dan başlar; mobil uygulamada kalıcı olması için değişkene `storage` verilir (`platform_guide('mobile')` §Kalıcı state). **Tanımlı değişkenler de buradadır:** `variable_create` ile yazılan her `key` sayfa düzeyinde `state.page.<key>`, uygulama düzeyinde `state.app.<key>` olur; `value` nesneyse alanlarına inilir (`state.page.MyTest.isActive`) |
|
|
103
103
|
| `Steps` | Adım çıktıları: `Steps.load_specialties` — anahtar adımın **ADIdır**, `stepId` değil (`setStorage(task.name, …)`); `Steps.<adım>.error` o adımın hatası: `{ status, message, body }` — `message` ucun kendi mesajı (`err.reason`, boşsa HTTP durum metni), `status` HTTP kodu, `body` ucun hata gövdesi (`microservice.ts` `.catch`; tablo `platform_guide('page')`). `Steps.error` ise hata akışı için doldurulur (`onError` koşmadan önce `setStorage('error', …)` yapar) ve **yalnız orada okunur**: storage sayfa başına tek `FlowMachine`'de yaşar, ama dışarıdan başlayan her akış başlarken `'error'`'u ve kendi adımlarının önceki değerlerini temizler (`flow-engine.ts` `run`, 2026-09-25) — normal akışta `Steps.error` boştur; hata akışı ve onun tetiklediği akış (`nested`) onu görür ve şekli `{ message, code, step: { id, name, type, data } }`'dir: kullanıcıya gösterilecek metin `Steps.error.message`'tır (`err.response.data.message`, boşsa durum metni). **Başarılı bir adımda `Steps.<adım>.data` gerçek bir `Array`'dir** — `.map` · `.filter` · `.concat` · `.length` çalışır. Adım HATA dönerse `Steps.<adım>.data` `null`'dır, gövde `.error.body`'dedir; aşağıdaki kutuya bakın |
|
|
104
104
|
| `self` · `item` · `Row` / `RowIndex` | Tetikleyen komponent (yazılabilir) · repeater/tablo satırı (yazmak YALNIZ o satırı günceller). Satır AKIŞINDA (satırdaki düğümün olayı) ARC-121 düzeltmesini (2026-09-27) taşıyan runtime gerekir, eskisinde boştu — sürümden bağımsız yol `<liste>.selectedItem` / `.selectedIndex`. Tekrarlayıcının KENDİ `change`/`select` akışında satır bağlamı yoktur. `item.x = …` yazımı da aynı kuraldadır: o satırı günceller, tekrarlayıcının `change` akışını koşturmaz |
|