@simplysm/sd-claude 14.0.50 → 14.0.52

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 (318) hide show
  1. package/claude/references/sd-frontend-design.md +10 -9
  2. package/claude/references/sd-simplysm-v14/angular/README.md +497 -0
  3. package/claude/references/sd-simplysm-v14/angular/bootstrap/provide-sd-angular.md +37 -0
  4. package/claude/references/sd-simplysm-v14/angular/bootstrap/sd-angular-config-provider.md +16 -0
  5. package/claude/references/sd-simplysm-v14/angular/directives/sd-command-directive.md +27 -0
  6. package/claude/references/sd-simplysm-v14/angular/directives/sd-events.md +25 -0
  7. package/claude/references/sd-simplysm-v14/angular/directives/sd-intersection-directive.md +36 -0
  8. package/claude/references/sd-simplysm-v14/angular/directives/sd-invalid.md +24 -0
  9. package/claude/references/sd-simplysm-v14/angular/directives/sd-resize-directive.md +42 -0
  10. package/claude/references/sd-simplysm-v14/angular/directives/sd-ripple.md +23 -0
  11. package/claude/references/sd-simplysm-v14/angular/directives/sd-router-link.md +38 -0
  12. package/claude/references/sd-simplysm-v14/angular/directives/sd-show-effect.md +18 -0
  13. package/claude/references/sd-simplysm-v14/angular/directives/sd-typed-template.md +69 -0
  14. package/claude/references/sd-simplysm-v14/angular/features/sd-address-search-modal.md +50 -0
  15. package/claude/references/sd-simplysm-v14/angular/features/sd-permission-table.md +20 -0
  16. package/claude/references/sd-simplysm-v14/angular/features/sd-shared-data-components.md +158 -0
  17. package/claude/references/sd-simplysm-v14/angular/features/sd-tiptap-editor.md +26 -0
  18. package/claude/references/sd-simplysm-v14/angular/pipes/format-pipe.md +41 -0
  19. package/claude/references/sd-simplysm-v14/angular/plugins/sd-global-error-handler.md +23 -0
  20. package/claude/references/sd-simplysm-v14/angular/plugins/sd-option-event-plugin.md +34 -0
  21. package/claude/references/sd-simplysm-v14/angular/provider-types/sd-menu.md +65 -0
  22. package/claude/references/sd-simplysm-v14/angular/provider-types/sd-modal-content-def.md +148 -0
  23. package/claude/references/sd-simplysm-v14/angular/provider-types/sd-toast-content-def.md +73 -0
  24. package/claude/references/sd-simplysm-v14/angular/provider-types/shared-data-base.md +59 -0
  25. package/claude/references/sd-simplysm-v14/angular/providers/sd-activated-modal-provider.md +34 -0
  26. package/claude/references/sd-simplysm-v14/angular/providers/sd-app-structure-provider.md +81 -0
  27. package/claude/references/sd-simplysm-v14/angular/providers/sd-busy-provider.md +18 -0
  28. package/claude/references/sd-simplysm-v14/angular/providers/sd-file-dialog-provider.md +40 -0
  29. package/claude/references/sd-simplysm-v14/angular/providers/sd-local-storage-provider.md +20 -0
  30. package/claude/references/sd-simplysm-v14/angular/providers/sd-modal-provider.md +67 -0
  31. package/claude/references/sd-simplysm-v14/angular/providers/sd-navigate-window-provider.md +18 -0
  32. package/claude/references/sd-simplysm-v14/angular/providers/sd-print-provider.md +25 -0
  33. package/claude/references/sd-simplysm-v14/angular/providers/sd-service-client-factory-provider.md +43 -0
  34. package/claude/references/sd-simplysm-v14/angular/providers/sd-shared-data-provider.md +64 -0
  35. package/claude/references/sd-simplysm-v14/angular/providers/sd-system-config-provider.md +46 -0
  36. package/claude/references/sd-simplysm-v14/angular/providers/sd-system-log-provider.md +18 -0
  37. package/claude/references/sd-simplysm-v14/angular/providers/sd-theme-provider.md +38 -0
  38. package/claude/references/sd-simplysm-v14/angular/providers/sd-toast-provider.md +65 -0
  39. package/claude/references/sd-simplysm-v14/angular/recipes/_common-rules.md +336 -0
  40. package/claude/references/sd-simplysm-v14/angular/recipes/crud-detail/extension-a-edit-save.md +191 -0
  41. package/claude/references/sd-simplysm-v14/angular/recipes/crud-detail/extension-b-delete-restore.md +103 -0
  42. package/claude/references/sd-simplysm-v14/angular/recipes/crud-detail/extension-c-modal-view.md +198 -0
  43. package/claude/references/sd-simplysm-v14/angular/recipes/crud-detail/extension-d-control-view.md +109 -0
  44. package/claude/references/sd-simplysm-v14/angular/recipes/crud-detail/extension-e-auxiliary.md +87 -0
  45. package/claude/references/sd-simplysm-v14/angular/recipes/crud-detail/extension-f-complex-detail.md +202 -0
  46. package/claude/references/sd-simplysm-v14/angular/recipes/crud-detail.md +280 -0
  47. package/claude/references/sd-simplysm-v14/angular/recipes/crud-list/extension-a-inline-edit.md +386 -0
  48. package/claude/references/sd-simplysm-v14/angular/recipes/crud-list/extension-b-selection.md +215 -0
  49. package/claude/references/sd-simplysm-v14/angular/recipes/crud-list/extension-c-inline-delete.md +64 -0
  50. package/claude/references/sd-simplysm-v14/angular/recipes/crud-list/extension-d-select-modal.md +193 -0
  51. package/claude/references/sd-simplysm-v14/angular/recipes/crud-list/extension-e-readonly-modal.md +140 -0
  52. package/claude/references/sd-simplysm-v14/angular/recipes/crud-list/extension-f-modal-edit.md +123 -0
  53. package/claude/references/sd-simplysm-v14/angular/recipes/crud-list/extension-g-excel.md +145 -0
  54. package/claude/references/sd-simplysm-v14/angular/recipes/crud-list.md +377 -0
  55. package/claude/references/sd-simplysm-v14/angular/recipes/data-select-button.md +368 -0
  56. package/claude/references/sd-simplysm-v14/angular/recipes/page-modal-container.md +238 -0
  57. package/claude/references/sd-simplysm-v14/angular/styling/classes.md +149 -0
  58. package/claude/references/sd-simplysm-v14/angular/styling/mixins.md +100 -0
  59. package/claude/references/sd-simplysm-v14/angular/styling/themes.md +35 -0
  60. package/claude/references/sd-simplysm-v14/angular/styling/variables.md +147 -0
  61. package/claude/references/sd-simplysm-v14/angular/type-utilities/directive-input-signals.md +232 -0
  62. package/claude/references/sd-simplysm-v14/angular/ui-data/sd-list.md +37 -0
  63. package/claude/references/sd-simplysm-v14/angular/ui-data/sd-sheet.md +212 -0
  64. package/claude/references/sd-simplysm-v14/angular/ui-form/sd-additional-button.md +26 -0
  65. package/claude/references/sd-simplysm-v14/angular/ui-form/sd-anchor.md +31 -0
  66. package/claude/references/sd-simplysm-v14/angular/ui-form/sd-button.md +103 -0
  67. package/claude/references/sd-simplysm-v14/angular/ui-form/sd-checkbox-group.md +39 -0
  68. package/claude/references/sd-simplysm-v14/angular/ui-form/sd-checkbox.md +81 -0
  69. package/claude/references/sd-simplysm-v14/angular/ui-form/sd-date-range-picker.md +27 -0
  70. package/claude/references/sd-simplysm-v14/angular/ui-form/sd-form.md +89 -0
  71. package/claude/references/sd-simplysm-v14/angular/ui-form/sd-modal-select-button.md +54 -0
  72. package/claude/references/sd-simplysm-v14/angular/ui-form/sd-numpad.md +26 -0
  73. package/claude/references/sd-simplysm-v14/angular/ui-form/sd-range.md +26 -0
  74. package/claude/references/sd-simplysm-v14/angular/ui-form/sd-select.md +68 -0
  75. package/claude/references/sd-simplysm-v14/angular/ui-form/sd-shared-data-select.md +52 -0
  76. package/claude/references/sd-simplysm-v14/angular/ui-form/sd-state-preset.md +37 -0
  77. package/claude/references/sd-simplysm-v14/angular/ui-form/sd-switch.md +27 -0
  78. package/claude/references/sd-simplysm-v14/angular/ui-form/sd-textarea.md +33 -0
  79. package/claude/references/sd-simplysm-v14/angular/ui-form/sd-textfield.md +145 -0
  80. package/claude/references/sd-simplysm-v14/angular/ui-layout/sd-dock-container.md +64 -0
  81. package/claude/references/sd-simplysm-v14/angular/ui-layout/sd-dock.md +37 -0
  82. package/claude/references/sd-simplysm-v14/angular/ui-layout/sd-gap.md +26 -0
  83. package/claude/references/sd-simplysm-v14/angular/ui-layout/sd-kanban-board.md +96 -0
  84. package/claude/references/sd-simplysm-v14/angular/ui-layout/sd-kanban-lane.md +34 -0
  85. package/claude/references/sd-simplysm-v14/angular/ui-layout/sd-kanban.md +29 -0
  86. package/claude/references/sd-simplysm-v14/angular/ui-navigation/sd-collapse.md +35 -0
  87. package/claude/references/sd-simplysm-v14/angular/ui-navigation/sd-pagination.md +26 -0
  88. package/claude/references/sd-simplysm-v14/angular/ui-navigation/sd-sidebar-container.md +49 -0
  89. package/claude/references/sd-simplysm-v14/angular/ui-navigation/sd-sidebar-menu.md +22 -0
  90. package/claude/references/sd-simplysm-v14/angular/ui-navigation/sd-sidebar-user.md +43 -0
  91. package/claude/references/sd-simplysm-v14/angular/ui-navigation/sd-tab.md +51 -0
  92. package/claude/references/sd-simplysm-v14/angular/ui-navigation/sd-topbar-container.md +97 -0
  93. package/claude/references/sd-simplysm-v14/angular/ui-navigation/sd-topbar-menu.md +23 -0
  94. package/claude/references/sd-simplysm-v14/angular/ui-navigation/sd-topbar-user.md +38 -0
  95. package/claude/references/sd-simplysm-v14/angular/ui-navigation/sd-topbar.md +30 -0
  96. package/claude/references/sd-simplysm-v14/angular/ui-overlay/sd-busy-container.md +69 -0
  97. package/claude/references/sd-simplysm-v14/angular/ui-overlay/sd-confirm-modal.md +30 -0
  98. package/claude/references/sd-simplysm-v14/angular/ui-overlay/sd-dropdown.md +40 -0
  99. package/claude/references/sd-simplysm-v14/angular/ui-overlay/sd-modal.md +34 -0
  100. package/claude/references/sd-simplysm-v14/angular/ui-overlay/sd-prompt-modal.md +30 -0
  101. package/claude/references/sd-simplysm-v14/angular/ui-overlay/sd-toast.md +35 -0
  102. package/claude/references/sd-simplysm-v14/angular/ui-visual/sd-barcode.md +36 -0
  103. package/claude/references/sd-simplysm-v14/angular/ui-visual/sd-calendar.md +34 -0
  104. package/claude/references/sd-simplysm-v14/angular/ui-visual/sd-echarts.md +32 -0
  105. package/claude/references/sd-simplysm-v14/angular/ui-visual/sd-label.md +24 -0
  106. package/claude/references/sd-simplysm-v14/angular/ui-visual/sd-note.md +23 -0
  107. package/claude/references/sd-simplysm-v14/angular/ui-visual/sd-progress.md +23 -0
  108. package/claude/references/sd-simplysm-v14/angular/utils/inject-routing-signals.md +161 -0
  109. package/claude/references/sd-simplysm-v14/angular/utils/inject-sd-system-config-resource.md +35 -0
  110. package/claude/references/sd-simplysm-v14/angular/utils/mark.md +43 -0
  111. package/claude/references/sd-simplysm-v14/angular/utils/selection-managers.md +96 -0
  112. package/claude/references/sd-simplysm-v14/angular/utils/set-safe-style.md +19 -0
  113. package/claude/references/sd-simplysm-v14/angular/utils/setup-functions.md +93 -0
  114. package/claude/references/sd-simplysm-v14/capacitor-plugin-auto-update/README.md +38 -0
  115. package/claude/references/sd-simplysm-v14/capacitor-plugin-auto-update/apk-installer/apk-installer.md +115 -0
  116. package/claude/references/sd-simplysm-v14/capacitor-plugin-auto-update/auto-update/auto-update.md +113 -0
  117. package/claude/references/sd-simplysm-v14/capacitor-plugin-file-system/README.md +197 -0
  118. package/claude/references/sd-simplysm-v14/capacitor-plugin-intent/README.md +235 -0
  119. package/claude/references/sd-simplysm-v14/capacitor-plugin-usb-storage/README.md +251 -0
  120. package/claude/references/sd-simplysm-v14/core-browser/README.md +52 -0
  121. package/claude/references/sd-simplysm-v14/core-browser/extensions/copy-paste.md +59 -0
  122. package/claude/references/sd-simplysm-v14/core-browser/extensions/element-prototype-extensions.md +137 -0
  123. package/claude/references/sd-simplysm-v14/core-browser/extensions/get-bounds.md +84 -0
  124. package/claude/references/sd-simplysm-v14/core-browser/utils/download-blob.md +59 -0
  125. package/claude/references/sd-simplysm-v14/core-browser/utils/fetch-url-bytes.md +91 -0
  126. package/claude/references/sd-simplysm-v14/core-browser/utils/indexed-db-store.md +131 -0
  127. package/claude/references/sd-simplysm-v14/core-browser/utils/indexed-db-virtual-fs.md +121 -0
  128. package/claude/references/sd-simplysm-v14/core-browser/utils/open-file-dialog.md +60 -0
  129. package/claude/references/sd-simplysm-v14/core-common/README.md +179 -0
  130. package/claude/references/sd-simplysm-v14/core-common/errors/argument-error.md +26 -0
  131. package/claude/references/sd-simplysm-v14/core-common/errors/not-implemented-error.md +33 -0
  132. package/claude/references/sd-simplysm-v14/core-common/errors/sd-error.md +38 -0
  133. package/claude/references/sd-simplysm-v14/core-common/errors/timeout-error.md +36 -0
  134. package/claude/references/sd-simplysm-v14/core-common/extensions/array.md +125 -0
  135. package/claude/references/sd-simplysm-v14/core-common/extensions/map.md +43 -0
  136. package/claude/references/sd-simplysm-v14/core-common/extensions/set.md +35 -0
  137. package/claude/references/sd-simplysm-v14/core-common/features/debounce-queue.md +48 -0
  138. package/claude/references/sd-simplysm-v14/core-common/features/event-emitter.md +52 -0
  139. package/claude/references/sd-simplysm-v14/core-common/features/serial-queue.md +44 -0
  140. package/claude/references/sd-simplysm-v14/core-common/type-utils/common-types.md +100 -0
  141. package/claude/references/sd-simplysm-v14/core-common/type-utils/env.md +42 -0
  142. package/claude/references/sd-simplysm-v14/core-common/types/date-only.md +86 -0
  143. package/claude/references/sd-simplysm-v14/core-common/types/date-time.md +106 -0
  144. package/claude/references/sd-simplysm-v14/core-common/types/lazy-gc-map.md +59 -0
  145. package/claude/references/sd-simplysm-v14/core-common/types/time.md +62 -0
  146. package/claude/references/sd-simplysm-v14/core-common/types/uuid.md +41 -0
  147. package/claude/references/sd-simplysm-v14/core-common/utils/bytes.md +36 -0
  148. package/claude/references/sd-simplysm-v14/core-common/utils/dt.md +60 -0
  149. package/claude/references/sd-simplysm-v14/core-common/utils/err.md +26 -0
  150. package/claude/references/sd-simplysm-v14/core-common/utils/json.md +58 -0
  151. package/claude/references/sd-simplysm-v14/core-common/utils/num.md +56 -0
  152. package/claude/references/sd-simplysm-v14/core-common/utils/obj.md +107 -0
  153. package/claude/references/sd-simplysm-v14/core-common/utils/path.md +30 -0
  154. package/claude/references/sd-simplysm-v14/core-common/utils/primitive.md +28 -0
  155. package/claude/references/sd-simplysm-v14/core-common/utils/str.md +63 -0
  156. package/claude/references/sd-simplysm-v14/core-common/utils/template-strings.md +49 -0
  157. package/claude/references/sd-simplysm-v14/core-common/utils/transfer.md +35 -0
  158. package/claude/references/sd-simplysm-v14/core-common/utils/wait.md +35 -0
  159. package/claude/references/sd-simplysm-v14/core-common/utils/xml.md +49 -0
  160. package/claude/references/sd-simplysm-v14/core-common/utils/zip-archive.md +77 -0
  161. package/claude/references/sd-simplysm-v14/core-node/README.md +59 -0
  162. package/claude/references/sd-simplysm-v14/core-node/features/fs-watcher.md +110 -0
  163. package/claude/references/sd-simplysm-v14/core-node/logging/create-file-reporter.md +78 -0
  164. package/claude/references/sd-simplysm-v14/core-node/logging/pretty-reporter.md +38 -0
  165. package/claude/references/sd-simplysm-v14/core-node/logging/setup-consola.md +77 -0
  166. package/claude/references/sd-simplysm-v14/core-node/utils/cpx.md +128 -0
  167. package/claude/references/sd-simplysm-v14/core-node/utils/fsx.md +168 -0
  168. package/claude/references/sd-simplysm-v14/core-node/utils/pathx.md +73 -0
  169. package/claude/references/sd-simplysm-v14/core-node/worker/create-worker.md +85 -0
  170. package/claude/references/sd-simplysm-v14/core-node/worker/worker.md +160 -0
  171. package/claude/references/sd-simplysm-v14/excel/README.md +66 -0
  172. package/claude/references/sd-simplysm-v14/excel/core-classes/excel-cell.md +79 -0
  173. package/claude/references/sd-simplysm-v14/excel/core-classes/excel-col.md +36 -0
  174. package/claude/references/sd-simplysm-v14/excel/core-classes/excel-row.md +34 -0
  175. package/claude/references/sd-simplysm-v14/excel/core-classes/excel-workbook.md +93 -0
  176. package/claude/references/sd-simplysm-v14/excel/core-classes/excel-worksheet.md +147 -0
  177. package/claude/references/sd-simplysm-v14/excel/types/excel-address-point.md +33 -0
  178. package/claude/references/sd-simplysm-v14/excel/types/excel-style-options.md +57 -0
  179. package/claude/references/sd-simplysm-v14/excel/types/excel-value-type.md +28 -0
  180. package/claude/references/sd-simplysm-v14/excel/types/excel-xml-content-type-data.md +23 -0
  181. package/claude/references/sd-simplysm-v14/excel/types/excel-xml-drawing-data.md +29 -0
  182. package/claude/references/sd-simplysm-v14/excel/types/excel-xml-relationship-data.md +39 -0
  183. package/claude/references/sd-simplysm-v14/excel/types/excel-xml-shared-string-data.md +42 -0
  184. package/claude/references/sd-simplysm-v14/excel/types/excel-xml-style-data.md +97 -0
  185. package/claude/references/sd-simplysm-v14/excel/types/excel-xml-workbook-data.md +22 -0
  186. package/claude/references/sd-simplysm-v14/excel/types/excel-xml-worksheet-data.md +68 -0
  187. package/claude/references/sd-simplysm-v14/excel/types/excel-xml.md +15 -0
  188. package/claude/references/sd-simplysm-v14/excel/utilities/excel-utils.md +101 -0
  189. package/claude/references/sd-simplysm-v14/excel/wrapper/excel-wrapper.md +108 -0
  190. package/claude/references/sd-simplysm-v14/lint/README.md +183 -0
  191. package/claude/references/sd-simplysm-v14/orm-common/README.md +156 -0
  192. package/claude/references/sd-simplysm-v14/orm-common/core/db-context.md +208 -0
  193. package/claude/references/sd-simplysm-v14/orm-common/core/db-transaction-error.md +64 -0
  194. package/claude/references/sd-simplysm-v14/orm-common/expression/expr-unit.md +62 -0
  195. package/claude/references/sd-simplysm-v14/orm-common/expression/expr.md +198 -0
  196. package/claude/references/sd-simplysm-v14/orm-common/models/migration.md +37 -0
  197. package/claude/references/sd-simplysm-v14/orm-common/query-builder/create-query-builder.md +80 -0
  198. package/claude/references/sd-simplysm-v14/orm-common/queryable-executable/executable.md +54 -0
  199. package/claude/references/sd-simplysm-v14/orm-common/queryable-executable/parse-search-query.md +75 -0
  200. package/claude/references/sd-simplysm-v14/orm-common/queryable-executable/queryable.md +238 -0
  201. package/claude/references/sd-simplysm-v14/orm-common/schema-builders/column-builder.md +63 -0
  202. package/claude/references/sd-simplysm-v14/orm-common/schema-builders/foreign-key-builder.md +137 -0
  203. package/claude/references/sd-simplysm-v14/orm-common/schema-builders/index-builder.md +54 -0
  204. package/claude/references/sd-simplysm-v14/orm-common/schema-builders/procedure.md +67 -0
  205. package/claude/references/sd-simplysm-v14/orm-common/schema-builders/table.md +95 -0
  206. package/claude/references/sd-simplysm-v14/orm-common/schema-builders/view.md +71 -0
  207. package/claude/references/sd-simplysm-v14/orm-common/types/data-type.md +146 -0
  208. package/claude/references/sd-simplysm-v14/orm-common/types/dialect.md +151 -0
  209. package/claude/references/sd-simplysm-v14/orm-common/types/expr.md +175 -0
  210. package/claude/references/sd-simplysm-v14/orm-common/types/parse-query-result.md +58 -0
  211. package/claude/references/sd-simplysm-v14/orm-common/types/query-def.md +224 -0
  212. package/claude/references/sd-simplysm-v14/orm-node/README.md +65 -0
  213. package/claude/references/sd-simplysm-v14/orm-node/connections/mssql-db-conn.md +85 -0
  214. package/claude/references/sd-simplysm-v14/orm-node/connections/mysql-db-conn.md +83 -0
  215. package/claude/references/sd-simplysm-v14/orm-node/connections/postgresql-db-conn.md +86 -0
  216. package/claude/references/sd-simplysm-v14/orm-node/core/create-db-conn.md +62 -0
  217. package/claude/references/sd-simplysm-v14/orm-node/core/create-orm.md +107 -0
  218. package/claude/references/sd-simplysm-v14/orm-node/core/node-db-context-executor.md +50 -0
  219. package/claude/references/sd-simplysm-v14/orm-node/types/db-conn-config.md +91 -0
  220. package/claude/references/sd-simplysm-v14/orm-node/types/db-conn-constants.md +33 -0
  221. package/claude/references/sd-simplysm-v14/orm-node/types/db-conn.md +60 -0
  222. package/claude/references/sd-simplysm-v14/orm-node/types/get-dialect-from-config.md +17 -0
  223. package/{README.md → claude/references/sd-simplysm-v14/sd-claude/README.md} +85 -84
  224. package/{docs → claude/references/sd-simplysm-v14/sd-claude}/assets.md +2 -2
  225. package/{docs → claude/references/sd-simplysm-v14/sd-claude}/hooks.md +15 -1
  226. package/claude/references/sd-simplysm-v14/sd-cli/README.md +138 -0
  227. package/claude/references/sd-simplysm-v14/sd-cli/angular-vite-plugin/sd-angular-plugin.md +60 -0
  228. package/claude/references/sd-simplysm-v14/sd-cli/config/build-target.md +31 -0
  229. package/claude/references/sd-simplysm-v14/sd-cli/config/npm-config.md +27 -0
  230. package/claude/references/sd-simplysm-v14/sd-cli/config/sd-browser-support-config.md +19 -0
  231. package/claude/references/sd-simplysm-v14/sd-cli/config/sd-build-package-config.md +21 -0
  232. package/claude/references/sd-simplysm-v14/sd-cli/config/sd-capacitor-config.md +109 -0
  233. package/claude/references/sd-simplysm-v14/sd-cli/config/sd-client-package-config.md +33 -0
  234. package/claude/references/sd-simplysm-v14/sd-cli/config/sd-config.md +78 -0
  235. package/claude/references/sd-simplysm-v14/sd-cli/config/sd-electron-config.md +27 -0
  236. package/claude/references/sd-simplysm-v14/sd-cli/config/sd-package-config.md +18 -0
  237. package/claude/references/sd-simplysm-v14/sd-cli/config/sd-post-publish-script-config.md +19 -0
  238. package/claude/references/sd-simplysm-v14/sd-cli/config/sd-publish-config.md +72 -0
  239. package/claude/references/sd-simplysm-v14/sd-cli/config/sd-pwa-config.md +41 -0
  240. package/claude/references/sd-simplysm-v14/sd-cli/config/sd-scripts-package-config.md +19 -0
  241. package/claude/references/sd-simplysm-v14/sd-cli/config/sd-server-package-config.md +32 -0
  242. package/claude/references/sd-simplysm-v14/sd-cli/config/sd-watch-hook-config.md +19 -0
  243. package/claude/references/sd-simplysm-v14/sd-cli/ts-compiler/sd-ts-compiler.md +158 -0
  244. package/claude/references/sd-simplysm-v14/service-client/README.md +74 -0
  245. package/claude/references/sd-simplysm-v14/service-client/features/event-client.md +93 -0
  246. package/claude/references/sd-simplysm-v14/service-client/features/file-client.md +63 -0
  247. package/claude/references/sd-simplysm-v14/service-client/features/orm-client-connector.md +89 -0
  248. package/claude/references/sd-simplysm-v14/service-client/features/orm-client-db-context-executor.md +31 -0
  249. package/claude/references/sd-simplysm-v14/service-client/main/service-client.md +206 -0
  250. package/claude/references/sd-simplysm-v14/service-client/protocol/client-protocol-wrapper.md +64 -0
  251. package/claude/references/sd-simplysm-v14/service-client/transport/service-transport.md +68 -0
  252. package/claude/references/sd-simplysm-v14/service-client/transport/socket-provider.md +100 -0
  253. package/claude/references/sd-simplysm-v14/service-client/types/blob-input.md +7 -0
  254. package/claude/references/sd-simplysm-v14/service-client/types/browser-worker.md +47 -0
  255. package/claude/references/sd-simplysm-v14/service-client/types/file-collection.md +21 -0
  256. package/claude/references/sd-simplysm-v14/service-client/types/service-connection-options.md +22 -0
  257. package/claude/references/sd-simplysm-v14/service-client/types/service-progress.md +39 -0
  258. package/claude/references/sd-simplysm-v14/service-common/README.md +161 -0
  259. package/claude/references/sd-simplysm-v14/service-common/app-structure/app-structure-item.md +107 -0
  260. package/claude/references/sd-simplysm-v14/service-common/app-structure/get-flat-permissions.md +57 -0
  261. package/claude/references/sd-simplysm-v14/service-common/app-structure/is-usable-modules-chain.md +23 -0
  262. package/claude/references/sd-simplysm-v14/service-common/app-structure/is-usable-modules.md +42 -0
  263. package/claude/references/sd-simplysm-v14/service-common/events/define-event.md +68 -0
  264. package/claude/references/sd-simplysm-v14/service-common/protocol/create-service-protocol.md +93 -0
  265. package/claude/references/sd-simplysm-v14/service-common/protocol/protocol-config.md +21 -0
  266. package/claude/references/sd-simplysm-v14/service-common/protocol/service-add-event-listener-message.md +23 -0
  267. package/claude/references/sd-simplysm-v14/service-common/protocol/service-auth-message.md +17 -0
  268. package/claude/references/sd-simplysm-v14/service-common/protocol/service-emit-event-message.md +21 -0
  269. package/claude/references/sd-simplysm-v14/service-common/protocol/service-error-message.md +29 -0
  270. package/claude/references/sd-simplysm-v14/service-common/protocol/service-event-message.md +21 -0
  271. package/claude/references/sd-simplysm-v14/service-common/protocol/service-get-event-listener-infos-message.md +19 -0
  272. package/claude/references/sd-simplysm-v14/service-common/protocol/service-message.md +52 -0
  273. package/claude/references/sd-simplysm-v14/service-common/protocol/service-progress-message.md +21 -0
  274. package/claude/references/sd-simplysm-v14/service-common/protocol/service-remove-event-listener-message.md +19 -0
  275. package/claude/references/sd-simplysm-v14/service-common/protocol/service-request-message.md +17 -0
  276. package/claude/references/sd-simplysm-v14/service-common/protocol/service-response-message.md +17 -0
  277. package/claude/references/sd-simplysm-v14/service-common/service-types/app-structure-service.md +15 -0
  278. package/claude/references/sd-simplysm-v14/service-common/service-types/auto-update-service.md +20 -0
  279. package/claude/references/sd-simplysm-v14/service-common/service-types/orm-service.md +61 -0
  280. package/claude/references/sd-simplysm-v14/service-common/types/service-upload-result.md +19 -0
  281. package/claude/references/sd-simplysm-v14/service-server/README.md +162 -0
  282. package/claude/references/sd-simplysm-v14/service-server/auth/auth-token-payload.md +18 -0
  283. package/claude/references/sd-simplysm-v14/service-server/auth/sign-jwt.md +30 -0
  284. package/claude/references/sd-simplysm-v14/service-server/auth/verify-jwt.md +35 -0
  285. package/claude/references/sd-simplysm-v14/service-server/core/auth.md +64 -0
  286. package/claude/references/sd-simplysm-v14/service-server/core/define-service.md +81 -0
  287. package/claude/references/sd-simplysm-v14/service-server/core/execute-service-method.md +43 -0
  288. package/claude/references/sd-simplysm-v14/service-server/core/service-context.md +79 -0
  289. package/claude/references/sd-simplysm-v14/service-server/legacy/handle-v1-connection.md +25 -0
  290. package/claude/references/sd-simplysm-v14/service-server/main/create-service-server.md +32 -0
  291. package/claude/references/sd-simplysm-v14/service-server/main/service-server.md +113 -0
  292. package/claude/references/sd-simplysm-v14/service-server/protocol/server-protocol-wrapper.md +35 -0
  293. package/claude/references/sd-simplysm-v14/service-server/services/app-structure-service.md +59 -0
  294. package/claude/references/sd-simplysm-v14/service-server/services/auto-update-service.md +34 -0
  295. package/claude/references/sd-simplysm-v14/service-server/services/orm-service.md +43 -0
  296. package/claude/references/sd-simplysm-v14/service-server/transport-http/handle-http-request.md +33 -0
  297. package/claude/references/sd-simplysm-v14/service-server/transport-http/handle-static-file.md +29 -0
  298. package/claude/references/sd-simplysm-v14/service-server/transport-http/handle-upload.md +33 -0
  299. package/claude/references/sd-simplysm-v14/service-server/transport-socket/service-socket.md +64 -0
  300. package/claude/references/sd-simplysm-v14/service-server/transport-socket/websocket-handler.md +57 -0
  301. package/claude/references/sd-simplysm-v14/service-server/types/service-server-options.md +36 -0
  302. package/claude/references/sd-simplysm-v14/service-server/utils/get-config.md +29 -0
  303. package/claude/references/sd-simplysm-v14/storage/README.md +99 -0
  304. package/claude/references/sd-simplysm-v14/storage/clients/ftp-storage-client.md +99 -0
  305. package/claude/references/sd-simplysm-v14/storage/clients/sftp-storage-client.md +108 -0
  306. package/claude/references/sd-simplysm-v14/storage/factory/storage-factory.md +114 -0
  307. package/claude/references/sd-simplysm-v14/storage/types/file-info.md +32 -0
  308. package/claude/references/sd-simplysm-v14/storage/types/storage-client.md +55 -0
  309. package/claude/references/sd-simplysm-v14/storage/types/storage-conn-config.md +34 -0
  310. package/claude/rules/sd-claude-rules.md +8 -8
  311. package/claude/rules/sd-simplysm-v14.md +33 -0
  312. package/claude/skills/sd-claude-docs/SKILL.md +41 -24
  313. package/claude/skills/sd-claude-docs/references/package-docs.md +240 -116
  314. package/claude/skills/sd-inner-debug/SKILL.md +1 -1
  315. package/claude/skills/sd-inner-review/SKILL.md +4 -2
  316. package/package.json +2 -3
  317. /package/{docs → claude/references/sd-simplysm-v14/sd-claude}/cli.md +0 -0
  318. /package/{docs → claude/references/sd-simplysm-v14/sd-claude}/scripts.md +0 -0
