@appaflytech/zeyrek-ai-mcp 0.0.35 → 0.0.37

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.
Files changed (210) hide show
  1. package/.env.example +15 -0
  2. package/README.md +5 -5
  3. package/dist/config.js +37 -0
  4. package/dist/index.js +2 -0
  5. package/dist/knowledge/access.js +802 -0
  6. package/dist/knowledge/backendConstraints.js +18 -1
  7. package/dist/knowledge/case.js +41 -0
  8. package/dist/knowledge/componentCatalog.js +655 -424
  9. package/dist/knowledge/componentEvents.js +140 -23
  10. package/dist/knowledge/connectDataBind.js +843 -0
  11. package/dist/knowledge/connectDataDefinition.js +344 -0
  12. package/dist/knowledge/connectDataExecute.js +93 -0
  13. package/dist/knowledge/connectDataRules.js +128 -0
  14. package/dist/knowledge/decision.js +121 -0
  15. package/dist/knowledge/expressionPrelude.js +59 -0
  16. package/dist/knowledge/layoutSummary.js +140 -2
  17. package/dist/knowledge/microserviceParameters.js +58 -0
  18. package/dist/knowledge/mining.js +94 -0
  19. package/dist/knowledge/mobileComponentCatalog.js +864 -0
  20. package/dist/knowledge/mobileComponentEvents.js +239 -0
  21. package/dist/knowledge/mobileStore.js +321 -0
  22. package/dist/knowledge/normalize.js +235 -84
  23. package/dist/knowledge/outlines.js +36 -0
  24. package/dist/knowledge/platform/lint.js +385 -0
  25. package/dist/knowledge/platform/mobile.js +142 -0
  26. package/dist/knowledge/platform/mobileLayoutLint.js +324 -0
  27. package/dist/knowledge/platform/mobileRules.js +231 -0
  28. package/dist/knowledge/platform/mobileSpacing.js +82 -0
  29. package/dist/knowledge/platform/mobileTabs.js +108 -0
  30. package/dist/knowledge/platform/nativeBoxModel.js +44 -0
  31. package/dist/knowledge/platform/profile.js +25 -0
  32. package/dist/knowledge/platform/resolve.js +122 -0
  33. package/dist/knowledge/platform/web.js +39 -0
  34. package/dist/knowledge/policy.js +18 -4
  35. package/dist/knowledge/process.js +510 -0
  36. package/dist/knowledge/reservedTables.js +79 -0
  37. package/dist/knowledge/rowFilter.js +43 -0
  38. package/dist/knowledge/rules.js +423 -37
  39. package/dist/knowledge/schemaGuard.js +11 -0
  40. package/dist/knowledge/styleResolve.js +53 -19
  41. package/dist/knowledge/styleResolveNative.js +527 -0
  42. package/dist/knowledge/themeResolve.js +4 -4
  43. package/dist/knowledge/themeSchema.js +1 -2
  44. package/dist/observability/trace.js +6 -3
  45. package/dist/platform/argumentAliases.js +128 -0
  46. package/dist/platform/bodyFile.js +103 -0
  47. package/dist/platform/capture/actions.js +398 -0
  48. package/dist/platform/capture/browser.js +1 -0
  49. package/dist/platform/capture/capture.js +683 -0
  50. package/dist/platform/capture/console-log.js +122 -0
  51. package/dist/platform/capture/index.js +11 -0
  52. package/dist/platform/capture/readiness.js +103 -0
  53. package/dist/platform/capture/summary.js +77 -0
  54. package/dist/platform/capture/target.js +5 -0
  55. package/dist/platform/capture/targets/studio-mobile.js +15 -0
  56. package/dist/platform/capture/targets/studio-web.js +5 -0
  57. package/dist/platform/diagnostics.js +10 -4
  58. package/dist/platform/domSummary.js +1 -1
  59. package/dist/platform/errors.js +163 -10
  60. package/dist/platform/http.js +29 -8
  61. package/dist/platform/impersonation.js +45 -0
  62. package/dist/platform/jsonArray.js +24 -0
  63. package/dist/platform/responses.js +39 -5
  64. package/dist/platform/schemaReadMemory.js +60 -0
  65. package/dist/platform/secrets.js +19 -1
  66. package/dist/platform/strictArguments.js +68 -12
  67. package/dist/platform/tokenVault.js +187 -4
  68. package/dist/resources/examples/bpm-gorev-kontrol.json +294 -0
  69. package/dist/resources/examples/bpm-iso-dokuman.json +228 -0
  70. package/dist/resources/examples/bpm-satinalma.json +376 -0
  71. package/dist/resources/examples/connect-data-convert-verify.json +107 -0
  72. package/dist/resources/examples/connect-data-dropdown-roles.json +66 -0
  73. package/dist/resources/examples/connect-data-edit-m2m.json +73 -0
  74. package/dist/resources/examples/connect-data-query-params.json +50 -0
  75. package/dist/resources/examples/connect-data-table-entity.json +147 -0
  76. package/dist/resources/examples/connect-data-workflow-microservice.json +65 -0
  77. package/dist/resources/examples/middleware-authenticate.json +1 -1
  78. package/dist/resources/examples/mobile-app-shell-drawer.json +244 -0
  79. package/dist/resources/examples/mobile-app-shell-stack.json +157 -0
  80. package/dist/resources/examples/mobile-app-shell-tab.json +208 -0
  81. package/dist/resources/examples/mobile-form-screen.json +365 -0
  82. package/dist/resources/examples/mobile-list-detail-form.json +703 -0
  83. package/dist/resources/examples/page-detail-profile.json +2 -1
  84. package/dist/resources/examples/page-dynamic-form.json +3 -2
  85. package/dist/resources/examples/page-flex-sections.json +1 -1
  86. package/dist/resources/examples/page-html-embed.json +1 -1
  87. package/dist/resources/examples/workflow-basic.json +8 -2
  88. package/dist/resources/guides/access.md +810 -0
  89. package/dist/resources/guides/bpm.md +691 -0
  90. package/dist/resources/guides/case.md +153 -0
  91. package/dist/resources/guides/component.md +93 -6
  92. package/dist/resources/guides/decision.md +185 -0
  93. package/dist/resources/guides/entity.md +50 -8
  94. package/dist/resources/guides/expressions.md +14 -5
  95. package/dist/resources/guides/functions.md +1 -1
  96. package/dist/resources/guides/getting-started.md +38 -1
  97. package/dist/resources/guides/layout.md +11 -7
  98. package/dist/resources/guides/merge.md +37 -3
  99. package/dist/resources/guides/microservice.md +87 -2
  100. package/dist/resources/guides/middleware.md +68 -4
  101. package/dist/resources/guides/mobile-layout.md +193 -0
  102. package/dist/resources/guides/mobile-publish.md +124 -0
  103. package/dist/resources/guides/mobile.md +549 -0
  104. package/dist/resources/guides/page.md +463 -83
  105. package/dist/resources/guides/theme.md +8 -3
  106. package/dist/resources/guides/translation.md +1 -1
  107. package/dist/resources/guides/workflow.md +58 -1
  108. package/dist/resources/read.js +41 -0
  109. package/dist/resources/rules.js +11 -5
  110. package/dist/resources/schema-fingerprint.json +7 -5
  111. package/dist/resources/schemas/AccessExplanationDto.json +144 -0
  112. package/dist/resources/schemas/AccessMatrixDto.json +170 -0
  113. package/dist/resources/schemas/AccessPermissionDto.json +57 -0
  114. package/dist/resources/schemas/AccessPolicyDto.json +223 -0
  115. package/dist/resources/schemas/AccessResourceDto.json +86 -0
  116. package/dist/resources/schemas/AppSessionDto.json +53 -0
  117. package/dist/resources/schemas/ConnectDataDescribeDto.json +344 -0
  118. package/dist/resources/schemas/ConnectDataRequestModel.json +202 -0
  119. package/dist/resources/schemas/ConnectDataResultDto.json +421 -0
  120. package/dist/resources/schemas/ConnectorCredentialDto.json +70 -0
  121. package/dist/resources/schemas/ConvertConnectDataToMicroserviceCommand.json +37 -0
  122. package/dist/resources/schemas/CreateAccessPolicyCommand.json +209 -0
  123. package/dist/resources/schemas/CreateDecisionCommand.json +221 -0
  124. package/dist/resources/schemas/CreateEntityCommand.json +118 -0
  125. package/dist/resources/schemas/CreateMicroserviceCommand.json +13 -1
  126. package/dist/resources/schemas/CreateProcessDefinitionCommand.json +1337 -0
  127. package/dist/resources/schemas/CreateQueryCommand.json +4 -0
  128. package/dist/resources/schemas/CreateWorkflowCommand.json +146 -1
  129. package/dist/resources/schemas/DataJobDto.json +6 -1
  130. package/dist/resources/schemas/DecisionDto.json +302 -0
  131. package/dist/resources/schemas/DetachConnectDataMicroserviceCommand.json +28 -0
  132. package/dist/resources/schemas/ExplainAccessQuery.json +33 -0
  133. package/dist/resources/schemas/GenerateProcessDocumentCommand.json +116 -0
  134. package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +25 -1
  135. package/dist/resources/schemas/MicroserviceDto.json +15 -3
  136. package/dist/resources/schemas/OrganizationProjectDto.json +335 -0
  137. package/dist/resources/schemas/OrganizationSchemaDto.json +196 -0
  138. package/dist/resources/schemas/OrganizationSchemaVersionDto.json +207 -0
  139. package/dist/resources/schemas/PatchProcessDefinitionCommand.json +76 -0
  140. package/dist/resources/schemas/PreviewMicroserviceCommand.json +10 -1
  141. package/dist/resources/schemas/PreviewWorkflowCommand.json +25 -1
  142. package/dist/resources/schemas/ProcessDefinitionDto.json +1480 -0
  143. package/dist/resources/schemas/ProcessSimulationDto.json +96 -0
  144. package/dist/resources/schemas/SaveConnectorCredentialCommand.json +90 -0
  145. package/dist/resources/schemas/SaveProcessEventSourceCommand.json +61 -0
  146. package/dist/resources/schemas/SaveProcessMailSourceCommand.json +89 -0
  147. package/dist/resources/schemas/SignProcessTaskCommand.json +73 -0
  148. package/dist/resources/schemas/SimulateProcessDefinitionQuery.json +92 -0
  149. package/dist/resources/schemas/StartMobileBuildCommand.json +54 -0
  150. package/dist/resources/schemas/StepDto.json +25 -1
  151. package/dist/resources/schemas/UpdateAccessPolicyCommand.json +209 -0
  152. package/dist/resources/schemas/UpdateDecisionCommand.json +216 -0
  153. package/dist/resources/schemas/UpdateEntityCommand.json +118 -0
  154. package/dist/resources/schemas/UpdateMicroserviceCommand.json +13 -1
  155. package/dist/resources/schemas/UpdateProcessDefinitionCommand.json +1357 -0
  156. package/dist/resources/schemas/UpdateQueryCommand.json +4 -0
  157. package/dist/resources/schemas/UpdateViewCommand.json +0 -2
  158. package/dist/resources/schemas/UpdateWorkflowCommand.json +146 -1
  159. package/dist/resources/schemas/UpsertMobileStoreSettingsCommand.json +274 -0
  160. package/dist/resources/schemas/ValidateProcessDefinitionQuery.json +1339 -0
  161. package/dist/server.js +31 -1
  162. package/dist/tools/access.js +369 -2
  163. package/dist/tools/accessOperations.js +135 -0
  164. package/dist/tools/accessPolicies.js +170 -0
  165. package/dist/tools/accessRuntime.js +203 -0
  166. package/dist/tools/applications.js +60 -3
  167. package/dist/tools/bpm.js +343 -0
  168. package/dist/tools/bpmGovernance.js +266 -0
  169. package/dist/tools/bpmOperations.js +230 -0
  170. package/dist/tools/bpmReporting.js +154 -0
  171. package/dist/tools/bpmRouting.js +150 -0
  172. package/dist/tools/bpmRuntime.js +396 -0
  173. package/dist/tools/case.js +178 -0
  174. package/dist/tools/caseRuntime.js +89 -0
  175. package/dist/tools/common.js +10 -1
  176. package/dist/tools/components.js +118 -14
  177. package/dist/tools/connectData.js +450 -0
  178. package/dist/tools/connectors.js +193 -0
  179. package/dist/tools/datasources.js +3 -3
  180. package/dist/tools/decisions.js +206 -0
  181. package/dist/tools/dependencies.js +1 -1
  182. package/dist/tools/deployments.js +38 -3
  183. package/dist/tools/designSchemaPatch.js +34 -9
  184. package/dist/tools/dtos.js +36 -5
  185. package/dist/tools/entities.js +79 -12
  186. package/dist/tools/externalComponents.js +26 -5
  187. package/dist/tools/flows.js +119 -17
  188. package/dist/tools/folders.js +1 -1
  189. package/dist/tools/imports.js +5 -2
  190. package/dist/tools/layouts.js +50 -17
  191. package/dist/tools/merges.js +2 -2
  192. package/dist/tools/microservices.js +77 -25
  193. package/dist/tools/mobile.js +280 -0
  194. package/dist/tools/mobileStore.js +556 -0
  195. package/dist/tools/modules.js +48 -0
  196. package/dist/tools/organizationStructure.js +137 -0
  197. package/dist/tools/pages.js +465 -98
  198. package/dist/tools/platformInfo.js +28 -8
  199. package/dist/tools/profileWrite.js +92 -0
  200. package/dist/tools/projects.js +71 -9
  201. package/dist/tools/rows.js +69 -13
  202. package/dist/tools/schemaOps.js +61 -1
  203. package/dist/tools/screenWrite.js +36 -7
  204. package/dist/tools/screens.js +95 -5
  205. package/dist/tools/templates.js +26 -1
  206. package/dist/tools/translations.js +1 -1
  207. package/dist/tools/variables.js +11 -5
  208. package/dist/tools/workflows.js +10 -5
  209. package/package.json +2 -2
  210. package/dist/platform/screenshot.js +0 -1162
