@appaflytech/zeyrek-ai-mcp 0.0.61 → 0.0.63
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 +6 -0
- package/dist/config.js +2 -0
- package/dist/knowledge/backendConstraints.js +2 -2
- package/dist/knowledge/mobileStore.js +24 -4
- package/dist/knowledge/normalize.js +10 -0
- package/dist/knowledge/platform/mobileLayoutLint.js +8 -0
- package/dist/knowledge/platform/mobileRules.js +6 -0
- package/dist/knowledge/rules.js +89 -0
- package/dist/observability/gapSink.js +43 -0
- package/dist/platform/argumentAliases.js +6 -0
- package/dist/platform/capture/actions.js +71 -2
- package/dist/platform/capture/capture.js +5 -2
- package/dist/platform/responses.js +5 -2
- package/dist/platform/writeDiff.js +52 -1
- package/dist/resources/examples/middleware-password-reset.json +1281 -0
- package/dist/resources/examples/mobile-reorder-persist.json +33 -7
- package/dist/resources/examples/workflow-email-step.json +67 -0
- package/dist/resources/guides/deployment.md +13 -1
- package/dist/resources/guides/entity.md +7 -0
- package/dist/resources/guides/expressions.md +5 -0
- package/dist/resources/guides/getting-started.md +15 -30
- package/dist/resources/guides/microservice.md +30 -2
- package/dist/resources/guides/middleware.md +83 -3
- package/dist/resources/guides/mobile-layout.md +35 -3
- package/dist/resources/guides/mobile-publish.md +33 -1
- package/dist/resources/guides/mobile-steps.md +12 -2
- package/dist/resources/guides/mobile.md +11 -8
- package/dist/resources/guides/page.md +28 -3
- package/dist/resources/guides/workflow.md +30 -1
- package/dist/resources/schema-fingerprint.json +3 -3
- package/dist/tools/applications.js +6 -3
- package/dist/tools/common.js +8 -0
- package/dist/tools/designSchemaPatch.js +4 -1
- package/dist/tools/flows.js +14 -4
- package/dist/tools/layouts.js +3 -1
- package/dist/tools/microservices.js +8 -2
- package/dist/tools/mobileStore.js +13 -2
- package/dist/tools/pages.js +27 -12
- package/dist/tools/platformInfo.js +15 -6
- package/dist/tools/workflows.js +4 -2
- package/package.json +1 -1
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
"synthetic": true,
|
|
4
4
|
"note": "Sırası kalıcı kaydedilen mobil liste: repeater tutamaçtan sürüklenir, reorder akışı event.data.patches'ı bulk-update ucuna TEK istekte kök dizi olarak yazar, ardından listeyi sıra alanına göre yeniden okur. mobiltest › siralama-kalici'de (lokal, 2026-10-27) uçta ölçülen sayfanın kimliksiz hâli: sürükleme → tek PUT 200, yeniden okunan satırlar yeni sırada. Sayfa mizanpaj sözleşmesine ve MOB-035'e uyar, akışlar mobil profilden ve kendi lint kurallarından bulgusuz geçer (test/mobile-rules.test.ts, test/mobile-layout-lint.test.ts).",
|
|
5
5
|
"pairsWith": "mobile-offline-list (aynı listenin okuma deseni), mobile-list-detail-form (satıra dokunma), mobile-app-shell-stack ya da mobile-app-shell-tab (kabuk)",
|
|
6
|
-
"sourceRef": "platform_guide('page') §Kural 1g-5; platform_guide('microservice') §Toplu
|
|
6
|
+
"sourceRef": "platform_guide('page') §Kural 1g-5; platform_guide('microservice') §Toplu yazmada sahiplik (MS-027); MOB-035; zeyrek-frontend docs/adr/0029-akis-adiminda-kok-dizi-govde.md"
|
|
7
7
|
},
|
|
8
8
|
"_rules": {
|
|
9
9
|
"list": "repeater'a reorderable: true + orderField: '<sayı alanı>' yazılır; tutamaç her satırın sonunda çizilir. Yalnız dikey ve tek sütunlu listede çalışır — direction: 'horizontal' ya da columnCount > 1 MOB-035 uyarısıdır ve tutamaç çizilmez. Satırlar orderField'a göre çizilir; okuma ucunun sıralı dönmesi yine de yenilemede aynı sırayı garanti eder.",
|
|
10
10
|
"payload": "Bırakınca reorder yayılır: event.data = { fromIndex, toIndex, row, patches: [{ id, value }] }. patches yalnız sırası değişen satırlardır; id satırın _id'si (yoksa id), value orderField'a yazılacak 1 tabanlı yeni adım. Yerinde bırakılan sürükleme olay yaymaz; orderField yoksa ya da bir satırda anahtar yoksa patches BOŞ dizidir.",
|
|
11
|
-
"endpoint": "Yazma ucu tablo kaynaklı operation: 'bulk-update'; fields = birincil anahtar + sıra alanı, başka alan yok (tanımdaki alan gövdede yoksa NULL yazılır). Gövdede birincil anahtar zorunludur, WHERE'e taşınır. Tek istekte en fazla 1000 satır, parti hep-ya-hiç.",
|
|
11
|
+
"endpoint": "Yazma ucu tablo kaynaklı operation: 'bulk-update'; fields = birincil anahtar + sıra alanı, başka alan yok (tanımdaki alan gövdede yoksa NULL yazılır). Gövdede birincil anahtar zorunludur, WHERE'e taşınır. Tek istekte en fazla 1000 satır, parti hep-ya-hiç. Uç oturum ister (middlewareIds: auth middleware) ve filters'ta sahip alanını kapsar: { field: <sahip alanı>, operator: 'eq', source: 'expression', value: 'user.id' }. Platform bu kapsamı her satırın UPDATE'ine koyar; başkasının kimliği gelirse bütün parti 404 ile reddedilir, hiçbir satır yazılmaz (MS-027, zeyrek-backend ADR 0074). Kapsam alanını fields'a koymayın: uç onu yazarsa kayıt reddedilir.",
|
|
12
12
|
"bodyKeys": "Gövde anahtarları alan slug'larıdır; fields[].name ile yeniden adlandırma yoktur. { id, value } yaması uca olduğu gibi gitmez: map ile { id, <orderField>: value }'ya eşlenir.",
|
|
13
13
|
"rootArray": "Satır listesi ucun gövde parametrelerinden YALNIZ BİRİNE (birincil anahtarınkine) code ile verilir, öbürleri hiç yazılmaz. Servis şeması isParameterArray dediği için değeri olan tek gövde parametresinin dizisi gövdenin KENDİSİ olur — parametre adı anlamını yitirir. İkinci bir gövde parametresine değer verilirse gövde nesneye döner ve platform tek satır okur: 422 'Please provide the primary key value'. ADR 0029'dan eski platformda bu akış da 422 ile düşer.",
|
|
14
14
|
"onError": "Servis hata dönerse liste kendiliğinden eski sıraya DÖNMEZ: yeniden okuma sürüklemeden önceki satırların aynısını getirir, liste aynı satırlarda tuttuğu sırayı korur ve ekran kaydedilmemiş sırada kalır. Hatayı Steps.<yazma adımı>.error ile kullanıcıya gösterin; eski sıraya döndürmenin doğrulanmış bir yolu yoktur.",
|
|
15
|
-
"verify": "page_screenshot({ applicationSlug: '<uygulama>', pageSlug: 'siralama', actions: [{ action: 'swipe', target: 'lst_gorev__row-2__handle', to: 'lst_gorev__row-0' }] }) üçüncü satırı başa taşır; txt_sonuc 'kaydedildi 3 satır' yazar ve row_list (order sira) aynı sırayı verir. Tutamaç kimliği yalnız çalışma anında basılır. Her denemeden sonra satırları bulk-update ile ilk sıralarına geri yazın."
|
|
15
|
+
"verify": "page_screenshot({ applicationSlug: '<uygulama>', pageSlug: 'siralama', actions: [{ action: 'swipe', target: 'lst_gorev__row-2__handle', to: 'lst_gorev__row-0' }] }) üçüncü satırı başa taşır; txt_sonuc 'kaydedildi 3 satır' yazar ve row_list (order sira) aynı sırayı verir. Tutamaç kimliği yalnız çalışma anında basılır. Her denemeden sonra satırları bulk-update ile ilk sıralarına geri yazın. Sahiplik: aynı ucu ikinci bir kullanıcının oturumuyla, gövdeye birinci kullanıcının bir satır kimliği koyarak microservice_execute ile çağırın — yanıt 404, row_list'te hiçbir satırın sırası değişmez."
|
|
16
16
|
},
|
|
17
17
|
"microservices": [
|
|
18
18
|
{
|
|
@@ -33,7 +33,17 @@
|
|
|
33
33
|
"alias": "gorev",
|
|
34
34
|
"isSingle": false,
|
|
35
35
|
"joins": [],
|
|
36
|
-
"filters": [
|
|
36
|
+
"filters": [
|
|
37
|
+
{
|
|
38
|
+
"filterType": "single",
|
|
39
|
+
"conditionType": "and",
|
|
40
|
+
"alias": "gorev",
|
|
41
|
+
"fieldSlug": "sahip",
|
|
42
|
+
"operator": "eq",
|
|
43
|
+
"source": "expression",
|
|
44
|
+
"value": "user.id"
|
|
45
|
+
}
|
|
46
|
+
],
|
|
37
47
|
"selectFields": [
|
|
38
48
|
{
|
|
39
49
|
"alias": "gorev",
|
|
@@ -58,7 +68,10 @@
|
|
|
58
68
|
"offset": "offset",
|
|
59
69
|
"source": "query-string"
|
|
60
70
|
}
|
|
61
|
-
}
|
|
71
|
+
},
|
|
72
|
+
"middlewareIds": [
|
|
73
|
+
"<auth middleware id>"
|
|
74
|
+
]
|
|
62
75
|
}
|
|
63
76
|
},
|
|
64
77
|
{
|
|
@@ -79,7 +92,17 @@
|
|
|
79
92
|
"alias": "gorev",
|
|
80
93
|
"isSingle": true,
|
|
81
94
|
"joins": [],
|
|
82
|
-
"filters": [
|
|
95
|
+
"filters": [
|
|
96
|
+
{
|
|
97
|
+
"filterType": "single",
|
|
98
|
+
"conditionType": "and",
|
|
99
|
+
"alias": "gorev",
|
|
100
|
+
"fieldSlug": "sahip",
|
|
101
|
+
"operator": "eq",
|
|
102
|
+
"source": "expression",
|
|
103
|
+
"value": "user.id"
|
|
104
|
+
}
|
|
105
|
+
],
|
|
83
106
|
"selectFields": [],
|
|
84
107
|
"fields": [
|
|
85
108
|
{
|
|
@@ -104,7 +127,10 @@
|
|
|
104
127
|
"offset": "offset",
|
|
105
128
|
"source": "query-string"
|
|
106
129
|
}
|
|
107
|
-
}
|
|
130
|
+
},
|
|
131
|
+
"middlewareIds": [
|
|
132
|
+
"<auth middleware id>"
|
|
133
|
+
]
|
|
108
134
|
}
|
|
109
135
|
}
|
|
110
136
|
],
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_meta": {
|
|
3
|
+
"synthetic": true,
|
|
4
|
+
"note": "email adımı: parametreden gelen tek alıcıya HTML ileti gönderir; başarıda 200, alıcı yanlışsa ya da SMTP düşerse fail dalından 400 döner. Alıcı YALNIZ rule.expression ile verilir ve { to, cc, bcc } adres dizgisi listeleri döndürür. Backend sözleşmesi koddan okundu ve testle sabitlendi (zeyrek-backend EmailStepTests, incident #197, 2026-11-04); aynı adım şekli lokal yığında ölçüldü (2026-11-04, workflow_preview → mailpit'e tek ileti, doğru alıcı; to: [] → fail dalı EmailRecipientsMissingException).",
|
|
5
|
+
"sourceRef": "platform_guide('workflow') §Email adımı; WF-037; zeyrek-backend EmailStep.cs EvaluateAsync, StepSchemaGenerator.cs (email)"
|
|
6
|
+
},
|
|
7
|
+
"_rules": {
|
|
8
|
+
"alici": "Alıcı rule.expression: \"({ to: [input.email] })\". cc ve bcc aynı biçimde isteğe bağlıdır. Öğe adres DİZGİSİDİR; { type, value } nesnesi EmailRecipientInvalidException ile fail dalına düşer.",
|
|
9
|
+
"okunmayanAnahtarlar": "rule.send / send.toMail / to / toMail / sendTo / recipients kural düzeyinde OKUNMAZ, platform bağlarken atar; gateway WF-037 ile kaydı durdurur. workflow_step_schema'daki expression.to/cc/bcc ifadenin döndürdüğü şekildir.",
|
|
10
|
+
"govde": "rule.body HTML olarak gider; şablon dizgisiyle değer gömülür. rule.subject konu, rule.displayName gönderen ADI (gönderen adresi platform ayarıdır).",
|
|
11
|
+
"hata": "Alıcı yoksa EmailRecipientsMissingException, öğe dizgi değilse EmailRecipientInvalidException, SMTP hatası da aynı fail dalına düşer; <adım>.error.code hata anahtarını verir.",
|
|
12
|
+
"verify": "workflow_preview({ input: { email: 'deneme@zeyrek.local' } }) → 200 { gonderildi: true }; curl -s localhost:8025/api/v1/messages son iletide To=deneme@zeyrek.local, Subject='Doğrulama kodu'. Önizleme gerçekten gönderir."
|
|
13
|
+
},
|
|
14
|
+
"definition": {
|
|
15
|
+
"name": "Doğrulama Kodu Gönder",
|
|
16
|
+
"slug": "dogrulama-kodu-gonder",
|
|
17
|
+
"description": "Verilen adrese tek kullanımlık doğrulama kodu e-postası gönderir.",
|
|
18
|
+
"type": "workflow",
|
|
19
|
+
"parameterDtoId": "<girdi DTO id: { email: string }>",
|
|
20
|
+
"parameterBehavior": "object",
|
|
21
|
+
"returnDtoId": "<çıktı DTO id: { gonderildi: boolean, hata?: string }>",
|
|
22
|
+
"returnBehavior": "object",
|
|
23
|
+
"steps": [
|
|
24
|
+
{
|
|
25
|
+
"id": "<GUID-1>",
|
|
26
|
+
"name": "Start",
|
|
27
|
+
"type": "start",
|
|
28
|
+
"description": "Akışı başlatır.",
|
|
29
|
+
"branches": [{ "id": "<GUID-2>", "name": "start", "type": "success", "nextStepId": "<GUID-3>" }]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": "<GUID-3>",
|
|
33
|
+
"name": "kodGonder",
|
|
34
|
+
"type": "email",
|
|
35
|
+
"description": "Kodu girdideki adrese HTML ileti olarak gönderir.",
|
|
36
|
+
"rule": {
|
|
37
|
+
"stepType": "email",
|
|
38
|
+
"expression": "({ to: [input.email] })",
|
|
39
|
+
"subject": "'Doğrulama kodu'",
|
|
40
|
+
"body": "`<p>Doğrulama kodunuz: <b>482913</b></p><p>Kod 10 dakika geçerlidir.</p>`",
|
|
41
|
+
"displayName": "'Defter'",
|
|
42
|
+
"attachment": "[]"
|
|
43
|
+
},
|
|
44
|
+
"branches": [
|
|
45
|
+
{ "id": "<GUID-4>", "name": "success", "type": "success", "nextStepId": "<GUID-5>" },
|
|
46
|
+
{ "id": "<GUID-6>", "name": "fail", "type": "fail", "nextStepId": "<GUID-7>" }
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"id": "<GUID-5>",
|
|
51
|
+
"name": "Gonderildi",
|
|
52
|
+
"type": "response",
|
|
53
|
+
"description": "Gönderimin başarılı olduğunu döner.",
|
|
54
|
+
"rule": { "stepType": "response", "bodyExpression": "({ gonderildi: true })", "httpStatusCode": 200 },
|
|
55
|
+
"branches": []
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"id": "<GUID-7>",
|
|
59
|
+
"name": "Gonderilemedi",
|
|
60
|
+
"type": "response",
|
|
61
|
+
"description": "Gönderim hatasının anahtarını döner.",
|
|
62
|
+
"rule": { "stepType": "response", "bodyExpression": "({ gonderildi: false, hata: kodGonder.error.code })", "httpStatusCode": 400 },
|
|
63
|
+
"branches": []
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -20,7 +20,7 @@ kazasıdır, bu yüzden her tablo ve her dosya ayrıca seçilir.
|
|
|
20
20
|
| Uygulama asistanı | evet | ayar, rehber, beceri, senaryo ve **yayındaki paket** (beceri seti, rehber, menü, sağlama toplamı); hedefte asistan development'ta en son yayınlanan hâliyle çalışır, production'da ayrıca yayın yoktur (ASI-018). Kaynakta hiç yayın yoksa hedefin paketi korunur |
|
|
21
21
|
| Tablo satırları | **yalnız tabloda kayıt taşıma açıksa** | §Tablo satırları |
|
|
22
22
|
| Dosyalar | **yalnız id referansıyla** | §Dosyalar |
|
|
23
|
-
| Kullanıcı, rol ataması, süreç örneği, görev, defter | hayır | ortama özgüdür |
|
|
23
|
+
| Kullanıcı, rol ataması, süreç örneği, görev, defter | hayır | ortama özgüdür; test kullanıcısı için §Test kullanıcısını production'a taşımak |
|
|
24
24
|
|
|
25
25
|
Kaynak yalnız `development`'tır (DEP-003); deploy başlamadan production verisi ön denetimden geçer
|
|
26
26
|
(DEP-004). İkisinin ayrıntısı `deployment_start` açıklamasında.
|
|
@@ -83,6 +83,18 @@ Satırlar **tablo başına** açılan kayıt taşıma ayarıyla gider. Ayar enti
|
|
|
83
83
|
Kayıt taşıma deploy'un kuralıdır. Dal açarken `includeRecords: true` bu ayara bakmadan bütün veri
|
|
84
84
|
tablolarını kopyalar; merge satır taşımaz (`platform_guide('merge')` §Dalda çalışmak).
|
|
85
85
|
|
|
86
|
+
### Test kullanıcısını production'a taşımak
|
|
87
|
+
|
|
88
|
+
Kullanıcı ortama özgüdür ve varsayılan olarak taşınmaz. Belirli test kullanıcılarını bir kez taşımak için:
|
|
89
|
+
|
|
90
|
+
1. development'ta `entity_patch users`: `recordTransferMode: 'specific'`, `transferRowIds: ['<kullanıcı id>', …]`,
|
|
91
|
+
`recordTransferSkipExisting: true` (production'da aynı kullanıcı varsa ezilmez).
|
|
92
|
+
2. Deploy'u insan başlatır (production'a giden tek yol). Satır bütün sütunlarıyla gider, **parola özeti dahil**:
|
|
93
|
+
kullanıcı production'da development'taki parolasıyla girer. `'specific'` silmez, `users` aynalanmaz.
|
|
94
|
+
3. Deploy bitince `recordTransferMode`'u `null`'a geri alın; açık kalırsa her deploy aynı satırları yeniden gönderir.
|
|
95
|
+
4. Rol bağını production'da okuyarak doğrulayın (`row_list user_roles`). Eksikse rolü insan Studio'dan atar;
|
|
96
|
+
production'a doğrudan yazılmaz.
|
|
97
|
+
|
|
86
98
|
## Dosyalar
|
|
87
99
|
|
|
88
100
|
`files` tablosu ortama aittir ve **toptan taşınmaz**. Bir dosya production'a yalnız iki yoldan biriyle,
|
|
@@ -405,6 +405,13 @@ sözleşmeden eski bir sürümdedir; gateway o zaman companion'a kendisi yazar v
|
|
|
405
405
|
ile kaç dil satırı yazdığını söyler — `localizationsFailed` ise ana satır yazılmış, dil satırları
|
|
406
406
|
yazılmamıştır. Aşağıdaki elle tarif yalnız o eski yol ve `rows` ile toplu yazma için gerekir.
|
|
407
407
|
|
|
408
|
+
**Workflow tablo adımı ve tablo kaynaklı uç** çok dilli alana yazabilir: adım şemasının sunduğu eşlik tablosu alanı
|
|
409
|
+
(`{slug}_localization`'daki alan) seçilir, platform değeri isteğin dilinde eşlik satırına yazar. Toplu adımda
|
|
410
|
+
(`bulk-create` / `bulk-update`) çok dilli değer yalnız adımın `fields` listesinde yapılandırılırsa yazılır; ifadenin
|
|
411
|
+
satırlarındaki çok dilli anahtar yok sayılır (bir `get` adımından okunan değer varsayılan dilin metnidir, aynen
|
|
412
|
+
yazılsaydı isteğin diline yanlış çeviri olurdu). 2026-11-04'ten eski backend bu yazımda `NullReferenceException`
|
|
413
|
+
(500) verir; o sürümde değeri ayrı bir adımla eşlik tablosuna yazın (yukarıdaki elle tarif).
|
|
414
|
+
|
|
408
415
|
**Kullanım (tamamı mevcut genel araçlarla, yeni tool GEREKMEZ):**
|
|
409
416
|
|
|
410
417
|
```
|
|
@@ -49,6 +49,11 @@ tetikleyen satır `row` / `previousRow` / `user`.
|
|
|
49
49
|
> sıralanabilir hem de datetime filtrelerine doğrudan verilebilir. Gün aritmetiği gerekiyorsa
|
|
50
50
|
> `UtcNow()` üzerinde `getTime()` ile yapın.
|
|
51
51
|
>
|
|
52
|
+
> **Milisaniye:** `UtcNow()` ifade içinde milisaniyeyi taşır ve tablo adımı onu milisaniyesiyle yazar (2026-11-04'ten
|
|
53
|
+
> eski backend saniyeye kırpar). Değeri bir değişkene yazıp SONRAKİ ifadede okursanız saniyeye düşer
|
|
54
|
+
> (`yyyy-MM-ddTHH:mm:ss`). Aynı saniyedeki kayıtları sıralamak için zamanı tablo adımına doğrudan verin ya da
|
|
55
|
+
> `getTime()` sayısını saklayın.
|
|
56
|
+
>
|
|
52
57
|
> **Tuzak:** çözümlenemeyen bir datetime filtre değeri 400 vermez — platform o filtreyi
|
|
53
58
|
> **sessizce düşürür** ve uç TÜM satırları döndürür. Bir tarih filtresi yazdıktan sonra
|
|
54
59
|
> `microservice_debug` ile ifadenin neye çözüldüğüne bakın (`source:'filter'` satırları).
|
|
@@ -73,42 +73,27 @@ Bağımlılıklar bu sırayı zorunlu kılar; atlama:
|
|
|
73
73
|
|
|
74
74
|
Onay zinciri, görev kutusu, SLA gibi insanı bekleyen işler workflow'a sıkıştırılmaz: entity ve (gerekiyorsa) servis workflow'ları hazır olduktan sonra süreç tanımı yazılır — `platform_guide('bpm')`.
|
|
75
75
|
|
|
76
|
-
## İlk kullanıcıyı kim açar?
|
|
76
|
+
## İlk kullanıcıyı kim açar?
|
|
77
77
|
|
|
78
|
-
Kimlik kurulmuş yeni bir projede
|
|
79
|
-
|
|
78
|
+
Kimlik kurulmuş yeni bir projede `users` tablosu **boş** gelir. Studio operatörü (`admin@…`) organizasyon
|
|
79
|
+
kullanıcısıdır, uygulamanın kullanıcısı DEĞİLDİR; kimse onu otomatik kopyalamaz. Middleware bağlanınca her istek
|
|
80
|
+
401 döner (09-02 ve 09-04 koşularında ikisinde de yaşandı).
|
|
80
81
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
açılabilir.
|
|
85
|
-
2. O workflow'un ucu, kimlik kurulduğu için `middlewareIds` ile auth middleware'ine bağlıdır.
|
|
86
|
-
3. Auth middleware'i token'ı **çerezten** okur; ortada henüz kullanıcı yoktur, dolayısıyla token
|
|
87
|
-
da yoktur.
|
|
88
|
-
4. **Yeni projede `users` tablosu BOŞ gelir.** Studio operatörü (`admin@…`) organizasyon
|
|
89
|
-
kullanıcısıdır, uygulamanın kullanıcısı DEĞİLDİR; kimse onu otomatik kopyalamaz.
|
|
90
|
-
|
|
91
|
-
**Bu bir platform boşluğudur, kısa yolu yoktur.** İlk kullanıcıyı açmanın desteklenen tek yolu
|
|
92
|
-
aşağıdaki bootstrap desenidir: geçici bir uç aç, kullanıcıyı yaz, ucu hemen sil. Bakım/migration
|
|
93
|
-
uçlarını (`/organizations/sync-users` gibi `Sync*` uçları) **kullanma** — onlar ürün yüzeyinin
|
|
94
|
-
parçası değildir, özellik geliştirirken veri düzeltmek için yazılmışlardır ve kaldırılmaktadırlar.
|
|
95
|
-
Gateway de onları araç olarak yayınlamaz.
|
|
96
|
-
|
|
97
|
-
Bu tur, kimlik kuran her yeni projede ödenmek zorunda: iki koşuda da (09-02 ve 09-04) `users`
|
|
98
|
-
boş bulundu ve ikisi de bu deseni elle kurdu.
|
|
99
|
-
|
|
100
|
-
**Bootstrap deseni** (crm koşumunda uygulandı):
|
|
82
|
+
İlk yöneticiyi **`row_create` ile `users` tablosuna** yazın. Platform `password` alanını yazarken özetler
|
|
83
|
+
(`Hasher.Hash`, PBKDF2) ve zaten özetli değere dokunmaz. Tek satır da toplu yazım (`rows`) da böyledir. Parolayı
|
|
84
|
+
düz gönderin, kendiniz özetlemeyin.
|
|
101
85
|
|
|
102
86
|
```
|
|
103
|
-
1.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
4. Giriş ucundan token al, sonraki her şeyi normal korumalı yoldan yap.
|
|
87
|
+
1. entity_get users → alan slug'larını okuyun.
|
|
88
|
+
2. row_create users { …kimlik alanları…, password: '<düz parola>' } — özeti platform alır.
|
|
89
|
+
3. Rol bağını kurun (user_roles satırı).
|
|
90
|
+
4. Giriş ucundan token alın; sonraki her şeyi normal korumalı yoldan yapın.
|
|
108
91
|
```
|
|
109
92
|
|
|
110
|
-
|
|
111
|
-
|
|
93
|
+
> **Eski not (2026-09):** o koşularda `row_create` parolayı düz yazıyordu ve kullanıcı geçici, korumasız bir
|
|
94
|
+
> workflow ucuyla açılıyordu (bootstrap deseni). 2026-11-04 ölçümünde `row_create` özetledi ve açılan kullanıcı
|
|
95
|
+
> girdi. Geçici uç açmayın: unutulan uç herkese açık bir kullanıcı yaratma kapısıdır. Bakım uçlarını (`Sync*`) da
|
|
96
|
+
> kullanmayın; ürün yüzeyinin parçası değildir ve gateway onları araç olarak yayınlamaz.
|
|
112
97
|
|
|
113
98
|
## Middleware gerekli mi? (karar tablosu)
|
|
114
99
|
|
|
@@ -212,7 +212,9 @@ DTO'dan süzülmez (WF-026 — `platform_guide('workflow')` §Hata yolu). Sözle
|
|
|
212
212
|
Kurallar:
|
|
213
213
|
- Gövde bir **dizi** olur; her eleman bir satırdır. `bulk-delete` gövdesinde yalnız birincil
|
|
214
214
|
anahtar alanı aranır ve tek bir `IN` filtresine çevrilir.
|
|
215
|
-
- `filters` **
|
|
215
|
+
- `bulk-create` `filters` **kullanmaz** — satırlar gövdeden okunur. `bulk-update`/`bulk-delete`'te `filters`
|
|
216
|
+
**kapsamdır**: gövdedeki kimlikler yalnız kapsamın içindeyse yazılır/silinir (aşağıdaki §Toplu yazmada sahiplik,
|
|
217
|
+
MS-027).
|
|
216
218
|
- Tek istekte en fazla **1000 satır** (`EntityConstants.BulkRowLimit`); aşılırsa
|
|
217
219
|
`BulkRowLimitException` (422).
|
|
218
220
|
- Parti **hep-ya-hiç**tir: bir satır doğrulamadan geçmezse `BulkRowFailedException` hangi
|
|
@@ -404,7 +406,7 @@ KOŞUL belirleyemez, yalnız bir ANAHTAR belirler (yukarıdaki §"Güncelle/sil
|
|
|
404
406
|
|
|
405
407
|
| Ne istiyorsun | Yol |
|
|
406
408
|
|---|---|
|
|
407
|
-
| Anahtarlar elinde, hepsi ya hep ya hiç yazılsın | **`bulk-update` ucu** — gövde satır dizisi,
|
|
409
|
+
| Anahtarlar elinde, hepsi ya hep ya hiç yazılsın | **`bulk-update` ucu** — gövde satır dizisi, tek çağrı, tek transaction (aşağıdaki §Toplu yazmada sahiplik). En fazla 1000 satır; bir satır düşerse hiçbiri yazılmaz |
|
|
408
410
|
| Satır başına SONUÇ ve satır başına YETKİ kararı gerekiyor | **İş akışı + döngü** (BK-05 deseni): liste ucunu çağır → her satır için `microservice` adımı → sayaçları topla → özet döndür |
|
|
409
411
|
|
|
410
412
|
Kapsam ve politika koşulu her iki yolda da satır satır uygulanır; fark sonucun şeklidir. Toplu
|
|
@@ -413,6 +415,32 @@ onay gibi işlerde ikinci yol tercih edilir çünkü yetkisi yetmeyen satır öt
|
|
|
413
415
|
akış "0 onaylandı · 2 reddedildi" döndürdü). Döngüdeki uç, çağıranın kimliğiyle denetlenir —
|
|
414
416
|
akış içinden yazmak yetki YÜKSELTMEZ.
|
|
415
417
|
|
|
418
|
+
### Toplu yazmada sahiplik (MS-027)
|
|
419
|
+
|
|
420
|
+
`bulk-update` ve `bulk-delete` ucunun `filters`'ı **kapsamdır**: gövdedeki her kimlik yalnız bu kapsamın içindeyse
|
|
421
|
+
yazılır ya da silinir (zeyrek-backend ADR 0074, 2026-11-04). Basit erişim modelinde kapsam olmadan uç, istemcinin
|
|
422
|
+
gönderdiği her kimliği — başkasının satırı dahil — yazar.
|
|
423
|
+
|
|
424
|
+
```jsonc
|
|
425
|
+
"middlewareIds": ["<auth middleware id>"],
|
|
426
|
+
"sourceParameters": {
|
|
427
|
+
"operation": "bulk-update",
|
|
428
|
+
"fields": [{ "fieldSlug": "id", "source": "body" }, { "fieldSlug": "sira", "source": "body" }],
|
|
429
|
+
"filters": [{ "filterType": "single", "conditionType": "and", "fieldSlug": "sahip", "operator": "eq", "source": "expression", "value": "user.id" }]
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
| Kural | Sonuç |
|
|
434
|
+
|---|---|
|
|
435
|
+
| Kapsam filtresi kök, `single`, `and`, `eq`; değeri `static` ya da `expression` (`user.id` gibi) | Geçer |
|
|
436
|
+
| Değer `body`/`header`/`query-string`'den ya da onları okuyan bir ifadeden (`body[0].sahip`, `header['x-…']`) geliyor · `eq` dışı işleç · grup · kapsam alanı `fields`'ta da var | Gateway MS-027 ile durdurur; platform `BulkWriteScopeFilterException` (400) |
|
|
437
|
+
| Filtre yok ya da `middlewareIds` boş | MS-027 uyarır; uç her kimliği yazar |
|
|
438
|
+
| Gövdede kapsam dışı tek kimlik | Bütün parti 404, hiçbir satır yazılmaz/silinmez |
|
|
439
|
+
| Kapsam ifadesi boş döner (`user` yok) | 404 |
|
|
440
|
+
|
|
441
|
+
Toplu silme önce sayım yapmaz; siler, eksik silinirse 404 döner ve istek geri alınır. Workflow `table` adımının
|
|
442
|
+
toplu dalları bu kuralı uygulamaz: satırları akış yazarının ifadesi seçer, istemciden gelen kimliği akış süzmelidir.
|
|
443
|
+
|
|
416
444
|
## İlişki alanı: `row_list` NESNE döner, uç/step JOIN'siz DÜZ FK döner
|
|
417
445
|
|
|
418
446
|
Aynı alan iki yoldan farklı şekilde gelir ve bu fark sessizce yanlış sonuç üretir:
|
|
@@ -113,6 +113,9 @@ expression editörü ve senin keşif yolun budur; **değişken adını/alanını
|
|
|
113
113
|
token'ı üretirken `system.generateToken({ … })`'a verdiğin sözlüğün anahtarlarıdır
|
|
114
114
|
(`claims.userId`, `claims.email` …) — `token.data.id` diye evrensel bir alan YOKTUR.
|
|
115
115
|
|
|
116
|
+
Bu rehberdeki `start → X` okları başlangıç adımının dalıdır: `{ name: 'start', type: 'success', nextStepId: X }`.
|
|
117
|
+
Dal tipi `start` değildir; platform 400 döner (WF-038, `platform_guide('workflow')` §Step'ler nasıl bağlanır).
|
|
118
|
+
|
|
116
119
|
```
|
|
117
120
|
start → token [code: return system.validateToken(cookie.<TokenCookie>)]
|
|
118
121
|
token → valid? [condition: token.data.isValid && token.data.claims.<idAnahtarın>]
|
|
@@ -343,6 +346,83 @@ bırakıldığında akış `500 JavaScriptException: user is not defined` verdi.
|
|
|
343
346
|
`platform_guide('microservice')` §"Kimlik kapsaması × uç kaynağı", bağlamanın yazım tuzağı
|
|
344
347
|
`platform_guide('workflow')` §"Query adımı".
|
|
345
348
|
|
|
349
|
+
## Tarif 4 — Şifre sıfırlama (kodla)
|
|
350
|
+
|
|
351
|
+
"Şifremi unuttum" bir middleware değil, iki **anonim** uçtur (`source: 'workflow'`, `middlewareIds: []`)
|
|
352
|
+
ve kullanıcı başına tek satırlık bir kod tablosudur. Hazır parçalar: `ngapps://examples/middleware-password-reset`
|
|
353
|
+
(tablo, dört DTO, iki akış, iki uç). Lokal yığında bu örnekten kurularak uçtan uca ölçüldü (2026-11-04).
|
|
354
|
+
|
|
355
|
+
**Tablo** `password_reset_codes`: `user_id` (UUID, **UNIQUE**), `salt`, `code_hash`, `expires_at`, `sent_at`,
|
|
356
|
+
`window_started_at`, `used_at` (TIMESTAMPTZ), `attempts` (INT4). Kullanıcı başına tek satır olduğu için yeni kod
|
|
357
|
+
satırı günceller ve önceki kod kendiliğinden geçersiz olur.
|
|
358
|
+
|
|
359
|
+
```
|
|
360
|
+
sifre-kodu-iste { email }
|
|
361
|
+
start → kullanici [table get users, email eq input.email]
|
|
362
|
+
→ kullaniciVar? ─ yok → sureEsitle ─────────────────────────────────┐
|
|
363
|
+
→ mevcutKod [table get password_reset_codes, user_id] │
|
|
364
|
+
→ hizSiniri? (sent_at 60 sn'den yeni ya da pencerede hak bitti) ─ evet → sureEsitle
|
|
365
|
+
→ kodUret [code] → satirVar? ─ evet → kodGuncelle (used_at null; attempts/pencere taşınır)
|
|
366
|
+
└ hayır → kodYaz │
|
|
367
|
+
→ eposta [email: to kullanici.data.email] (success ve fail) ───────────┤
|
|
368
|
+
yanit 200 { message: 'Kayıtlıysa e-posta gönderildi.' }
|
|
369
|
+
|
|
370
|
+
sifre-sifirla { email, code, newPassword }
|
|
371
|
+
start → parolaUygun? ─ hayır → 200 { ok: false, … }
|
|
372
|
+
→ kullanici → kullaniciVar? ─ yok → sureEsitle → gecersiz
|
|
373
|
+
→ kod [table get] → kodGecerli? (satır var · used_at boş · attempts < 5 · süresi dolmamış)
|
|
374
|
+
└ hayır → sureEsitle → gecersiz
|
|
375
|
+
→ denemeSay [table update attempts + 1, filtre: id eq kod.data.id AND attempts eq kod.data.attempts]
|
|
376
|
+
→ sayildi? (güncellenen satır döndü mü) ─ hayır → sureEsitle → gecersiz
|
|
377
|
+
→ kodEslesiyor? ─ hayır → gecersiz 200 { ok: false, … }
|
|
378
|
+
→ parolaYaz [table update users.password = input.newPassword]
|
|
379
|
+
→ kodKullanildi (used_at = UtcNow()) → 200 { ok: true, … }
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
- **Kod üretimi:** `GenerateGuid()`'in son 10 hex hanesi v7 GUID'in rastgele bitlerinden gelir:
|
|
383
|
+
`String(parseInt(hex.slice(-10), 16) % 1000000).padStart(6, '0')`. Saklanan değer
|
|
384
|
+
`crypto.SHA256.encode(salt + ':' + code)`, `salt` satıra özgü yeni bir GUID. Bu fonksiyon adına rağmen
|
|
385
|
+
PBKDF2'dir ve tuzu girdinin kendisidir (`platform_guide('expressions')`); tuz eklenmezse aynı 6 hane her
|
|
386
|
+
satırda aynı özeti verir.
|
|
387
|
+
- **Deneme hakkı karşılaştırmadan önce, karşılaştır-ve-yaz ile harcanır.** `denemeSay` sayacı yalnız okunduğu
|
|
388
|
+
değerdeyse artırır. Satırı Postgres kilitler. Aynı anda gelen öteki istek kilit açılınca koşulu yeniden
|
|
389
|
+
değerlendirir, satırı güncelleyemez ve adım boş döner; o istek kodu karşılaştırmadan reddedilir. Okuyup-artıran
|
|
390
|
+
bir adım (`attempts: kod.data.attempts + 1`, filtresiz) paralel isteklerde aynı sayacı okur ve hepsi karşılaştırma
|
|
391
|
+
yapar: 5 sınırı istek sayısı kadar aşılır. Ölçüm:
|
|
392
|
+
- Aynı anda 10 yanlış denemeden 2'si sayıldı, 8'i karşılaştırmadan reddedildi.
|
|
393
|
+
- Aynı doğru kodla aynı anda gelen 5 istekten tam 1'i `ok: true` aldı.
|
|
394
|
+
- **Sayaç yeni kodla sıfırlanmaz.** `window_started_at`'ten 24 saat geçmedikçe `attempts` taşınır; hak bitmişse yeni
|
|
395
|
+
kod da gönderilmez. Böylece hesap başına günde en çok 5 tahmin yapılır (10⁶'da 5). Her yeni kodla 5 hak açan
|
|
396
|
+
sürüm günde ~7 200 tahmine izin verirdi. Bedel: 5 kez yanlış yazan kullanıcı 24 saat bekler.
|
|
397
|
+
- **Sıfırla ucunun her sonucu 2xx döner.** Uç 4xx/5xx ile biterse platform işlemin bütün yazımlarını geri alır
|
|
398
|
+
(`platform_guide('workflow')` §Önizleme çıktısını okumak, "Yazmalar" satırı). 400 dönen ilk sürümde beş yanlış
|
|
399
|
+
denemeden sonra `attempts` 0 kaldı, altıncı denemede doğru kod kabul edildi. İstemci başarıyı `body.ok`'tan okur.
|
|
400
|
+
- **Hesap varlığı sızmaz:** kod iste ucu her durumda aynı gövdeyi döner. Kod karşılaştırılmayan her dal
|
|
401
|
+
`sureEsitle` (`crypto.SHA256.encode(GenerateGuid())`) adımından geçer, çünkü özet hesabı yanıt süresinin
|
|
402
|
+
büyük kısmıdır. Eşitlemesiz sürümde kayıtsız adres 9 ms'de, kayıtlı adres 130 ms'de döndü; eşitlemeden
|
|
403
|
+
sonra iki dal da 117–175 ms aralığında. E-posta gönderim süresi eşitlenmez; SMTP yavaşsa bu fark kalır.
|
|
404
|
+
- **Parola düz yazılır:** platform `users` tablosunun parola alanını yazarken özetler, zaten özetlenmiş
|
|
405
|
+
değere dokunmaz (`row_create` ve table adımı ikisi de). Elle `crypto.SHA256.encode` vermek de çift
|
|
406
|
+
özetlemez.
|
|
407
|
+
- **Hız sınırı:** aynı kullanıcıya 60 sn'de bir kod gider. Aynı anda gelen iki kod isteği eski `sent_at`'i birlikte
|
|
408
|
+
okuyup bu sınırı aşabilir; etkisi fazladan iletidir, sayaç ve pencere taşındığı için tahmin hakkı artmaz. Kod ve
|
|
409
|
+
özet yanıta girmez. `microservice_debug` adım çıktısında kodu gösterir; teşhis ucu olarak açık bırakma.
|
|
410
|
+
- **Açık oturumlar kapanmaz.** Basit projede uygulamanın kendi jetonu süresi dolana kadar geçerli kalır.
|
|
411
|
+
Kompleks projede platform parola değişiminde yenileme jetonu ailesini iptal etmez (PS-32, karar bekliyor).
|
|
412
|
+
Tarif bunu çözmez; jeton ömrünü kısa tut.
|
|
413
|
+
- **Sayfa:** giriş sayfasındaki "Şifremi unuttum" bağlantısı iki adımlı bir sayfaya gider: önce e-posta
|
|
414
|
+
formu (`sifre-kodu-iste`), sonra kod ve yeni parola formu (`sifre-sifirla`, `Steps.<adım>.data.ok`'a göre
|
|
415
|
+
bildirim). Sayfa da anonimdir (`middlewareIds` boş).
|
|
416
|
+
|
|
417
|
+
Doğrulama (lokalde ölçülen sıra), hepsi `microservice_execute(anonymous: true)` ile:
|
|
418
|
+
|
|
419
|
+
1. Kod iste → 200 ve mailpit'te tek ileti (`curl -s 'localhost:8025/api/v1/search?query=to:<email>'`).
|
|
420
|
+
2. Kayıtsız adres → aynı gövde, ileti yok.
|
|
421
|
+
3. Aynı anda 10 yanlış kod → hepsi `ok: false`, `attempts` ≤ 5.
|
|
422
|
+
4. Sayaç 5'e varınca doğru kod da `ok: false`. Yeni kod istenirse ileti gitmez.
|
|
423
|
+
5. Pencere dolunca yeni kod gelir. Aynı anda 5 doğru kod → tam bir `ok: true`.
|
|
424
|
+
6. Giriş ucu yeni parolayla 200, eskisiyle 401.
|
|
425
|
+
|
|
346
426
|
## Tuzaklar
|
|
347
427
|
|
|
348
428
|
- Expression'daki değişken adı, bağlı middleware'lerden birinin kurduğu adla **birebir**
|
|
@@ -368,9 +448,9 @@ Middleware bağlamadan önce `users` tablosunda satır olduğundan emin ol — `
|
|
|
368
448
|
kapı bağlanınca her istek 401 döner ve bu ancak sayfa boş açılınca fark edilir (09-02 ve 09-04
|
|
369
449
|
koşularında ikisinde de yaşandı).
|
|
370
450
|
|
|
371
|
-
İlk kullanıcıyı
|
|
372
|
-
`platform_guide('getting-started')` §"İlk kullanıcıyı kim açar?".
|
|
373
|
-
|
|
451
|
+
İlk kullanıcıyı `row_create` ile `users`'a yazın; platform parolayı özetler:
|
|
452
|
+
`platform_guide('getting-started')` §"İlk kullanıcıyı kim açar?". Bakım uçları (`Sync*`) ürün yüzeyinin parçası
|
|
453
|
+
değildir ve gateway onları araç olarak yayınlamaz.
|
|
374
454
|
|
|
375
455
|
## Korumalı bir ucu TEST ederken: token ÇEREZDEN okunur
|
|
376
456
|
|
|
@@ -106,9 +106,19 @@ Bir ekran yukarıdan aşağıya şu sırayla kurulur: **başlık → bölümler
|
|
|
106
106
|
|
|
107
107
|
## Boşluk
|
|
108
108
|
|
|
109
|
-
Aralığın tek kaynağı ebeveynin `gap`'idir. Runtime'ın varsayılanları katmanlarda birikir
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
Aralığın tek kaynağı ebeveynin `gap`'idir. Runtime'ın varsayılanları katmanlarda birikir. İki kat iç içe zeminsiz
|
|
110
|
+
kap, içeriği 28 dp kaydırır. Varsayılanlar (boşluk tipi yazılmamışsa; açık yazılan değer her satırda uygulanır):
|
|
111
|
+
|
|
112
|
+
| Düğüm | Dış boşluk | İç boşluk | Not |
|
|
113
|
+
|---|---|---|---|
|
|
114
|
+
| Margin tipi yazılmamış düğüm | 4 dp | — | runtime `RUNTIME_NORMAL_MARGIN` |
|
|
115
|
+
| Bırakılabilir düğüm (çocuk alan kap) | 4 dp | 10 dp | `RUNTIME_NORMAL_PADDING`; `isWrapper` dış boşluğu sıfırlar, iç boşluğu sıfırlamaz |
|
|
116
|
+
| `safe-area`, `scroll-view`, `app-bar`, `tooltip`, `view` | 0 | 0 | ekran kenarına yaslanan kaplar (`FLUSH_SPACING_TYPES`) |
|
|
117
|
+
| `auth-layout`, `modal`, `drawer` | 0 | 0 | katmanlar; `modal`/`drawer` paneli temanın `spacing.md` iç boşluğunu kendisi verir |
|
|
118
|
+
|
|
119
|
+
Çocuklar arası: `container` ve `scroll-view` `gap` varsayılanı `medium` = 16 dp (ölçek `small` 8 · `medium` 16 ·
|
|
120
|
+
`large` 24). Mobil `repeater` `gap` okumaz: satır arası `verticalSpacing`, sütun arası `horizontalSpacing`
|
|
121
|
+
(şema varsayılanı 12 / 12). `carousel` `gap` 0'dır (px).
|
|
112
122
|
|
|
113
123
|
- **Gateway yeni düğüme `marginType: 'none'` yazar** (`page_create`, `page_schema_patch`,
|
|
114
124
|
`page_schema_validate`) ve bunu `gatewayNormalized`'da söyler.
|
|
@@ -130,6 +140,10 @@ yazılmamış her düğüm 4 dp, bırakılabilir düğüm ayrıca 10 dp padding
|
|
|
130
140
|
sağ kenar hizadan kopar (MOB-020).
|
|
131
141
|
- **Yönü yazılmamış `container` ve `form` satırdır.** Alanlar yan yana sıkışır; `direction: 'column'` yazın.
|
|
132
142
|
`safe-area` ve `scroll-view` sütun doğar.
|
|
143
|
+
- **Satırda üç ya da daha çok çocuk dar sütunlara sıkışır** (MOB-043): `direction: 'row'` ve sarmasız kapta üç ya da
|
|
144
|
+
daha çok çocuk yan yana, en az ikisi `fill` ise önizlemede de telefonda da daralırlar (2 alan + düğme: her biri ~106 dp).
|
|
145
|
+
Alt alta için `direction: 'column'`. İki `fill` çocuk eşit paylaşır ([İptal | Kaydet], Ad | Soyad) ve bir `fill` alan
|
|
146
|
+
+ düğme (arama satırı) bilinçli satırdır, uyarı almaz.
|
|
133
147
|
- **Ortalanmış ekran** (giriş, boş durum) için sütuna `horizontalAlign: 'center'` verin. Bu kapsamda kenar
|
|
134
148
|
eşitliği aranmaz.
|
|
135
149
|
|
|
@@ -162,6 +176,24 @@ Gerekçesiz ya da iç içe dikey kaydırıcı jestleri çakıştırır (MOB-016)
|
|
|
162
176
|
boyunca eşit böler ve kısa içerik üst üste biner. Yapışkan başlık ve ekrana sabit içerik için
|
|
163
177
|
`platform_guide('mobile')` §Ekrana sabit içerik.
|
|
164
178
|
|
|
179
|
+
## Klavye
|
|
180
|
+
|
|
181
|
+
Klavye açılınca yazılan alan görünür kalır; tasarıma iş düşmez. Kabuk 0.12.0 ve sonrası:
|
|
182
|
+
|
|
183
|
+
- **Kaydırılan sayfa** kendi payını açar ve odaktaki satırı klavyenin üstüne kaydırır.
|
|
184
|
+
- **Sabit boylu sayfa** (kaydırma bir `scroll-view`'da) klavye kadar kısalır.
|
|
185
|
+
- **Alta çapalı katman** (`fab`, `floating-container` `bottom-*`) klavyenin tepesine kalkar; orta/üst çapalı katman
|
|
186
|
+
yerinde kalır.
|
|
187
|
+
- **Sekme çubuğu** klavye açıkken görünür kalır. Gizlemek için çubuğa `"hideOnKeyboard": true`
|
|
188
|
+
(`platform_guide('mobile')` §Gezinme).
|
|
189
|
+
- **Kullanıcının koyduğu `scroll-view`** (0.12.0'dan SONRAKİ kabuk; 0.12.0'da yok): klavye açıkken içindeki düğmeye ilk dokunuş düğmeye gider (önce klavyeyi
|
|
190
|
+
kapatmakla harcanmaz). Kaydırıcı kendi klavye payını açmaz; sabit boylu sayfa onu zaten kısaltır. Bilinen sınır:
|
|
191
|
+
kısalan `scroll-view`'un altında kalan alan odaklanınca kendiliğinden görünür alana gelmez, kullanıcı kaydırır.
|
|
192
|
+
Gönder düğmesini formun üstüne ya da alta çapalı katmana koyun.
|
|
193
|
+
|
|
194
|
+
0.12.0'dan eski kabukta sayfa payı yoktur: alt yarıdaki alan klavyenin altında kalır. Geçici çözüm, formu ve gönder
|
|
195
|
+
düğmesini ekranın üst yarısına almaktır. Klavye açıkken yazılan hâli önizlemede sınamak: `set` + `blur: false`.
|
|
196
|
+
|
|
165
197
|
## Doğrulama
|
|
166
198
|
|
|
167
199
|
Bir ekranı bitirince iki çağrı yapın:
|
|
@@ -185,6 +185,36 @@ yazılmayan soru boş kalır).
|
|
|
185
185
|
Cevap uygulamanın içeriğinden çıkar (sayfalar, akışlar, reklam ayarı: `settings.ads.enabled` → `advertising`);
|
|
186
186
|
emin olunmayan soru kullanıcıya sorulur. Google Play'in IARC anketi konsoldadır (insan).
|
|
187
187
|
|
|
188
|
+
## Kabuk sürümleri
|
|
189
|
+
|
|
190
|
+
Telefondaki uygulama, derlemenin aldığı **kabuk paketinin** taşıdığı özellikleri çalıştırır. Yayındaki kabuğun sürümünü
|
|
191
|
+
ve her yeteneği `mobile_store_get`'in `shell` alanı söyler (`shell.capabilities.<ad>: { carried, since }`; kaynak
|
|
192
|
+
`GET mobile-store/shell`, tarama yapmaz). Bir geçici
|
|
193
|
+
çözümü kaldırmadan önce oradan okuyun; rehberin anlattığı davranış en yeni kabuğundur. Tablonun kaynağı backend'dir
|
|
194
|
+
(`MobileBuildOptions.ShellCapabilities`); bu tabloyla eşliğini bir test denetler.
|
|
195
|
+
|
|
196
|
+
| Yetenek | En düşük kabuk | Rehberdeki yeri | Eski kabukta |
|
|
197
|
+
|---|---|---|---|
|
|
198
|
+
| `push` | 0.1.0 | §Ön kontrol P-11 | derleme push'suz çıkar (`push_shell_outdated`) |
|
|
199
|
+
| `permissions` | 0.2.0 | §İzinler | sabit izinler bildirilir, kipler uygulanmaz (P-12) |
|
|
200
|
+
| `launchPermissions` | 0.3.0 | §İzinler | açılışta izin sorulmaz (P-12) |
|
|
201
|
+
| `ads` | 0.4.0 | §Reklamlar | derleme reklamsız çıkar (P-13) |
|
|
202
|
+
| `scan` | 0.5.0 | `platform_guide('mobile-steps')` `scan-code` | adım `error` dalıyla biter (P-14) |
|
|
203
|
+
| `appLock` | 0.6.0 | §Uygulama kilidi | uygulama kilitsiz açılır (P-12) |
|
|
204
|
+
| `offlineSeed` | 0.7.0 | `platform_guide('page')` §Kural 1g-4 | derlemede ekran kopyası yok: ilk açılış ağ ister |
|
|
205
|
+
| `nonProductionEnvironments` | 0.7.0 | §Test derlemesi | production dışı ortam açılmaz; ön kontrol P-32 `fail`, derleme başlamaz |
|
|
206
|
+
| `purchases` | 0.9.0 | §Satın almalar | satın alma adımları `unsupported` döner |
|
|
207
|
+
| `purchasesSilentRestore` | 0.10.0 | §Satın almalar | açılışta sessiz geri yükleme yapılmaz |
|
|
208
|
+
| `optionalStoreUrls` | 0.12.0 | §Ön kontrol P-33 | boş gizlilik ya da destek adresiyle açılmaz; ön kontrol P-33 `fail` |
|
|
209
|
+
| `backStep` | 0.12.0 | `platform_guide('mobile')` `back` adımı | `back` adımı yok: önceki sayfaya `redirect` |
|
|
210
|
+
| `tabRedirectReturns` | 0.12.0 | `platform_guide('mobile')` sekmeye `redirect` | sekme sayfasına `redirect` yığına kopya iter |
|
|
211
|
+
| `keyboardInsets` | 0.12.0 | `platform_guide('mobile')` | klavye alttaki alanı örter: gönder düğmesini ekranın üst yarısına alın |
|
|
212
|
+
| `flowDrain` | 0.12.0 | `platform_guide('mobile-steps')` §Sayfa kapanınca | sayfa kapanınca akış yarıda kalır: isteği göndermeden ekranı değiştirmeyin |
|
|
213
|
+
| `sharedAppState` | 0.12.0 | `platform_guide('mobile')` §Kalıcı state | her sayfa kendi `state.app` kopyasını alır: sayfalar arası değeri saklama türüyle taşıyın |
|
|
214
|
+
| `offlineQueueEvent` | 0.12.0 | `platform_guide('page')` §Kural 1g-4 | `offlineQueueChange` koşmaz: kuyruğu bir zamanlayıcıyla yeniden okuyun |
|
|
215
|
+
|
|
216
|
+
Lokal yığının derleme varsayılanı daha eski olabilir; telefondaki sürümü tahmin etmeyin, `shell.version`'dan okuyun.
|
|
217
|
+
|
|
188
218
|
## Ön kontrol
|
|
189
219
|
|
|
190
220
|
`mobile_build_start { dryRun: true }` yalnız `ok` OLMAYAN satırları döner (`okCount` gerisini sayar). Tablo
|
|
@@ -194,7 +224,7 @@ emin olunmayan soru kullanıcıya sorulur. Google Play'in IARC anketi konsoldad
|
|
|
194
224
|
|---|---|---|
|
|
195
225
|
| P-01 | `settings_incomplete`; Android `internal`'da `release_notes_too_long` (bir dilde `releaseNotes` > 500 — Play sınırı; ayar 4000'e izin verir çünkü App Store'a da gider; ikisi birlikteyse tek satırda iki cümle). Mesaj alanları istek dilinde adlandırır ("başlık (English)", "gizlilik politikası adresi"), ayar yolunu değil — yol `mobile_store_get`'ten | `mobile_store_update` |
|
|
196
226
|
| P-02 | `assets_missing` | `mobile_asset_create` |
|
|
197
|
-
| P-03 | `secrets_missing`, `secrets_unreadable`, `signing_expired` (`warn signing_expiring`) | Studio › Yayın › Hesaplar |
|
|
227
|
+
| P-03 | `secrets_missing`, `secrets_unreadable`, `signing_expired` (`warn signing_expiring`); iOS'ta sertifika Apple hesabının dağıtım sertifikaları arasında yoksa `signing_certificate_revoked` (iptal edilmiş ya da silinmiş; platformun açtığı sertifika Apple kimliğiyle, yüklenen sertifika baştaki sıfırları atılmış seri numarasıyla eşlenir), App Store Connect'e sorulamazsa `warn signing_revocation_unverified` (derlemeyi engellemez) | Studio › Yayın › Hesaplar |
|
|
198
228
|
| P-04 | `apple_account_missing`, `asc_bundle_id_missing`, `asc_app_missing`, `asc_unauthorized`, `asc_forbidden`, `asc_unreachable` | `asc_bundle_id_missing`: `mobile_store_update { registerAppleBundleId: true }`. `asc_app_missing`: App Store Connect'te uygulama kaydı (API açamaz, insan açar). Diğerleri: Studio hesabı |
|
|
199
229
|
| P-05 | `google_account_missing`, `play_app_missing`, `play_unauthorized`, `play_forbidden`, `play_unreachable` | Play Console'da uygulama + servis hesabı yetkisi |
|
|
200
230
|
| P-06 | `production_missing`, `production_page_missing` (yalın `build`'de `warn`, başlatmayı engellemez); production dışı `environment` ile `fail environment_page_missing` | production ortamı ve dağıtım — insan onaylı, ajan yapmaz; başka ortamda açılış sayfası o ortamda |
|
|
@@ -214,6 +244,8 @@ emin olunmayan soru kullanıcıya sorulur. Google Play'in IARC anketi konsoldad
|
|
|
214
244
|
| P-20 | kendi kaydı olan uygulamada hesap silme yolu görülmezse `manual account_deletion_unverified` (5.1.1(v)) | ekran + kullanıcı doğrular |
|
|
215
245
|
| P-21 | `age_rating_missing` (yaş derecelendirmesi yanıtsız) | `settings.apple.advisory` |
|
|
216
246
|
| P-22 | yayın hedefinde `release_source_missing`: kaynak derleme yok (`testflight` → `app-store-review` → `app-store-release`, `internal` → `play-promote`) | önce kaynak hedefi derle |
|
|
247
|
+
| P-32 | production dışı `environment` ile derlemede kabuk o ortamı açamıyorsa `shell_environment_unsupported` (`nonProductionEnvironments`, 0.7.0); açabiliyorsa `ok shell_environment_ready`. Ön kontrol atlanırsa `mobile_build_start` aynı koşulda 422 `MobileBuildShellLacksCapabilityException` döner | production'dan derleyin ya da daha yeni kabuk (destek) |
|
|
248
|
+
| P-33 | kabuk `optionalStoreUrls` taşımıyorsa (0.12.0'dan eski) ve boş mağaza adresi varsa `store_urls_shell_required` — kabuk boş adresle açılmaz; ileti boş alanları sayar: destek adresi her derlemede, gizlilik adresi mağaza dışı derlemede (mağaza yüklemesinde onu P-01 / P-19 sorar). 0.12.0+ kabukta satır yok | `settings.urls.support` / `settings.urls.privacyPolicy` ya da daha yeni kabuk |
|
|
217
249
|
|
|
218
250
|
`manual` satır başlatmayı engellemez ama kullanıcıya söylenir; `fail` engeller. Satırın `message` ve `fixHint`'i
|
|
219
251
|
backend'den, istek dilinde gelir.
|
|
@@ -36,7 +36,8 @@ Motorun kendi adımları (`setter`, `condition`, `microservice`, `code`, `functi
|
|
|
36
36
|
|
|
37
37
|
## Sayfa kapanınca
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
Kabuk 0.12.0+ (`flowDrain`; daha eski kabukta akış sayfayla kesilir — isteği göndermeden ekranı değiştirmeyin; yayındaki
|
|
40
|
+
sürüm `mobile_store_get` `shell`). Sayfa söküldüğünde (geri, yığının köke inmesi, çıkış — sekme değişimi sayfayı sökmez) açık akış yarıda kalmaz (kök ADR 0030): yeni koşum başlamaz, açık
|
|
40
41
|
koşumlar telefona yazan adımlarla sonuna kadar sürer. 30 sn'de bitmeyen koşum kapatılır.
|
|
41
42
|
|
|
42
43
|
| Adımlar | Sayfa kapalıyken |
|
|
@@ -48,7 +49,7 @@ koşumlar telefona yazan adımlarla sonuna kadar sürer. 30 sn'de bitmeyen koşu
|
|
|
48
49
|
|
|
49
50
|
Atlanan her adım telefonun defterine `page-unmounted` kaydı düşer (`mobile_error_list`). `state.*` yazımı `storage` ya da
|
|
50
51
|
`code` adımıyla yapılır — `setter` atlanır. "İsteği göndermeden önce ekranı değiştir", "önce yaz sonra çık" gibi geçici
|
|
51
|
-
çözümler gerekmez. Studio mobil önizlemesi ve `/view` de sayfa geçişinde boşaltır; "Dene"nin yeniden başlatması ve
|
|
52
|
+
çözümler 0.12.0+ kabukta gerekmez. Studio mobil önizlemesi ve `/view` de sayfa geçişinde boşaltır; "Dene"nin yeniden başlatması ve
|
|
52
53
|
önizlemenin kapanması akışı keser. Web (Svelte) uygulaması akışı sayfayla birlikte keser.
|
|
53
54
|
|
|
54
55
|
## Önizlemede çekim hedefleri
|
|
@@ -71,6 +72,15 @@ rollerini sayar):
|
|
|
71
72
|
|
|
72
73
|
Pencere açıkken altındaki sayfa öğesine `click` uygulanmaz (SCR-023).
|
|
73
74
|
|
|
75
|
+
**`click` gerçek noktaya basar** (SCR-024). Hedef sırayla seçilir: kök basılabilirse kendisi, sonra `<id>__press`, sonra
|
|
76
|
+
içteki ilk düğme. Basış hedefin görünen bir noktasına gerçek fare olayıyla yapılır: önce orta; ortada hedefin içindeki
|
|
77
|
+
başka basılabilir öğe (kabın düğmesi, çipin "kaldır"ı) varsa kutunun başka bir noktası. Görünmeyen hedef önce görünüme
|
|
78
|
+
kaydırılır. Hiçbir noktası hedefin değilse eylem uygulanmaz: `applied: false`, `coveredBy: '<örten elementId>'`.
|
|
79
|
+
Telefonda da dokunuşu o öğe alır. Örten şema düğümü değilse (kabuğun bildirim katmanı, açık kalmış toast) `coveredBy`
|
|
80
|
+
rolünü ve metnini söyler: `div[role=alert] "…"`. Örten bir `stack` katmanı ya da yerleşim kabıysa boş alanı dokunuşu
|
|
81
|
+
geçirmeli; bilerek üstteyse onu hedefleyin. Kutusu boş hedefte not "görünmüyor" der. `flowRuns` biten akışı sayar:
|
|
82
|
+
`alert`/`confirm` adımında bekleyen akış, pencereyi yanıtlayan eylemin raporunda sayılır.
|
|
83
|
+
|
|
74
84
|
## Bildirim tarifi
|
|
75
85
|
|
|
76
86
|
Bildirim adımları paletten yalnız mobil uygulamada sunulur. Tarif — S04 "her gün hatırlat": önce
|