@@ -0,0 +1,191 @@
1
+ ← [CRUD 상세폼 레시피 진입점](../crud-detail.md)
2
+
3
+ # 확장 A: 편집/저장
4
+
5
+ > **선행:** 없음 (최소 뼈대 §3에 직접 얹음)
6
+
7
+ 최소 뼈대의 읽기 전용 필드를 **편집 가능**으로 바꾸고, topbar에 "저장" 버튼(또는 Ctrl+S)을 추가하여 **일괄 저장**을 도입한다. `obj.clone(this.data())` snapshot + `obj.equal(data, _dataSnapshot)` 기반 변경 감지를 부착하고, 라우트 이탈 시 미저장 변경사항을 확인하는 `setupCanDeactivate` 가드를 등록한다. 편집은 page/modal/control 모든 뷰에서 동작하며, 뷰별 UI 배치는 [확장 C](./extension-c-modal-view.md)/[확장 D](./extension-d-control-view.md)에서 처리한다.
8
+
9
+ **이 확장이 도입하는 요소:**
10
+
11
+ - **imports:** `computed`, `viewChild`, `obj`, `setupCanDeactivate`, `mark`, `SdCommandDirective` outputs에 `sdSaveCommand` 추가, 아이콘 `tablerDeviceFloppy`
12
+ - **DI:** 없음 (최소 뼈대와 동일)
13
+ - **파생:** `canEdit = computed(() => perms().includes("edit"))` (뷰 분기 없음 — 편집은 3뷰 모두에서 가능, 뷰별 UI는 [확장 C](./extension-c-modal-view.md)/[확장 D](./extension-d-control-view.md)에서 분기)
14
+ - **상태:** `_dataSnapshot?: ICustomer` (직전 `_refresh()` 시점 data의 깊은 복제), `isNew = computed(() => data().id == null)` (신규 레코드 여부)
15
+ - **권한 키 확장:** `injectPermsSignal` 두 번째 인자 `["use"]` → `["use", "edit"]`
16
+ - **hostDirectives·host:** `outputs`에 `sdSaveCommand` 추가, `host`에 `(sdSaveCommand)="onSaveButtonClick()"` 추가
17
+ - **viewChild:** `formCtrl = viewChild<SdForm>("formCtrl")` (Ctrl+S 경로 통합용)
18
+ - **메서드:** `onSaveButtonClick`, `onSubmit`, `_checkIgnoreChanges`
19
+ - **for Template:** `protected readonly mark = mark;` (템플릿 `(valueChange)="mark(data)"` 호출용)
20
+ - **생성자:** 기존 초기 effect 뒤에 `setupCanDeactivate(() => this._checkIgnoreChanges())`
21
+ - **템플릿:** topbar에 "저장" 버튼 추가(`@if (canEdit())`), `<sd-form>` → `<sd-form #formCtrl (formSubmit)="onSubmit()">` 교체, 필드 `[readonly]="true"` → `[disabled]="!canEdit()"` 전환, 모든 입력 필드에 `(valueChange)="mark(data)"` 추가
22
+ - **_refresh 변경:** 말미에 `this._dataSnapshot = data.id == null ? undefined : obj.clone(this.data());` 추가
23
+
24
+ > 상세: [`<sd-form> #formCtrl / requestSubmit()`](../../ui-form/sd-form.md) · [`setupCanDeactivate`](../../utils/setup-functions.md#setupcandeactivate)
25
+
26
+ > **아래 코드 블록은 diff 조각이다.** 독립 실행 가능한 완성 클래스가 아니며, 최소 뼈대 위에 번호 순서대로 삽입·교체할 지점을 나타낸다. 그대로 컴파일되지 않는다.
27
+
28
+ ```typescript
29
+ // 1) imports 교체 — @angular/core에 {computed, viewChild} 추가, @simplysm/core-common에 {obj} 추가,
30
+ // @simplysm/angular에 {setupCanDeactivate, mark} 추가, 아이콘에 tablerDeviceFloppy 추가.
31
+ import { tablerAlertTriangle, tablerDeviceFloppy } from "@ng-icons/tabler-icons";
32
+ import {
33
+ ChangeDetectionStrategy, Component, computed, effect, inject, input, signal, untracked,
34
+ viewChild, ViewEncapsulation,
35
+ } from "@angular/core";
36
+ import { type DateTime, obj } from "@simplysm/core-common";
37
+ import {
38
+ FormatPipe, injectPermsSignal, injectViewTitleSignal, mark,
39
+ SdBusyContainer, SdButton, SdCommandDirective,
40
+ SdForm, SdTextfield, SdToastProvider, SdTopbar, SdTopbarContainer,
41
+ setupCanDeactivate,
42
+ } from "@simplysm/angular";
43
+
44
+ // 2) @Component — hostDirectives에 sdSaveCommand 추가, host에 (sdSaveCommand) 바인딩 추가.
45
+ @Component({
46
+ // ...selector/changeDetection/encapsulation/standalone/imports 동일
47
+ hostDirectives: [
48
+ { directive: SdCommandDirective, outputs: ["sdSaveCommand"] },
49
+ ],
50
+ host: {
51
+ "(sdSaveCommand)": "onSaveButtonClick()",
52
+ },
53
+ // template: 아래 6)에서 상세
54
+ })
55
+
56
+ // 3) 권한 키 확장 — ["use"] → ["use", "edit"]
57
+ perms = injectPermsSignal(["sales.customer"], ["use", "edit"]);
58
+
59
+ // 4) 파생·상태·viewChild 추가
60
+ protected readonly canEdit = computed(() => this.perms().includes("edit"));
61
+ protected readonly isNew = computed(() => this.data().id == null);
62
+ private _dataSnapshot?: ICustomer;
63
+
64
+ protected readonly formCtrl = viewChild<SdForm>("formCtrl");
65
+
66
+ // 5) 생성자에 setupCanDeactivate 추가 (기존 초기 effect 뒤)
67
+ constructor() {
68
+ // 기존 초기 effect 동일...
69
+ setupCanDeactivate(() => this._checkIgnoreChanges());
70
+ }
71
+
72
+ // 6) template — topbar에 "저장" 버튼 추가, <sd-form>을 <sd-form #formCtrl (formSubmit)>로 교체,
73
+ // 필드 [readonly]="true" → [disabled]="!canEdit()"로 전환, 입력 필드에 (valueChange)="mark(data)" 추가.
74
+ template: `
75
+ <sd-busy-container [busy]="busyCount() > 0">
76
+ @if (initialized()) {
77
+ @if (!perms().includes("use")) { <!-- 경고 동일 --> }
78
+ @else {
79
+ <sd-topbar-container>
80
+ <sd-topbar>
81
+ <h4>{{ viewTitle() }}</h4>
82
+ @if (canEdit()) {
83
+ <sd-button [theme]="'link-primary'" (click)="onSaveButtonClick()">
84
+ <ng-icon [svg]="tablerDeviceFloppy" /> 저장 <small>(CTRL+S)</small>
85
+ </sd-button>
86
+ }
87
+ </sd-topbar>
88
+
89
+ <div class="flex-column fill">
90
+ <sd-form #formCtrl (formSubmit)="onSubmit()" class="flex-fill">
91
+ <div class="p-default">
92
+ <table class="form-table">
93
+ <tbody>
94
+ <tr>
95
+ <th>명칭</th>
96
+ <td>
97
+ <sd-textfield
98
+ [type]="'text'" [required]="true"
99
+ [disabled]="!canEdit()" [(value)]="data().name"
100
+ (valueChange)="mark(data)"
101
+ />
102
+ </td>
103
+ </tr>
104
+ <tr>
105
+ <th>전화번호</th>
106
+ <td>
107
+ <sd-textfield
108
+ [type]="'text'" [disabled]="!canEdit()"
109
+ [(value)]="data().phone"
110
+ (valueChange)="mark(data)"
111
+ />
112
+ </td>
113
+ </tr>
114
+ </tbody>
115
+ </table>
116
+ </div>
117
+ </sd-form>
118
+ <!-- lastModified 블록 동일 (최소 뼈대 §3) -->
119
+ </div>
120
+ </sd-topbar-container>
121
+ }
122
+ }
123
+ </sd-busy-container>
124
+ `
125
+
126
+ // 7) 메서드 추가 — onSaveButtonClick / onSubmit / _checkIgnoreChanges
127
+ protected onSaveButtonClick(): void {
128
+ this.formCtrl()?.requestSubmit();
129
+ }
130
+
131
+ protected async onSubmit(): Promise<void> {
132
+ if (this.busyCount() > 0) return;
133
+ if (!this.perms().includes("edit")) return;
134
+
135
+ // 신규는 변경 체크 없이 저장. 기존 항목이면 snapshot 대비 변경 여부를 판정.
136
+ if (!this.isNew() && this._dataSnapshot != null && obj.equal(this.data(), this._dataSnapshot)) {
137
+ this._sdToast.info("변경사항이 없습니다.");
138
+ return;
139
+ }
140
+
141
+ this.busyCount.update((v) => v + 1);
142
+ await this._sdToast.try(async () => {
143
+ // 앱별 ORM upsert — 예:
144
+ // await this._appOrm.connectAsync(async (db) => {
145
+ // await db.customer()
146
+ // .where((c) => [expr.eq(c.id, this.data().id)])
147
+ // .upsert(() => ({ name: this.data().name, phone: this.data().phone }));
148
+ // });
149
+ // 검증 실패 시 throw 하면 sdToast.try가 포착하여 에러 토스트를 표시한다 (이후 흐름은 생략).
150
+
151
+ this._sdToast.success("저장되었습니다.");
152
+ await this._refresh();
153
+ });
154
+ this.busyCount.update((v) => v - 1);
155
+ }
156
+
157
+ private _checkIgnoreChanges(): boolean {
158
+ return (
159
+ this._dataSnapshot == null ||
160
+ obj.equal(this.data(), this._dataSnapshot) ||
161
+ confirm("변경사항이 있습니다. 무시하고 진행하시겠습니까?")
162
+ );
163
+ }
164
+
165
+ // 8) _refresh 변경 — 말미에 snapshot 갱신
166
+ }
167
+
168
+ // 9) _refresh 말미에 snapshot 갱신
169
+ private async _refresh(): Promise<void> {
170
+ // ... (최소 뼈대 동일 조회 로직)
171
+
172
+ this.data.set(data);
173
+ // 신규(id == null)면 snapshot을 비워 변경 체크를 항상 통과시키고 저장을 허용한다.
174
+ this._dataSnapshot = data.id == null ? undefined : obj.clone(this.data());
175
+ }
176
+
177
+ // 10) 아이콘 + mark 템플릿 참조 추가
178
+ protected readonly tablerDeviceFloppy = tablerDeviceFloppy;
179
+ protected readonly mark = mark;
180
+ ```
181
+
182
+ **포인트:**
183
+
184
+ - **`obj.equal`은 deep equal**(`packages/core-common/src/utils/obj.ts:172`)이므로 `data().field = "x"` 같은 field mutation도 snapshot 대비 **저장 판정에 자동 반영**된다. 입력 필드의 `(valueChange)="mark(data)"`는 OnPush 재렌더링·연계 computed 갱신을 위한 **통지** 용도이며, 값 비교(저장 감지)와는 별개다.
185
+ - **snapshot은 반드시 `obj.clone`으로 깊은 복제한다.** `this._dataSnapshot = this.data()` 같은 얕은 참조 대입은 `data().field = "x"` mutation을 snapshot까지 오염시켜 변경 감지가 실패한다. `_refresh()` 말미에서 `obj.clone`(`packages/core-common/src/utils/obj.ts:19`)으로 저장해야 비교가 정확히 동작한다.
186
+ - **`isNew = computed(() => data().id == null)`은 PK 형태에 주의한다.** 클라이언트 UUID 선할당·자연키·복합키처럼 신규 상태에서도 `id`가 존재하는 스키마에서는 이 판정이 깨진다. 해당 스키마는 `isNew`를 `signal<boolean>(false)`로 유지하고 `_refresh()` 내부에서 명시 세팅하며, snapshot 분기도 `this.isNew() ? undefined : obj.clone(this.data())`로 치환한다.
187
+ - **Ctrl+S 경로 통일:** `hostDirectives` → `(sdSaveCommand)="onSaveButtonClick()"` → `formCtrl()?.requestSubmit()` → `<sd-form (formSubmit)="onSubmit()">`. 버튼 클릭과 단축키가 완전히 동일한 경로로 수렴한다.
188
+ - **`setupCanDeactivate(() => this._checkIgnoreChanges())`** — 라우트 이탈 시 snapshot 대비 변경이 있으면 `confirm`으로 사용자 확인을 요청한다. [확장 C](./extension-c-modal-view.md)(modal 뷰)에서는 조건에 `viewType() === "modal" ||`를 추가하여 modal에서는 항상 이탈을 허용한다.
189
+ - **`_checkIgnoreChanges`는 `_dataSnapshot == null`을 true로 취급** — 신규(snapshot 없음)이거나 아직 로드 전이면 이탈을 즉시 허용한다. snapshot이 있어도 `obj.equal`로 값이 동일하면 허용.
190
+
191
+ > 공통 규칙(`mark` 오용 전반, `setupCanDeactivate` 호출 위치, `_sdSharedData.wait()` 조건, 시트 셀 `[inset]`/`[size]`, soft-delete 선택 기준)은 [레시피 공통 규칙](../_common-rules.md)을 참조한다.
@@ -0,0 +1,103 @@
1
+ ← [CRUD 상세폼 레시피 진입점](../crud-detail.md)
2
+
3
+ # 확장 B: 삭제/복구 토글
4
+
5
+ > **선행:** [확장 A: 편집/저장](./extension-a-edit-save.md)
6
+
7
+ 확장 A(편집/저장)를 전제로, 기존 레코드에 대한 **soft-delete 토글**(삭제/복구 버튼)을 추가한다. 도메인 타입에 `isDeleted: boolean` 필드를 추가하고, 삭제·복구 버튼은 최소 뼈대의 topbar에 `@if (!isNew() && canEdit())` 조건으로 배치한다. 뷰별 UI(modal 하단 바 / control 상단 바의 삭제 버튼)는 [확장 C](./extension-c-modal-view.md)/[확장 D](./extension-d-control-view.md)에서 추가로 처리한다.
8
+
9
+ > **적용 조건: DB Table에 `isDeleted` 컬럼이 있는 경우에만.** `isDeleted` 컬럼이 없는 테이블은 물리 삭제(row DELETE)로 처리하며, 이 확장을 사용하지 않는다. → [공통 규칙: 삭제 방식은 DB 스키마에 따라 결정한다](../_common-rules.md#삭제-방식은-db-스키마에-따라-결정한다)
10
+
11
+ **이 확장이 도입하는 요소:**
12
+
13
+ - **imports:** `tablerEraser`, `tablerRestore`
14
+ - **타입 확장:** `ICustomer.isDeleted: boolean` 필드 추가 + `data` 초기값·`_refresh` 빈 객체에 `isDeleted: false` 추가
15
+ - **메서드:** `onDeleteButtonClick`, `onRestoreButtonClick`, `_toggleDelete(del: boolean)`
16
+ - **템플릿:** topbar 내부(`canEdit()` 저장 버튼 뒤)에 `@if (!isNew() && canEdit())` 블록으로 삭제·복구 버튼 추가
17
+
18
+ > **아래 코드 블록은 diff 조각이다.** 독립 실행 가능한 완성 클래스가 아니며, 선행 확장(A) 위에 번호 순서대로 삽입할 지점을 나타낸다. 그대로 컴파일되지 않는다.
19
+
20
+ ```typescript
21
+ // 1) imports 추가
22
+ import {
23
+ tablerAlertTriangle, tablerDeviceFloppy, tablerEraser, tablerRestore,
24
+ } from "@ng-icons/tabler-icons";
25
+
26
+ // 2) ICustomer 확장 — isDeleted 필드 추가
27
+ interface ICustomer {
28
+ id: number | undefined;
29
+ name: string;
30
+ phone: string;
31
+ isDeleted: boolean; // ← 추가
32
+ lastModifiedAt: DateTime | undefined;
33
+ lastModifiedBy: string | undefined;
34
+ }
35
+
36
+ // 3) data 초기값·_refresh 빈 객체에 isDeleted: false 추가
37
+ protected readonly data = signal<ICustomer>({
38
+ id: undefined, name: "", phone: "",
39
+ isDeleted: false, // ← 추가
40
+ lastModifiedAt: undefined, lastModifiedBy: undefined,
41
+ });
42
+
43
+ // 4) template — topbar에 삭제·복구 버튼 추가 (저장 버튼 뒤)
44
+ template: `
45
+ <sd-topbar>
46
+ <h4>{{ viewTitle() }}</h4>
47
+ @if (canEdit()) {
48
+ <sd-button [theme]="'link-primary'" (click)="onSaveButtonClick()"> <!-- 저장 (확장 A) --> </sd-button>
49
+ }
50
+ @if (!isNew() && canEdit()) {
51
+ @if (data().isDeleted) {
52
+ <sd-button [theme]="'link-warning'" (click)="onRestoreButtonClick()">
53
+ <ng-icon [svg]="tablerRestore" /> 복구
54
+ </sd-button>
55
+ } @else {
56
+ <sd-button [theme]="'link-danger'" (click)="onDeleteButtonClick()">
57
+ <ng-icon [svg]="tablerEraser" /> 삭제
58
+ </sd-button>
59
+ }
60
+ }
61
+ </sd-topbar>
62
+ `
63
+
64
+ // 5) 메서드 추가
65
+ protected async onDeleteButtonClick(): Promise<void> {
66
+ await this._toggleDelete(true);
67
+ }
68
+
69
+ protected async onRestoreButtonClick(): Promise<void> {
70
+ await this._toggleDelete(false);
71
+ }
72
+
73
+ private async _toggleDelete(del: boolean): Promise<void> {
74
+ if (this.busyCount() > 0) return;
75
+ if (!this.perms().includes("edit")) return;
76
+ if (del && !confirm("삭제하시겠습니까?")) return;
77
+
78
+ this.busyCount.update((v) => v + 1);
79
+ await this._sdToast.try(async () => {
80
+ // 앱별 ORM delete/restore — 예:
81
+ // await this._appOrm.connectAsync(async (db) => {
82
+ // await db.customer.where(...).update({ isDeleted: del });
83
+ // });
84
+
85
+ this._sdToast.success(`${del ? "삭제" : "복구"}되었습니다.`);
86
+ await this._refresh();
87
+ });
88
+ this.busyCount.update((v) => v - 1);
89
+ }
90
+
91
+ // 6) 아이콘 추가
92
+ protected readonly tablerEraser = tablerEraser;
93
+ protected readonly tablerRestore = tablerRestore;
94
+ ```
95
+
96
+ **포인트:**
97
+
98
+ - **DB Table에 `isDeleted` 컬럼이 있는 경우의 삭제는 `isDeleted: true` 플래그 업데이트 soft-delete**로 구현한다 — 복구 기능·감사 이력·참조 무결성을 유지하기 위함. 서버는 `isDeleted: true` 레코드를 조회에서 기본 제외하고, 상세 폼에서는 복구 버튼으로 토글할 수 있다. `isDeleted` 컬럼이 없는 테이블은 이 확장을 사용하지 않고 물리 삭제(row DELETE)로 처리한다.
99
+ - **삭제 confirm은 `_toggleDelete`에서 `del === true`일 때만** 호출한다. 복구는 확인 없이 즉시 수행.
100
+ - **`isNew()` 신규 상태에서는 삭제·복구 버튼 노출 안 함** — DB에 저장되지 않은 레코드는 삭제 대상이 없으므로 `@if (!isNew() && canEdit())`로 감싼다.
101
+ - **모달/컨트롤 뷰의 삭제 버튼은 [확장 C](./extension-c-modal-view.md)/[확장 D](./extension-d-control-view.md)에서 별도 배치** — topbar가 없는 뷰에서는 이 확장만으로는 삭제 UI가 보이지 않는다. [확장 C](./extension-c-modal-view.md)(modal 하단 바) / [확장 D](./extension-d-control-view.md)(control 상단 바)에서 동일 `_toggleDelete` 메서드를 재사용하여 배치한다.
102
+
103
+ > 공통 규칙(삭제 방식 선택 기준, `mark` 오용, `setupCanDeactivate` 호출 위치 등)은 [레시피 공통 규칙](../_common-rules.md)을 참조한다.
@@ -0,0 +1,198 @@
1
+ ← [CRUD 상세폼 레시피 진입점](../crud-detail.md)
2
+
3
+ # 확장 C: modal 뷰
4
+
5
+ > **선행:** [확장 A: 편집/저장](./extension-a-edit-save.md) + [확장 B: 삭제/복구 토글](./extension-b-delete-restore.md)
6
+
7
+ 확장 A(편집/저장) + 확장 B(삭제/복구)를 전제로, 동일 컴포넌트를 **modal 뷰**로도 재사용한다. `SdModalProvider.showAsync`로 띄우면 `viewType() === "modal"`로 자동 판정되며, 기존 `<sd-topbar>`를 `@if (viewType() === "page") { ... }`로 래핑하여 page 뷰 전용으로 돌리고, 모달에는 하단 액션 바(확인·취소·삭제·복구)를 추가한다. 모달은 `implements SdModalContentDef<boolean | undefined>`로 계약을 부착하여 호출 측이 저장/닫기 결과를 받을 수 있다.
8
+
9
+ **이 확장이 도입하는 요소:**
10
+
11
+ - **imports:** `output`, `TemplateRef`, `injectViewTypeSignal`, `type SdModalContentDef`, `SdActivatedModalProvider`, `SdDockContainer`, `SdDock`
12
+ - **DI:** `_sdActivatedModal = inject(SdActivatedModalProvider, { optional: true })`
13
+ - **계약:** `implements SdModalContentDef<boolean | undefined>`, `close = output<boolean | undefined>()`, `actionTplRef?: TemplateRef<any>` (필드 선언만 — `SdModal`이 setter 프록시로 감쌈)
14
+ - **viewChild:** `_modalActionTpl = viewChild("modalActionTpl", { read: TemplateRef })`
15
+ - **생성자 effect:** `effect(() => { this.actionTplRef = this._modalActionTpl(); })` (모달 헤더 우측 액션 슬롯 브릿지)
16
+ - **파생:** `viewType = injectViewTypeSignal()` (`viewTitle`은 이미 `injectViewTitleSignal()`로 modal/page 자동 분기됨)
17
+ - **setupCanDeactivate 조건 변경:** `() => this.viewType() === "modal" || this._checkIgnoreChanges()` — modal에서는 항상 true(이탈 허용)
18
+ - **onSubmit / `_toggleDelete` 변경:** 성공 경로에 `this.close.emit(true)` 추가
19
+ - **템플릿 교체 1:** 기존 `<sd-topbar>...</sd-topbar>`를 `@if (viewType() === "page") { <sd-topbar>...</sd-topbar> }`로 **래핑** (page 뷰 전용으로 돌림)
20
+ - **템플릿:** topbar 내부 `<h4>{{ viewTitle() }}</h4>` 및 권한 경고의 viewTitle은 그대로 유지 (`injectViewTitleSignal()`이 modal/page 자동 분기)
21
+ - **템플릿 추가:** `<sd-topbar-container>` 내부 main 래퍼(`<div class="flex-column fill">`)를 `<sd-dock-container>`로 감싸고, 그 내부에 `@if (viewType() === "modal" && canEdit())` 블록으로 `<sd-dock [position]="'bottom'">` 하단 액션 바(확인·취소·삭제·복구) 추가
22
+ - **템플릿 추가:** `<sd-busy-container>` 바깥에 `<ng-template #modalActionTpl>`로 모달 우측 상단 액션 슬롯 정의 (필요 시 커스텀 액션 배치)
23
+
24
+ > 상세: [`SdModalContentDef` 구현 패턴](../../provider-types/sd-modal-content-def.md#구현-패턴)
25
+
26
+ > 상세: [`SdActivatedModalProvider` 사용법](../../providers/sd-activated-modal-provider.md#usage)
27
+
28
+ > 상세: [`injectViewTypeSignal`](../../utils/inject-routing-signals.md#injectviewtypesignal)
29
+
30
+ > 상세: [`<sd-dock> position="bottom"`](../../ui-layout/sd-dock.md)
31
+
32
+ > **아래 코드 블록은 diff 조각이다.** 독립 실행 가능한 완성 클래스가 아니며, 선행 확장(A+B) 위에 번호 순서대로 삽입·교체할 지점을 나타낸다. 그대로 컴파일되지 않는다.
33
+
34
+ ```typescript
35
+ // 1) imports 추가
36
+ import {
37
+ ChangeDetectionStrategy, Component, computed, effect, inject, input, output, signal,
38
+ TemplateRef, untracked, viewChild, ViewEncapsulation,
39
+ } from "@angular/core";
40
+ import {
41
+ FormatPipe, injectPermsSignal,
42
+ injectViewTitleSignal, injectViewTypeSignal,
43
+ SdActivatedModalProvider, SdAnchor,
44
+ SdBusyContainer, SdButton, SdCommandDirective,
45
+ SdDock, SdDockContainer,
46
+ SdForm, type SdModalContentDef,
47
+ SdTextfield, SdToastProvider,
48
+ SdTopbar, SdTopbarContainer, setupCanDeactivate,
49
+ } from "@simplysm/angular";
50
+
51
+ // 2) @Component imports 배열에 SdAnchor / SdDockContainer / SdDock 추가
52
+
53
+ // 3) DI 추가
54
+ private readonly _sdActivatedModal = inject(SdActivatedModalProvider, { optional: true });
55
+
56
+ // 4) 계약 부착 + viewChild + 파생 추가
57
+ export class CustomerDetail implements SdModalContentDef<boolean | undefined> {
58
+ //== SdModalContentDef 요구 필드 ==
59
+ close = output<boolean | undefined>();
60
+ // actionTplRef는 SdModal이 setter 프록시로 감싸므로 필드 선언만으로 충분
61
+ actionTplRef?: TemplateRef<any>;
62
+
63
+ //== viewChild (modal 우측 상단 액션 브릿지) ==
64
+ private readonly _modalActionTpl = viewChild("modalActionTpl", { read: TemplateRef });
65
+
66
+ //== 파생 ==
67
+ protected readonly viewType = injectViewTypeSignal();
68
+ // viewTitle은 최소 뼈대에서 이미 injectViewTitleSignal()로 선언됨 — modal/page 자동 분기
69
+
70
+ // ... (이하 기존)
71
+ }
72
+
73
+ // 5) 생성자 — setupCanDeactivate 조건 변경 + actionTplRef effect 추가
74
+ constructor() {
75
+ // 기존 초기 effect 동일...
76
+
77
+ // 변경: setupCanDeactivate 조건에 viewType() === "modal" || 추가
78
+ setupCanDeactivate(() => this.viewType() === "modal" || this._checkIgnoreChanges());
79
+
80
+ // 추가: 모달 헤더 우측 액션 슬롯 브릿지
81
+ effect(() => {
82
+ this.actionTplRef = this._modalActionTpl();
83
+ });
84
+ }
85
+
86
+ // 6) onSubmit / _toggleDelete 성공 경로에 close.emit(true) 추가
87
+ protected async onSubmit(): Promise<void> {
88
+ // ...
89
+ await this._sdToast.try(async () => {
90
+ // ... (ORM upsert)
91
+ this._sdToast.success("저장되었습니다.");
92
+ this.close.emit(true); // ← 추가 — modal 호출 측이 결과를 받는다
93
+ await this._refresh();
94
+ });
95
+ // ...
96
+ }
97
+
98
+ private async _toggleDelete(del: boolean): Promise<void> {
99
+ // ...
100
+ await this._sdToast.try(async () => {
101
+ // ... (ORM delete/restore)
102
+ this._sdToast.success(`${del ? "삭제" : "복구"}되었습니다.`);
103
+ this.close.emit(true); // ← 추가
104
+ });
105
+ // ...
106
+ }
107
+
108
+ // 7) template — 기존 <sd-topbar>를 @if (viewType() === "page")로 래핑,
109
+ // main 래퍼를 <sd-dock-container>로 감싸고
110
+ // modal 하단 바 + <ng-template #modalActionTpl> 추가
111
+ template: `
112
+ <sd-busy-container [busy]="busyCount() > 0">
113
+ @if (initialized()) {
114
+ @if (!perms().includes("use")) {
115
+ <!-- '{{ viewTitle() }}'에 대한 사용권한이 없습니다. -->
116
+ } @else {
117
+ <sd-topbar-container>
118
+ @if (viewType() === "page") {
119
+ <sd-topbar>
120
+ <h4>{{ viewTitle() }}</h4>
121
+ <!-- 저장/삭제/복구 (확장 A/B 동일) -->
122
+ </sd-topbar>
123
+ }
124
+
125
+ <sd-dock-container>
126
+ <!-- modal 하단 바: 확인/취소/삭제/복구 -->
127
+ @if (viewType() === "modal" && canEdit()) {
128
+ <sd-dock
129
+ [position]="'bottom'"
130
+ class="p-sm-default flex-row gap-sm bdt bdt-theme-gray-lightest"
131
+ >
132
+ @if (!isNew() && canEdit()) {
133
+ @if (data().isDeleted) {
134
+ <sd-button [size]="'sm'" [theme]="'warning'" (click)="onRestoreButtonClick()">
135
+ 복구
136
+ </sd-button>
137
+ } @else {
138
+ <sd-button [size]="'sm'" [theme]="'danger'" (click)="onDeleteButtonClick()">
139
+ 삭제
140
+ </sd-button>
141
+ }
142
+ }
143
+ <div class="flex-fill flex-row gap-sm main-align-end">
144
+ <sd-button [size]="'sm'" [theme]="'gray'" (click)="close.emit(undefined)">
145
+ 취소
146
+ </sd-button>
147
+ <sd-button [size]="'sm'" [theme]="'primary'" (click)="onSaveButtonClick()">
148
+ 확인
149
+ </sd-button>
150
+ </div>
151
+ </sd-dock>
152
+ }
153
+
154
+ <!-- main: form + 최종수정 (확장 A/B 동일) -->
155
+ <div class="flex-column fill"> ... </div>
156
+ </sd-dock-container>
157
+ </sd-topbar-container>
158
+ }
159
+ }
160
+ </sd-busy-container>
161
+
162
+ <!-- 모달 헤더 우측 액션 슬롯. 필요 시 커스텀 액션을 배치한다.
163
+ SdModalProvider가 contentComponent.actionTplRef를 setter 프록시로 감싸 SdModal로 브릿지한다.
164
+ (packages/angular/src/core/modal/sd-modal.provider.ts:140-150) -->
165
+ <ng-template #modalActionTpl>
166
+ <!-- 커스텀 액션이 필요하면 여기에 배치 -->
167
+ </ng-template>
168
+ `
169
+ ```
170
+
171
+ **포인트:**
172
+
173
+ - **modal 하단 바는 `[position]="'bottom'"` 반드시 명시**(`packages/angular/src/layout/dock/sd-dock.ts:97`). 기본값이 `"top"`이라 누락하면 상단에 쌓여 topbar 뒤에 겹친다.
174
+ - **`actionTplRef` setter 프록시는 modal 뷰에서만 동작한다.** `SdModalProvider`는 모달 컨텐츠 컴포넌트 생성 시 `if ("actionTplRef" in contentRef.instance)`로 확인한 뒤 setter 프록시를 설치한다(`sd-modal.provider.ts:141`). page/control 뷰에서는 프록시가 설치되지 않으므로 `this.actionTplRef = ...` 할당이 인스턴스 필드에만 저장되고 부작용이 없다.
175
+ - **`setupCanDeactivate`는 modal에서 true를 돌려 항상 이탈 허용**한다 — modal 자체에 취소 버튼이 있으므로 이중 confirm을 피한다. 페이지 뷰에서는 `_checkIgnoreChanges()`의 confirm 결과로 제어.
176
+ - **modal 취소 버튼은 `close.emit(undefined)`**. 호출 측은 `undefined`를 "취소"로, `true`를 "저장/삭제 성공"으로 해석한다. boolean 대신 사용자 정의 결과 타입이 필요하면 `close = output<FooResult | undefined>()` + `implements SdModalContentDef<FooResult | undefined>`로 변경하고, 호출 측은 `const result = await sdModal.showAsync({ type: CustomerDetail, ... })`로 받는다.
177
+ - **`viewTitle` 우선순위**: 모달 컴포넌트의 `title()` input이 세팅되어 있으면 그것을 우선, 없으면 라우트 기반 타이틀을 사용. `_sdSystemLog.writeAsync("warn", ...)`로 실패 시 로그 남김.
178
+ - **[확장 D](./extension-d-control-view.md)(control 뷰)와 병행 가능** — 두 분기 블록(`@if (viewType() === "modal")` / `@if (viewType() === "control")`)이 상호 배타이므로 같은 `<sd-dock-container>` 내부에 나란히 둘 수 있다.
179
+
180
+ ## 🚫 흔한 실수 (Anti-patterns)
181
+
182
+ > 공통 규칙(`mark` 오용, `setupCanDeactivate` / `injectViewTypeSignal()` 호출 위치, page 컴포넌트의 topbar 소유 등)은 [레시피 공통 규칙](../_common-rules.md)을 참조한다. 이 섹션은 **modal 뷰 확장 고유 실수**만 다룬다.
183
+
184
+ ### modal 하단 바의 `<sd-dock>`에 `[position]="'bottom'"`을 명시하지 않는다
185
+
186
+ ```typescript
187
+ // ✅ [position]="'bottom'"을 명시하여 <sd-dock-container> 하단에 고정
188
+ @if (viewType() === "modal" && canEdit()) {
189
+ <sd-dock
190
+ [position]="'bottom'"
191
+ class="p-sm-default flex-row gap-sm bdt bdt-theme-gray-lightest"
192
+ >
193
+ <!-- 확인/취소/삭제/복구 버튼 ... -->
194
+ </sd-dock>
195
+ }
196
+ ```
197
+
198
+ **근거**: `SdDock.position`의 기본값이 `"top"`이다(`packages/angular/src/layout/dock/sd-dock.ts:97`). 하단 액션 바 용도로 `<sd-dock>`을 도입할 때 `[position]`을 생략하면 `<sd-dock-container>`가 상단부터 dock을 쌓기 때문에 topbar 영역 뒤에 깔리고, topbar가 없는 modal 뷰에서도 "모달 하단 고정"이 깨진다. 타입 시스템은 기본값으로 통과시키므로 컴파일러·린터가 경고하지 않는다.
@@ -0,0 +1,109 @@
1
+ ← [CRUD 상세폼 레시피 진입점](../crud-detail.md)
2
+
3
+ # 확장 D: control 뷰
4
+
5
+ > **선행:** [확장 A: 편집/저장](./extension-a-edit-save.md) + [확장 B: 삭제/복구 토글](./extension-b-delete-restore.md)
6
+
7
+ 확장 A(편집/저장) + 확장 B(삭제/복구)를 전제로, 동일 컴포넌트를 **control 뷰**(마스터-디테일의 디테일 영역)로도 재사용한다. 마스터 화면이 `<app-customer-detail [itemId]="selectedId()" class="flex-fill">`처럼 컴포넌트 selector를 직접 삽입하면 `viewType() === "control"`로 자동 판정되어, 상단 바에 저장·삭제·복구 버튼이 가로로 배치된다. page 뷰의 topbar가 없고 modal 뷰의 하단 바가 없는 대신, main 영역 위에 `<sd-dock>` 상단 바가 놓인다.
8
+
9
+ **이 확장이 도입하는 요소:**
10
+
11
+ - **imports:** [확장 C](./extension-c-modal-view.md)에서 이미 도입된 `injectViewTypeSignal`, `SdDockContainer`, `SdDock`, `tablerDeviceFloppy`, `tablerEraser`, `tablerRestore`를 재사용. 확장 D를 확장 C 없이 단독 적용하는 경우 동일 imports를 신규 도입. `output` (`@angular/core`)
12
+ - **파생:** `viewType = injectViewTypeSignal()` (확장 C 없이 단독 적용 시 신규 도입)
13
+ - **output:** `submitted = output<boolean>()` — 저장/삭제/상태 변경 등 데이터 변경 성공 시 부모에게 목록 갱신을 알리는 신호. 확장 C의 `close` output(SdModalContentDef 계약, modal 닫기 결과)과는 **별개**
14
+ - **템플릿 추가:** [확장 C](./extension-c-modal-view.md)가 도입한 `<sd-dock-container>` 내부에 `@if (viewType() === "control" && canEdit())` 블록으로 `<sd-dock>` 상단 바(저장·삭제·복구) 추가 — 확장 C의 modal 하단 바 블록과 나란히 배치
15
+
16
+ > 상세: [`injectViewTypeSignal`](../../utils/inject-routing-signals.md#injectviewtypesignal)
17
+
18
+ > 상세: [`<sd-dock> position 기본 "top"`](../../ui-layout/sd-dock.md)
19
+
20
+ > **아래 코드 블록은 diff 조각이다.** 독립 실행 가능한 완성 클래스가 아니며, 선행 확장(A+B) 위에 번호 순서대로 삽입할 지점을 나타낸다. 그대로 컴파일되지 않는다.
21
+
22
+ ```typescript
23
+ // 1) imports 추가 (확장 D를 확장 C 없이 단독 적용하는 경우)
24
+ import { injectViewTypeSignal, SdDock, SdDockContainer } from "@simplysm/angular";
25
+
26
+ // 2) 파생 + output 추가 (확장 C 없이 단독 적용 시)
27
+ protected readonly viewType = injectViewTypeSignal();
28
+
29
+ // control 뷰에서 부모(마스터 시트)에게 "데이터 변경됨 → 목록 갱신 필요"를 알리는 output.
30
+ // 확장 C의 close output(SdModalContentDef 계약, modal 닫기 결과)과는 별개다.
31
+ submitted = output<boolean>();
32
+
33
+ // 3) onSubmit / _toggleDelete 성공 경로에 submitted.emit(true) 추가
34
+ protected async onSubmit(): Promise<void> {
35
+ // ... (ORM upsert)
36
+ this._sdToast.success("저장되었습니다.");
37
+ await this._refresh();
38
+ this.submitted.emit(true); // ← 추가
39
+ }
40
+
41
+ private async _toggleDelete(del: boolean): Promise<void> {
42
+ // ... (ORM delete/restore)
43
+ this._sdToast.success(`${del ? "삭제" : "복구"}되었습니다.`);
44
+ this.submitted.emit(true); // ← 추가
45
+ }
46
+
47
+ // 4) 부모(마스터 시트)에서의 사용
48
+ // <app-customer-detail [itemId]="selectedId()" (submitted)="headerSheet.doRefresh()" class="flex-fill" />
49
+
50
+ // 5) template — <sd-dock-container> 내부(확장 C 블록과 나란히)에 control 뷰 상단 바 추가
51
+ template: `
52
+ <sd-dock-container>
53
+ <!-- control 뷰 상단 바: 저장·삭제·복구 -->
54
+ @if (viewType() === "control" && canEdit()) {
55
+ <sd-dock class="p-default flex-row gap-default bdb bdb-theme-gray-lightest">
56
+ <sd-button [theme]="'primary'" (click)="onSaveButtonClick()">
57
+ <ng-icon [svg]="tablerDeviceFloppy" /> 저장 <small>(CTRL+S)</small>
58
+ </sd-button>
59
+ @if (!isNew() && canEdit()) {
60
+ @if (data().isDeleted) {
61
+ <sd-button [theme]="'warning'" (click)="onRestoreButtonClick()">
62
+ <ng-icon [svg]="tablerRestore" /> 복구
63
+ </sd-button>
64
+ } @else {
65
+ <sd-button [theme]="'danger'" (click)="onDeleteButtonClick()">
66
+ <ng-icon [svg]="tablerEraser" /> 삭제
67
+ </sd-button>
68
+ }
69
+ }
70
+ </sd-dock>
71
+ }
72
+
73
+ @if (viewType() === "modal" && canEdit()) {
74
+ <!-- modal 하단 바 (확장 C) -->
75
+ }
76
+
77
+ <!-- main: form + 최종수정 (확장 A/B 동일) -->
78
+ </sd-dock-container>
79
+ `
80
+ ```
81
+
82
+ **포인트:**
83
+
84
+ - **`submitted` output으로 부모 목록을 갱신한다.** 저장·삭제·복구·상태 변경 등 데이터 변경이 성공하면 `this.submitted.emit(true)`로 부모 마스터 시트에 목록 갱신을 알린다. 부모는 `(submitted)="headerSheet.doRefresh()"`로 수신한다. 확장 C의 `close` output(SdModalContentDef 계약, modal 닫기 결과 반환)과는 **별개**의 output이다.
85
+ - **control 뷰 = 마스터-디테일의 디테일 영역**. 마스터 화면이 `<app-customer-detail [itemId]="selectedId()" (submitted)="headerSheet.doRefresh()" class="flex-fill">`처럼 직접 삽입하여 좌측 리스트 선택에 따라 우측에 상세 폼을 표시한다. `injectViewTypeSignal()`은 `ActivatedRoute.component`의 selector와 호스트 `tagName`이 **다를 때** control로 판정한다(page는 일치, modal은 `SdActivatedModalProvider` 주입 시 우선).
86
+ - **상단 바는 `[position]` 생략** — `<sd-dock>`의 `[position]` 기본값이 `"top"`이므로 명시하지 않는다. modal 하단 바와 달리 기본 동작을 그대로 쓴다.
87
+ - **[확장 C](./extension-c-modal-view.md)(modal 뷰)와 병행 가능** — 두 분기 블록(`@if (viewType() === "control")` / `@if (viewType() === "modal")`)이 상호 배타이므로 같은 `<sd-dock-container>` 내부에 나란히 둬도 안전하다. [변형 확장 A~F 인덱스](../crud-detail.md#변형-확장-a-f-인덱스)가 이 조합을 수용한다.
88
+ - **control 뷰에서는 `setupCanDeactivate`가 아무 동작 하지 않는다** — 라우트 guard도 모달 canDeactivateFn도 연결되지 않는다(`packages/angular/src/core/routing/setupCanDeactivate.ts:10-26`). 마스터 화면이 이동할 때의 이탈 확인은 마스터 화면이 자체적으로 처리한다.
89
+
90
+ ## 🚫 흔한 실수 (Anti-patterns)
91
+
92
+ > 공통 규칙(topbar 소유권, `mark` 오용, `setupCanDeactivate` 호출 위치 등)은 [레시피 공통 규칙](../_common-rules.md)을 참조한다. 이 섹션은 **control 뷰 확장 고유 실수**만 다룬다.
93
+
94
+ ### control 뷰 분기에 자체 `<sd-topbar>`를 추가한다
95
+
96
+ ```typescript
97
+ // ✅ control 분기에는 <sd-dock> 상단 바만 둔다. page가 소유한 topbar와
98
+ // 중첩되지 않으며, 저장/삭제/복구 버튼을 가로로 배치한다.
99
+ @if (viewType() === "control" && canEdit()) {
100
+ <sd-dock class="p-default flex-row gap-default bdb bdb-theme-gray-lightest">
101
+ <sd-button [theme]="'primary'" (click)="onSaveButtonClick()">
102
+ <ng-icon [svg]="tablerDeviceFloppy" /> 저장 <small>(CTRL+S)</small>
103
+ </sd-button>
104
+ <!-- 삭제/복구는 @if (!isNew() && canEdit()) 블록으로 (확장 B 조건 재사용) -->
105
+ </sd-dock>
106
+ }
107
+ ```
108
+
109
+ **근거**: control 뷰는 마스터 화면이 `<app-customer-detail [itemId]="..." />`처럼 디테일 영역으로 직접 삽입하는 구조다. 마스터 화면은 이미 `<sd-topbar-container>`와 `<sd-topbar>`를 소유하므로, 내부 컴포넌트가 동일 구조를 중첩하면 타이틀·액션 영역이 이중으로 렌더링되어 레이아웃이 깨진다. control 분기의 상단 액션 영역은 `<sd-dock-container>` 내부 `<sd-dock>`으로 구성한다. → [공통 규칙: page 컴포넌트가 `<sd-topbar-container>`와 `<sd-topbar>`를 소유한다](../_common-rules.md#page-컴포넌트가-sd-topbar-container와-sd-topbar를-소유한다)