@@ -1,8 +1,9 @@
1
1
  import { logStep } from '../observability/trace.js';
2
2
  import { PlatformApiError } from '../platform/errors.js';
3
3
  import { ensureWorkflowErrorPath } from './errorPath.js';
4
- import { TYPED_STYLE_COMPONENTS } from './styleResolve.js';
5
- import { FUNCTIONS_NAMESPACE } from './functionGuards.js';
4
+ import { DOCUMENT_STYLE_TYPES, TYPED_STYLE_COMPONENTS } from './styleResolve.js';
5
+ import { STATE_STORAGE_KINDS, STEP_ALIASES } from './platform/profile.js';
6
+ import { inlineStyleFields } from './componentCatalog.js';
6
7
  const SORT_DEFAULT = { name: 'sortField', type: 'sortType', source: 'query-string' };
7
8
  const LIMIT_DEFAULT = { name: 'limit', offset: 'offset', source: 'query-string' };
8
9
  function asArray(value) {
@@ -270,12 +271,11 @@ export function normalizeWorkflowDefinition(definition) {
270
271
  */
271
272
  /**
272
273
  * Adım tipi sözlüğü backend'in, KOŞAN tipler barındırıcının: motor yedi tip register eder, web
273
- * `Runtime.svelte` altı, mobil `ScreenRuntime.tsx` beş tip ekler ve aynı sayfa kaydı ikisinde de
274
+ * `Runtime.svelte` altı, mobil `runtime-tasks.ts` yedi tip ekler ve aynı sayfa kaydı ikisinde de
274
275
  * koşar. Register edilmemiş bir tipe gelen akış sessizce ölür (`flow-engine.ts` `process()`
275
276
  * callback bulamazsa next/completed/stopped hiç çağrılmaz), üstelik `run()` promise'i
276
277
  * çözülmediği için `__ngAppsFlowActivity.running` sıfıra inmez. İki hâl tamamen türetilebilir
277
- * olduğu için lint değil NORMALİZASYON — kalanı (`confirm`/`download` mobilde yok) yalnız
278
- * uygulama tarafında kapanabilir, `FLOW-023` onları uyarır.
278
+ * olduğu için lint değil NORMALİZASYON; `FLOW-023` kalan hâlleri uyarır.
279
279
  */
280
280
  function normalizeFlowHostGaps(steps, list) {
281
281
  const fixes = [];
@@ -289,12 +289,15 @@ function normalizeFlowHostGaps(steps, list) {
289
289
  .map(step => String(step.stepId ?? ''))
290
290
  .filter(id => id.length > 0));
291
291
  for (const step of list) {
292
- // navigate ile redirect'in argümanı AYNI ({ path, pageId, query, targetBlank }) ama navigate'i
293
- // yalnız mobil register eder; redirect ise iki barındırıcıda da koşar (mobilde aynı navigate
294
- // uygulamasına eşlenir), yani redirect kesin üstün.
295
- if (step.type === 'navigate') {
296
- step.type = 'redirect';
297
- fixes.push(`'${label(step)}': type navigate → redirect yazıldı — argüman şekli aynı ({ path, pageId, query, targetBlank }) ama navigate'i web barındırıcısı register etmez, redirect ikisinde de koşar`);
292
+ // navigate ile redirect'in argümanı AYNI ({ path, pageId, query, targetBlank, presentation })
293
+ // ama navigate'i yalnız mobil register eder; redirect ise iki barındırıcıda da koşar (mobilde
294
+ // aynı navigate uygulamasına eşlenir), yani redirect kesin üstün. `presentation`
295
+ // (push | modal | replace) yalnız mobil barındırıcıda okunur; web yok sayar.
296
+ const alias = STEP_ALIASES[String(step.type ?? '')];
297
+ if (alias) {
298
+ const original = String(step.type);
299
+ step.type = alias;
300
+ fixes.push(`'${label(step)}': type ${original} → ${alias} yazıldı — argüman şekli aynı ({ path, pageId, query, targetBlank, presentation }) ama ${original}'i web barındırıcısı register etmez, ${alias} ikisinde de koşar`);
298
301
  }
299
302
  if (endStepIds.size === 0)
300
303
  continue;
@@ -359,7 +362,6 @@ const REQUIRED_COLLECTIONS = ['events', 'validations', 'stateBindings', 'childre
359
362
  *
360
363
  * - `states`: DesignComponentManagementSchema.ToDomain:47 `States.IsSticky`'yi guard'sız okur;
361
364
  * Appearance'ın satır 24'te varsayılanı var, States'in yok.
362
- * - `blocks` doldurulmaz: konum ebeveynin düzenine bağlıdır, eksikse PAGE-037 ile reddedilir.
363
365
  * - `events` / `validations` / `stateBindings`: yalnız **micro-op yolunda** zorunlu.
364
366
  * `page_create` (POST /pages) bunlarsız 200 döner, `PATCH …/schema` NRE verir; canlı deneyde
365
367
  * üçü birlikte verilince geçti, her biri tek başına verilince patladı (klinik H-005 eki).
@@ -371,9 +373,17 @@ const REQUIRED_COLLECTIONS = ['events', 'validations', 'stateBindings', 'childre
371
373
  // PropValueType (DesignComponentProperty.cs:421) — 'translation' listeden düşünce çeviriye bağlı
372
374
  // prop'un KENDİSİ veri sanılıp static Prop'a sarılıyordu: istek 200 dönüyor, prop bozuk kuruluyordu
373
375
  // (09-04 Y-22). Enum burada eksiksiz tutulur.
374
- export const PROP_VALUE_TYPES = new Set(['static', 'file', 'flow', 'widget', 'element', 'page', 'code', 'translation']);
376
+ export const PROP_VALUE_TYPES = new Set(['static', 'file', 'flow', 'widget', 'element', 'page', 'code', 'translation', 'connectData']);
377
+ // Prop'un TAŞIYICISI da aranır: `type` tek başına yetmez, çünkü liste öğesinin kendi `type` alanı
378
+ // olabilir ve değeri Prop tipi adıyla çakışabilir — datatable kolonunda dosya alanı tam olarak
379
+ // böyledir (`{ name, label, type: 'file', isFilterable }`). Yalnız `type`'a bakan sürüm o öğeyi
380
+ // kurulu bir Prop sanıp sarmadan bırakıyordu ve platform 500 JsonException veriyordu (koşu
381
+ // bulgusu S-2'nin gateway ayağı; Studio ayağı `staticFieldListItems` ile aynı kararı verdi).
375
382
  function isPropShaped(value) {
376
- return Boolean(value) && typeof value === 'object' && !Array.isArray(value) && PROP_VALUE_TYPES.has(String(value.type));
383
+ if (!value || typeof value !== 'object' || Array.isArray(value))
384
+ return false;
385
+ const candidate = value;
386
+ return PROP_VALUE_TYPES.has(String(candidate.type)) && ('data' in candidate || 'ref' in candidate);
377
387
  }
378
388
  function wrapPropDictionaryValues(dictionary) {
379
389
  let wrapped = false;
@@ -622,12 +632,16 @@ export function codePropTemplateErrors(ops) {
622
632
  *
623
633
  * Seçici anahtarlara (`&__body`, `:hover`, `--token`) DOKUNULMAZ; yalnız `a-b` biçimli düz CSS
624
634
  * özellik adları camelCase'e çevrilir ve çevrim `gatewayNormalized` ile bildirilir.
635
+ *
636
+ * TB-129 — `inlineFields` verilirse o ÜST DÜZEY alanların altına hiç girilmez: `table.style.th`
637
+ * gibi dallar satır içi `style` attribute'una basılır ve kebab-case zorunludur
638
+ * (`INLINE_STYLE_FIELDS_BY_TYPE`). Çeviri orada stili öldürüyordu.
625
639
  */
626
640
  const styleKeyPattern = /^[a-z][a-z0-9]*(-[a-z0-9]+)+$/;
627
641
  export function isStylePropName(name) {
628
642
  return name === 'style' || /Style$/.test(name);
629
643
  }
630
- export function normalizeStyleKeys(value, label, fixed) {
644
+ export function normalizeStyleKeys(value, label, fixed, inlineFields) {
631
645
  if (Array.isArray(value)) {
632
646
  for (const entry of value)
633
647
  normalizeStyleKeys(entry, label, fixed);
@@ -636,7 +650,10 @@ export function normalizeStyleKeys(value, label, fixed) {
636
650
  if (!value || typeof value !== 'object')
637
651
  return;
638
652
  const record = value;
653
+ const skipped = new Set(inlineFields ?? []);
639
654
  for (const key of Object.keys(record)) {
655
+ if (skipped.has(key))
656
+ continue;
640
657
  normalizeStyleKeys(record[key], label, fixed);
641
658
  if (!styleKeyPattern.test(key))
642
659
  continue;
@@ -658,10 +675,43 @@ export function styleKeyNote(fixed) {
658
675
  + 'değer kaydedilir ama hiç uygulanmaz ve tek işareti tarayıcı konsoludur (F-06). '
659
676
  + "Seçici anahtarlara ('&__body', ':hover', '--token') dokunulmadı."];
660
677
  }
661
- /** PAGE-009 ile aynı kural: JS tanımlayıcısı (tireler çıkarılınca) ve ayrılmış `Functions` değil. */
662
- function isDerivableComponentName(name) {
663
- const bare = name.replace(/-/g, '');
664
- return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(bare) && bare !== FUNCTIONS_NAMESPACE;
678
+ /**
679
+ * Kabın `states.isDroppable`ı KATALOĞUN kararıdır. Gateway düğümü kurarken bu alanı
680
+ * yazmıyordu (`DEFAULT_STATES` hepsini `false` bırakır) ve Studio kanvası düğümü kap saymıyordu:
681
+ * imleç `scroll-view`un İÇİNDEYKEN bırakma planı sayfa kabına düşüyor, çizgi kabın üst kenarına
682
+ * çiziliyordu (ölçüldü 2026-09-21, `test/mobile app` › `Satislar`). Aynı tipi paletten ekleyen
683
+ * Studio katalog değerini yazdığı için tek şemada iki türlü davranan kap oluyordu.
684
+ *
685
+ * Değer uygulamanın KENDİ sözlüğünden gelir (`listComponents`; mobil uygulamada `type=mobile`
686
+ * satırları), yani kural platformdan bağımsızdır ve kuruluşun kendi komponentlerini de kapsar.
687
+ * Yalnız `true` yazılır: katalog bir tipi kap saymıyorsa düğüme dokunulmaz.
688
+ */
689
+ export function normalizeDroppableStates(nodes, droppable) {
690
+ const fixed = [];
691
+ if (droppable.size === 0)
692
+ return fixed;
693
+ for (const node of asObjects(nodes)) {
694
+ const type = typeof node.type === 'string' ? node.type : '';
695
+ if (droppable.get(type) === true) {
696
+ // `states` normalde `normalizeComponentDefaults`tan geçmiş olur; gelmediyse burada da
697
+ // eksiksiz kurulur — yarım `states` platformda NullReference üretir.
698
+ if (!node.states || typeof node.states !== 'object' || Array.isArray(node.states)) {
699
+ node.states = { ...DEFAULT_STATES };
700
+ }
701
+ const states = node.states;
702
+ if (states.isDroppable !== true) {
703
+ states.isDroppable = true;
704
+ fixed.push(`'${String(node.elementId ?? node.name ?? type)}' (${type})`);
705
+ }
706
+ }
707
+ fixed.push(...normalizeDroppableStates(node.children, droppable));
708
+ }
709
+ return fixed;
710
+ }
711
+ export function droppableStatesNote(fixed) {
712
+ return fixed.length === 0
713
+ ? []
714
+ : [`states.isDroppable katalogdan yazıldı (${fixed.join(' · ')}) — kataloğun kap saydığı düğüm Studio kanvasında da kap olmalı; alanı bilerek kapatmak için resize_component/set_states kullanın`];
665
715
  }
666
716
  /**
667
717
  * Y-15 — adı eksik, null ya da boş/boşluk olan düğüm runtime'ın ifade ön sözünü bozar: `getNodeNames`
@@ -671,7 +721,6 @@ function isDerivableComponentName(name) {
671
721
  * hiç denetlemez (`PageSchemaValidator.cs`); Studio her düğüme `name = elementId` yazar
672
722
  * (`ScreenBuilderDroppable.svelte`). Kural deterministik olduğu için reddetmek yerine dolduruyoruz.
673
723
  * Doldurulan ad da PAGE-009'dan geçmek zorunda: çağıran bunu denetimden ÖNCE koşar. Dolu ada dokunulmaz.
674
- * `normalizeComponentDefaults` aynı boşluğu yalnız geçerli bir elementId için ikinci kez kapatır (güvenlik ağı).
675
724
  */
676
725
  export function fillComponentNames(nodes) {
677
726
  const filled = [];
@@ -696,16 +745,6 @@ export function normalizeComponentDefaults(nodes) {
696
745
  const fixed = [];
697
746
  for (const node of asObjects(nodes)) {
698
747
  const missing = [];
699
- // name'siz düğümü platform kabul eder ama sayfada bir akış ya da `{{ }}` kod prop'u varsa
700
- // FlowEngine runCode "Cannot read properties of undefined (reading 'catch')" ile düşer, akış
701
- // `running:1`'de takılır ve hata adsız düğümü işaret etmez (image ölçüm turu, 2026-10-04).
702
- // Yalnız PAGE-009'un geçerli saydığı elementId'den türetilir: page_create/page_update/layout
703
- // yollarında PAGE-009 denetimi bu normalizasyondan ÖNCE koşar; bozuk bir elementId'den türeyen
704
- // ad denetimi atlatıp "Missing initializer in const declaration" ile sayfayı öldürürdü.
705
- if ((typeof node.name !== 'string' || node.name.trim() === '') && typeof node.elementId === 'string' && isDerivableComponentName(node.elementId)) {
706
- node.name = node.elementId;
707
- missing.push('name');
708
- }
709
748
  if (!node.states || typeof node.states !== 'object') {
710
749
  node.states = { ...DEFAULT_STATES };
711
750
  missing.push('states');
@@ -741,8 +780,10 @@ export function normalizeComponentDefaults(nodes) {
741
780
  if (node.props && typeof node.props === 'object' && !Array.isArray(node.props)) {
742
781
  const props = node.props;
743
782
  const styleKeys = [];
783
+ // Satır içi stil dalları komponent TİPİNE göre atlanır; tip bilinmiyorsa eski davranış.
784
+ const inlineFields = (name) => name === 'style' ? inlineStyleFields(typeof node.type === 'string' ? node.type : undefined) : undefined;
744
785
  for (const name of Object.keys(props).filter(isStylePropName)) {
745
- normalizeStyleKeys(props[name], `${String(node.elementId ?? node.name ?? '?')}.${name}`, styleKeys);
786
+ normalizeStyleKeys(props[name], `${String(node.elementId ?? node.name ?? '?')}.${name}`, styleKeys, inlineFields(name));
746
787
  }
747
788
  if (styleKeys.length > 0)
748
789
  missing.push(`stil anahtarları camelCase'e çevrildi: ${styleKeys.join(' · ')}`);
@@ -906,12 +947,21 @@ function normalizeReplacePropValue(op) {
906
947
  }
907
948
  return notes;
908
949
  }
950
+ // connectData sarmalı burada değiştirilmez: gövde duvarı (CD-005, `connectDataOpIssues`) op'u
951
+ // set_connect_data'ya yönlendirerek rejectedOps'a yazar; burada atmak listeyi tek hataya indirirdi.
952
+ if (propType === 'connectData')
953
+ return notes;
909
954
  // Applier her sarmalın tipini korur, ama page/file/flow/widget/element taşıyıcıları başka alanlara
910
955
  // (FlowId, RedirectPageId, dosya) yazılır ve bu op'la bağları kurulmaz. Remove+add önerilmez:
911
956
  // düğümü yeniden kurmak ona yazan setter iteratee'lerinin element'ini siler (G-04, FLOW-018).
912
957
  throw new PlatformApiError(`${label}: '${propType}' tipli Prop replace_prop ile kurulmaz — bu op static, code ve translation sarmalını taşır. Komponenti silip yeniden EKLEMEYİN: ona yazan setter adımlarının hedefi silinir (FLOW-018). Akış bağı için set_event, diğer tipler için page_update(schema) ile tam şema yazın.`, 400);
913
958
  }
914
- export function normalizeSchemaOps(ops) {
959
+ /**
960
+ * `typesByElementId` — TB-129: `replace_prop` op'u yalnız elementId taşır, stil çevirisinin
961
+ * komponent tipine bakabilmesi için haritayı çağıran (okuduğu şemadan) verir. Verilmezse çeviri
962
+ * tip ayrımı yapmaz; bu, harita okunamadığında yazmayı engellememek için bilinçli düşüştür.
963
+ */
964
+ export function normalizeSchemaOps(ops, typesByElementId) {
915
965
  const components = [];
916
966
  const props = [];
917
967
  const validations = [];
@@ -919,7 +969,10 @@ export function normalizeSchemaOps(ops) {
919
969
  for (const op of asObjects(ops)) {
920
970
  if (op.op === 'replace_prop') {
921
971
  if (isStylePropName(String(op.prop ?? ''))) {
922
- normalizeStyleKeys(op.value, `'${String(op.target ?? '?')}.${String(op.prop)}'`, props);
972
+ const inline = String(op.prop) === 'style'
973
+ ? inlineStyleFields(typesByElementId?.get(String(op.target ?? '')))
974
+ : undefined;
975
+ normalizeStyleKeys(op.value, `'${String(op.target ?? '?')}.${String(op.prop)}'`, props, inline);
923
976
  }
924
977
  props.push(...normalizeReplacePropValue(op));
925
978
  continue;
@@ -997,11 +1050,11 @@ export function pageContainerNode() {
997
1050
  * çekilirlerse sayfa içeriğinin akışına girer ve katman olarak açılmazlar.
998
1051
  */
999
1052
  const ROOT_LAYER_TYPES = new Set(['modal', 'drawer']);
1000
- export function ensurePageContainer(schema) {
1053
+ export function ensurePageContainer(schema, rootContainer = pageContainerNode) {
1001
1054
  const nodes = asObjects(schema);
1002
1055
  if (nodes.some(node => node.elementId === PAGE_CONTAINER_ELEMENT_ID))
1003
1056
  return { schema: nodes, fixes: [] };
1004
- const container = pageContainerNode();
1057
+ const container = rootContainer();
1005
1058
  const layers = nodes.filter(node => ROOT_LAYER_TYPES.has(String(node.type ?? '')));
1006
1059
  container.children = nodes.filter(node => !ROOT_LAYER_TYPES.has(String(node.type ?? '')));
1007
1060
  const moved = container.children.length;
@@ -1044,7 +1097,7 @@ export function clampBlockSizes(nodes, maxSizes) {
1044
1097
  * başlatır (`ComponentHeightType.Fixed = 0`), yani "alan yok" = "sabit yükseklik". Studio'nun
1045
1098
  * kendi bırakma yolu bunu ASLA yapmaz: katalogdaki `isDefaultAutoHeight`e bakıp
1046
1099
  * `heightType: isDefaultAutoHeight ? 'auto' : 'fixed'` yazar
1047
- * (`ScreenBuilderDroppable.svelte:701`, `store-converter.ts:233`). Katalogda varsayılan
1100
+ * (`ScreenBuilderDroppable.svelte:701`, `store-converter.ts` `createDefaultSchemaNode`). Katalogda varsayılan
1048
1101
  * `true`'dur (`ZeyrekComponent.ts:62`); `false` diyen yalnız sekiz tip var — auth-layout, html,
1049
1102
  * image, layout, qrcode, table, text-editor, video.
1050
1103
  *
@@ -1830,19 +1883,18 @@ export function viewAliasNote(fixes) {
1830
1883
  return [`view alanlarının entity bağı id'den alias'a çevrildi (${fixes.join(', ')}): yazma tarafı viewEntityAlias ister, okuma tarafı viewEntityId döndürür — alias boş kalsa platform tüm alanları ilk tabloya düşürürdü`];
1831
1884
  }
1832
1885
  /**
1833
- * Değişken seti tek bir kayıtta yaşar: `CreateAppVariableCommand`/`CreatePageVariableCommand`
1834
- * yalnız `Data` alanı taşır (Studio uygulama başına ve sayfa başına TEK satır tutar —
1835
- * `IsVariableExistForApplicationAsync` doluysa create update'e döner). Studio'nun gönderdiği şekil
1836
- * `data`'nın **JSON string** olmasıdır: "[{\"key\":\"MyTest\",\"value\":{\"isActive\":true}}]".
1837
- * `Data` tipi `object` olduğu için platform her gövdeyi kabul eder; alan adı tutmayan bir gövde
1838
- * (name/type/defaultValue gibi) `Data`'yı null bırakır ve istek 200 döner ama HİÇBİR ŞEY yazılmaz
1839
- * (17/Ç1). Okuma tarafı da aynı string'i geri verir (`AppVariablesDto.Map` → GetRawText).
1886
+ * `storage` kayıtta isteğe bağlıdır (`docs/adr/0004-state-saklama-turu.md`). Tanınmayan tür ya da
1887
+ * metin olmayan değer çalışma zamanında da yok sayılır (`@ng-apps/ui-native` `variableStorageKinds`);
1888
+ * gateway onu taşımaz ki bir sonraki yazım bozuk değeri kalıcılaştırmasın.
1840
1889
  */
1841
1890
  export function parseVariableEntries(data) {
1842
1891
  const source = typeof data === 'string' ? safeParse(data) : data;
1843
1892
  return asObjects(source)
1844
1893
  .filter(entry => typeof entry.key === 'string' && entry.key !== '')
1845
- .map(entry => ({ key: String(entry.key), value: entry.value }));
1894
+ .map(entry => {
1895
+ const storage = STATE_STORAGE_KINDS.find(kind => kind === entry.storage);
1896
+ return { key: String(entry.key), value: entry.value, ...(storage ? { storage } : {}) };
1897
+ });
1846
1898
  }
1847
1899
  function safeParse(value) {
1848
1900
  try {
@@ -1857,17 +1909,29 @@ function safeParse(value) {
1857
1909
  * mevcut seti okuyup anahtar bazında birleştirir; `replace: true` verilirse bilinçli olarak ezer.
1858
1910
  */
1859
1911
  export function mergeVariableEntries(existing, incoming, replace) {
1912
+ const stored = (entry, current) => {
1913
+ const storage = entry.storage === undefined ? current?.storage : STATE_STORAGE_KINDS.find(kind => kind === entry.storage);
1914
+ const value = entry.value === undefined ? (current ? current.value : null) : entry.value;
1915
+ return { key: entry.key, value, ...(storage ? { storage } : {}) };
1916
+ };
1860
1917
  if (replace)
1861
- return { entries: incoming, fixes: [] };
1918
+ return { entries: incoming.map(entry => stored(entry)), fixes: [] };
1862
1919
  const merged = [...existing];
1863
1920
  const kept = [];
1921
+ const keptStorage = [];
1922
+ const keptValue = [];
1864
1923
  for (const entry of incoming) {
1865
1924
  const index = merged.findIndex(current => current.key === entry.key);
1866
1925
  if (index === -1) {
1867
- merged.push(entry);
1926
+ merged.push(stored(entry));
1868
1927
  continue;
1869
1928
  }
1870
- merged[index] = entry;
1929
+ const current = merged[index];
1930
+ if (entry.storage === undefined && current?.storage)
1931
+ keptStorage.push(`'${entry.key}' → ${current.storage}`);
1932
+ if (entry.value === undefined)
1933
+ keptValue.push(`'${entry.key}'`);
1934
+ merged[index] = stored(entry, current);
1871
1935
  }
1872
1936
  for (const entry of existing) {
1873
1937
  if (!incoming.some(current => current.key === entry.key))
@@ -1875,9 +1939,17 @@ export function mergeVariableEntries(existing, incoming, replace) {
1875
1939
  }
1876
1940
  return {
1877
1941
  entries: merged,
1878
- fixes: kept.length > 0
1879
- ? [`mevcut değişkenler korundu (${kept.join(', ')}): uç seti komple değiştirir, gateway anahtar bazında birleştirdi — kasten silmek için replace: true verin`]
1880
- : []
1942
+ fixes: [
1943
+ ...(kept.length > 0
1944
+ ? [`mevcut değişkenler korundu (${kept.join(', ')}): uç seti komple değiştirir, gateway anahtar bazında birleştirdi — kasten silmek için replace: true verin`]
1945
+ : []),
1946
+ ...(keptStorage.length > 0
1947
+ ? [`saklama türü korundu (${keptStorage.join(', ')}): gelen değişken storage taşımıyordu, kayıttaki tür silinmedi — belleğe çevirmek için storage: 'memory' verin`]
1948
+ : []),
1949
+ ...(keptValue.length > 0
1950
+ ? [`değer korundu (${keptValue.join(', ')}): gelen değişken value taşımıyordu, kayıttaki değer silinmedi — boşaltmak için value: null verin`]
1951
+ : [])
1952
+ ]
1881
1953
  };
1882
1954
  }
1883
1955
  export function blockSizeNote(clamped) {
@@ -1958,12 +2030,22 @@ export function nestedRepeaterNodes(nodes) {
1958
2030
  walk(asObjects(nodes), false);
1959
2031
  return found;
1960
2032
  }
1961
- export function nestedRepeaterMessage(ids) {
1962
- return `PAGE-013 — repeater içinde repeater (istek gönderilmedi): ${ids.join(' · ')}. `
2033
+ /**
2034
+ * Mobil profilde gruplu listenin (gün/kategori başlığı altında satırlar) hazır bileşeni vardır:
2035
+ * `grouped-list` (ui-native, ADR 0007). Web'de karşılığı yok — web mesajı textarea tarifinde kalır.
2036
+ */
2037
+ export function nestedRepeaterMessage(ids, platform = 'web') {
2038
+ const measured = `PAGE-013 — repeater içinde repeater (istek gönderilmedi): ${ids.join(' · ')}. `
1963
2039
  + 'Ölçüldü (2026-08-19): iç repeater OKUMADA çalışır ama girilen değer dış satıra GERİ YAZILMAZ '
1964
2040
  + '(_repeaterValues null kalır) ve tek düzenlemeden sonra DOM bozulur. Sebep yapısal: şemada iç '
1965
- + 'repeater tek düğümdür, dış satır başına ayrı düğüm yoktur. Satır içi listeyi tek bir textarea '
1966
- + "ile topla (satır başına bir öğe) ve kaydederken split('\n') ile böl — platform_guide('page') Kural 1g.";
2041
+ + 'repeater tek düğümdür, dış satır başına ayrı düğüm yoktur. ';
2042
+ const textarea = "Satır içi listeyi tek bir textarea ile topla (satır başına bir öğe) ve kaydederken split('\n') ile böl — platform_guide('page') Kural 1g.";
2043
+ if (platform !== 'mobile')
2044
+ return measured + textarea;
2045
+ return measured
2046
+ + 'Başlıklar altında gruplanmış liste (güne/kategoriye göre) için mobilde tek `grouped-list` kur: düz `rows` + '
2047
+ + '`groupBy` alan yolu; çocuk 0 bölüm başlığı (`{{ Section.title }}`), çocuk 1 öğe şablonu (`Row`, `{{ item.<alan> }}`) — '
2048
+ + "platform_guide('mobile') §Güne göre görev listesi. Satır içinde düzenlenen alt liste gerekiyorsa: " + textarea;
1967
2049
  }
1968
2050
  /**
1969
2051
  * `style` prop'u düz CSS STRING verilmiş düğümler. Çalışır (emotion string'i CSS metni sayar) ama
@@ -1977,7 +2059,7 @@ export function stringStyleNodes(nodes) {
1977
2059
  for (const node of list) {
1978
2060
  const props = (node.props ?? {});
1979
2061
  const style = props.style;
1980
- if (style && typeof style.data === 'string' && style.data.includes(':')) {
2062
+ if (style && typeof style.data === 'string' && style.data.includes(':') && !DOCUMENT_STYLE_TYPES.has(String(node.type))) {
1981
2063
  found.push(String(node.elementId ?? node.name ?? '?'));
1982
2064
  }
1983
2065
  walk(asObjects(node.children));
@@ -2108,6 +2190,95 @@ export function flowBindings(schema, hostEvents) {
2108
2190
  visitNode(node);
2109
2191
  return bindings;
2110
2192
  }
2193
+ /**
2194
+ * TB-125 — op listesi UYGULANDIKTAN sonra şemada (ve düzey olaylarında) hâlâ referanslı olan
2195
+ * akışlar. Simülasyon yalnız akış referansını umursar: eklenen düğüm ağacın köküne konur (konum
2196
+ * sayıma girmez), silinen düğüm ağaçtan çıkarılır, olay op'ları olay dizisini günceller.
2197
+ * `batch` op'u iç listesiyle düzleştirilir.
2198
+ */
2199
+ function survivingFlowRefs(nodes, hostEvents, ops) {
2200
+ const simulated = structuredClone(nodes);
2201
+ const simulatedHostEvents = structuredClone(hostEvents);
2202
+ const removeNode = (list, target) => {
2203
+ const index = list.findIndex(node => node.elementId === target);
2204
+ if (index >= 0) {
2205
+ list.splice(index, 1);
2206
+ return true;
2207
+ }
2208
+ for (const node of list) {
2209
+ if (Array.isArray(node.children) && removeNode(node.children, target))
2210
+ return true;
2211
+ }
2212
+ return false;
2213
+ };
2214
+ const eventsOf = (target) => {
2215
+ if (target === 'page')
2216
+ return simulatedHostEvents;
2217
+ const node = findNodeByElementId(simulated, target);
2218
+ if (!node)
2219
+ return undefined;
2220
+ if (!Array.isArray(node.events))
2221
+ node.events = [];
2222
+ return node.events;
2223
+ };
2224
+ const apply = (op) => {
2225
+ if (op.op === 'batch') {
2226
+ for (const inner of asObjects(op.ops))
2227
+ apply(inner);
2228
+ return;
2229
+ }
2230
+ if (op.op === 'add_component' && op.component && typeof op.component === 'object') {
2231
+ simulated.push(structuredClone(op.component));
2232
+ return;
2233
+ }
2234
+ const target = String(op.target ?? '');
2235
+ if (target === '')
2236
+ return;
2237
+ if (op.op === 'remove_component') {
2238
+ removeNode(simulated, target);
2239
+ return;
2240
+ }
2241
+ if (op.op !== 'set_event' && op.op !== 'add_event' && op.op !== 'remove_event')
2242
+ return;
2243
+ const events = eventsOf(target);
2244
+ if (events === undefined)
2245
+ return;
2246
+ const eventName = String(op.event ?? '');
2247
+ const eventValue = typeof op.eventValue === 'string' ? op.eventValue : undefined;
2248
+ const named = events.filter(event => String(event.name ?? '') === eventName && typeof event.value === 'string' && event.value !== '');
2249
+ if (op.op === 'add_event') {
2250
+ if (eventValue !== undefined)
2251
+ events.push({ name: eventName, value: eventValue });
2252
+ return;
2253
+ }
2254
+ if (op.op === 'set_event') {
2255
+ // set_event adın BÜTÜN bağlarını tek kayda indirir.
2256
+ for (const event of named)
2257
+ events.splice(events.indexOf(event), 1);
2258
+ if (eventValue !== undefined)
2259
+ events.push({ name: eventName, value: eventValue });
2260
+ return;
2261
+ }
2262
+ // Değersiz remove_event çoklu bağda platformda ambiguous-event ile düşer: hiçbir bağ kalkmaz.
2263
+ const removed = eventValue === undefined
2264
+ ? (named.length === 1 ? named : [])
2265
+ : named.filter(event => event.value === eventValue);
2266
+ for (const event of removed)
2267
+ events.splice(events.indexOf(event), 1);
2268
+ };
2269
+ for (const op of ops)
2270
+ apply(op);
2271
+ const surviving = new Set();
2272
+ for (const event of simulatedHostEvents) {
2273
+ if (typeof event.value === 'string')
2274
+ surviving.add(event.value);
2275
+ }
2276
+ for (const node of simulated) {
2277
+ for (const flowId of collectFlowRefs(node))
2278
+ surviving.add(flowId);
2279
+ }
2280
+ return surviving;
2281
+ }
2111
2282
  /**
2112
2283
  * Bir yamanın SİLECEĞİ akışlar. Platform referanssız kalan akışı KALICI siler; uyarı yamadan sonra
2113
2284
  * geldiğinde "önce yedekle" tavsiyesi uygulanamaz hâle geliyordu (09-05 koşusu: sekiz adımlı giriş
@@ -2116,26 +2287,15 @@ export function flowBindings(schema, hostEvents) {
2116
2287
  */
2117
2288
  export function orphanedFlowPlan(schema, pageEvents, ops) {
2118
2289
  const nodes = asObjects(schema);
2119
- const opList = asObjects(ops);
2290
+ // `batch` açılır: içindeki silme de plana girer (kapı yalnız üst düzeye bakınca batch'teki
2291
+ // `remove_component` akışı uyarısız siliyordu).
2292
+ const opList = asObjects(ops).flatMap(op => op.op === 'batch' ? asObjects(op.ops) : [op]);
2120
2293
  const plan = [];
2121
- const survivingRefs = (skipElementId, skipEvent) => {
2122
- const surviving = new Set();
2123
- for (const event of asObjects(pageEvents)) {
2124
- if (typeof event.value === 'string')
2125
- surviving.add(event.value);
2126
- }
2127
- collectFlowRefsExcept(nodes, skipElementId, surviving);
2128
- if (skipEvent !== undefined) {
2129
- const node = findNodeByElementId(nodes, skipElementId);
2130
- for (const event of asObjects(node?.events)) {
2131
- if (String(event.name ?? '') !== skipEvent && typeof event.value === 'string')
2132
- surviving.add(event.value);
2133
- }
2134
- for (const flowId of collectFlowRefs({ ...(node ?? {}), events: [] }))
2135
- surviving.add(flowId);
2136
- }
2137
- return surviving;
2138
- };
2294
+ // TB-125 — hayatta kalan referanslar yamanın SONUCUNDAN okunur, op op özgün ağaçtan değil. Eski
2295
+ // hesap her op'a tek başına baktığı için aynı yamada başka bir düğüme bağlanan akışı da
2296
+ // "referanssız" sayıyor ve düğüm taşıyan her yamayı ikiye böldürüyordu (S2-35). İstek atomik
2297
+ // olduğuna göre doğru soru tektir: yama bittiğinde bu akışa referans kalıyor mu?
2298
+ const surviving = survivingFlowRefs(nodes, asObjects(pageEvents), opList);
2139
2299
  for (const op of opList) {
2140
2300
  const target = String(op.target ?? '');
2141
2301
  if (target === '')
@@ -2145,7 +2305,6 @@ export function orphanedFlowPlan(schema, pageEvents, ops) {
2145
2305
  if (!doomed)
2146
2306
  continue;
2147
2307
  const losing = collectFlowRefs(doomed);
2148
- const surviving = survivingRefs(target);
2149
2308
  const orphaned = [...losing].filter(flowId => !surviving.has(flowId));
2150
2309
  if (orphaned.length > 0)
2151
2310
  plan.push({ target, op: 'remove_component', flowIds: orphaned });
@@ -2167,10 +2326,7 @@ export function orphanedFlowPlan(schema, pageEvents, ops) {
2167
2326
  // oraya bağlı akış kapıdan geçip kalıcı silinirdi. Düzey olayı olmayan widget'ta applier da
2168
2327
  // düğüm aramasına düşer.
2169
2328
  if (op.op === 'remove_event' && target === 'page' && Array.isArray(pageEvents)) {
2170
- const hostEvents = asObjects(pageEvents);
2171
- const removed = removedBy(boundTo(hostEvents));
2172
- const surviving = new Set(hostEvents.filter(event => !removed.includes(event) && typeof event.value === 'string').map(event => String(event.value)));
2173
- collectFlowRefsExcept(nodes, '', surviving);
2329
+ const removed = removedBy(boundTo(asObjects(pageEvents)));
2174
2330
  const flowIds = [...new Set(removed.map(event => String(event.value)))].filter(flowId => !surviving.has(flowId));
2175
2331
  if (flowIds.length > 0)
2176
2332
  plan.push({ target, op: 'remove_event', flowIds });
@@ -2182,11 +2338,6 @@ export function orphanedFlowPlan(schema, pageEvents, ops) {
2182
2338
  const losing = op.op === 'set_event' ? named.filter(event => event.value !== eventValue) : removedBy(named);
2183
2339
  if (losing.length === 0)
2184
2340
  continue;
2185
- const surviving = survivingRefs(target, eventName);
2186
- for (const event of named) {
2187
- if (!losing.includes(event))
2188
- surviving.add(String(event.value));
2189
- }
2190
2341
  const flowIds = [...new Set(losing.map(event => String(event.value)))].filter(flowId => !surviving.has(flowId));
2191
2342
  if (flowIds.length > 0)
2192
2343
  plan.push({ target, op: String(op.op), flowIds });
@@ -357,3 +357,39 @@ export function functionOutline(definition) {
357
357
  view: 'outline'
358
358
  };
359
359
  }
360
+ export function processOutline(payload) {
361
+ const model = (payload.definition && typeof payload.definition === 'object' ? payload.definition : payload);
362
+ const describeAssignee = (value) => {
363
+ if (!value || typeof value !== 'object' || Array.isArray(value))
364
+ return value ?? null;
365
+ const assignee = value;
366
+ const rule = [assignee.rule, assignee.arg].filter(part => part !== undefined && part !== null).join(':');
367
+ return assignee.of ? `${rule} of ${String(assignee.of)}` : rule;
368
+ };
369
+ const describeNodes = (nodes, depth) => asArray(nodes).flatMap(node => [{
370
+ id: node.id,
371
+ type: node.type,
372
+ depth,
373
+ editable: node.editable ?? 'locked',
374
+ description: node.description ?? null,
375
+ assignee: describeAssignee(node.assignee ?? node.to),
376
+ policy: node.policy ?? null,
377
+ sla: Array.isArray(node.sla) ? asArray(node.sla).map(sla => sla.duration) : node.sla ?? null,
378
+ on: asArray(node.on).map(handler => `${String(handler.event)} → ${String(handler.handler?.type ?? '?')}${handler.afterHandlers ? ` (${String(handler.afterHandlers)})` : ''}`)
379
+ }, ...describeNodes(node.nodes, depth + 1)]);
380
+ const nodes = describeNodes(model.nodes, 0);
381
+ return {
382
+ id: payload.id ?? null,
383
+ slug: model.slug ?? payload.slug,
384
+ name: model.name ?? payload.name,
385
+ version: payload.version ?? null,
386
+ activeVersionId: payload.activeVersionId ?? null,
387
+ versions: asArray(payload.versions).map(version => ({ number: version.number, state: version.state })),
388
+ nodeCount: nodes.length,
389
+ nodes,
390
+ edgeCount: asArray(model.edges).length,
391
+ on: asArray(model.on).map(handler => String(handler.event)),
392
+ view: 'outline',
393
+ hint: "Tek düğüm için process_definition_get view:'part', part:'definition.nodes[id=<düğüm>]'; değişiklik için process_definition_patch (yol /nodes[id=<düğüm>]/…)."
394
+ };
395
+ }