@simplysm/sd-claude 14.0.65 → 14.0.66

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 (227) hide show
  1. package/claude/references/sd-requirement-source-handling.md +64 -0
  2. package/claude/references/sd-simplysm14/README.md +44 -40
  3. package/claude/references/sd-simplysm14/apis/angular/README.md +95 -0
  4. package/claude/references/sd-simplysm14/apis/angular/app-structure.md +49 -0
  5. package/claude/references/sd-simplysm14/apis/angular/buttons.md +42 -0
  6. package/claude/references/sd-simplysm14/apis/angular/crud.md +35 -0
  7. package/claude/references/sd-simplysm14/apis/angular/forms.md +63 -0
  8. package/claude/references/sd-simplysm14/apis/angular/infrastructure.md +80 -0
  9. package/claude/references/sd-simplysm14/apis/angular/kanban.md +33 -0
  10. package/claude/references/sd-simplysm14/apis/angular/layout.md +41 -0
  11. package/claude/references/sd-simplysm14/apis/angular/modal.md +63 -0
  12. package/claude/references/sd-simplysm14/apis/angular/routing.md +45 -0
  13. package/claude/references/sd-simplysm14/apis/angular/select-dropdown.md +35 -0
  14. package/claude/references/sd-simplysm14/apis/angular/selection-managers.md +50 -0
  15. package/claude/references/sd-simplysm14/apis/angular/shared-data.md +42 -0
  16. package/claude/references/sd-simplysm14/apis/angular/sheet.md +52 -0
  17. package/claude/references/sd-simplysm14/apis/angular/toast.md +46 -0
  18. package/claude/references/sd-simplysm14/apis/angular/visual.md +41 -0
  19. package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +76 -0
  20. package/claude/references/sd-simplysm14/apis/capacitor-plugin-file-system/README.md +83 -0
  21. package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +80 -0
  22. package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +39 -0
  23. package/claude/references/sd-simplysm14/apis/core-browser/README.md +112 -0
  24. package/claude/references/sd-simplysm14/apis/core-common/README.md +53 -0
  25. package/claude/references/sd-simplysm14/apis/core-common/extensions.md +123 -0
  26. package/claude/references/sd-simplysm14/apis/core-common/features.md +46 -0
  27. package/claude/references/sd-simplysm14/apis/core-common/types.md +114 -0
  28. package/claude/references/sd-simplysm14/apis/core-common/utils.md +158 -0
  29. package/claude/references/sd-simplysm14/apis/core-node/README.md +12 -0
  30. package/claude/references/sd-simplysm14/apis/core-node/consola.md +64 -0
  31. package/claude/references/sd-simplysm14/apis/core-node/cpx.md +52 -0
  32. package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +53 -0
  33. package/claude/references/sd-simplysm14/apis/core-node/fsx.md +81 -0
  34. package/claude/references/sd-simplysm14/apis/core-node/pathx.md +55 -0
  35. package/claude/references/sd-simplysm14/apis/core-node/worker.md +111 -0
  36. package/claude/references/sd-simplysm14/apis/excel/README.md +81 -0
  37. package/claude/references/sd-simplysm14/apis/lint/README.md +80 -0
  38. package/claude/references/sd-simplysm14/apis/orm-common/README.md +33 -0
  39. package/claude/references/sd-simplysm14/apis/orm-common/db-context.md +77 -0
  40. package/claude/references/sd-simplysm14/apis/orm-common/executable.md +20 -0
  41. package/claude/references/sd-simplysm14/apis/orm-common/expr.md +92 -0
  42. package/claude/references/sd-simplysm14/apis/orm-common/queryable.md +98 -0
  43. package/claude/references/sd-simplysm14/apis/orm-common/schema-builders.md +128 -0
  44. package/claude/references/sd-simplysm14/apis/orm-node/README.md +59 -0
  45. package/claude/references/sd-simplysm14/apis/sd-claude/README.md +9 -0
  46. package/claude/references/sd-simplysm14/apis/sd-cli/README.md +50 -0
  47. package/claude/references/sd-simplysm14/apis/sd-cli/sd-config.md +155 -0
  48. package/claude/references/sd-simplysm14/apis/service-client/README.md +92 -0
  49. package/claude/references/sd-simplysm14/apis/service-common/README.md +29 -0
  50. package/claude/references/sd-simplysm14/apis/service-common/app-structure.md +63 -0
  51. package/claude/references/sd-simplysm14/apis/service-common/messages.md +56 -0
  52. package/claude/references/sd-simplysm14/apis/service-common/protocol.md +64 -0
  53. package/claude/references/sd-simplysm14/apis/service-common/service-types.md +43 -0
  54. package/claude/references/sd-simplysm14/apis/service-server/README.md +20 -0
  55. package/claude/references/sd-simplysm14/apis/service-server/auth.md +31 -0
  56. package/claude/references/sd-simplysm14/apis/service-server/builtin-services.md +47 -0
  57. package/claude/references/sd-simplysm14/apis/service-server/define-service.md +71 -0
  58. package/claude/references/sd-simplysm14/apis/service-server/internals.md +41 -0
  59. package/claude/references/sd-simplysm14/apis/service-server/server.md +66 -0
  60. package/claude/references/sd-simplysm14/apis/storage/README.md +69 -0
  61. package/claude/references/sd-simplysm14/{client-component.md → manuals/client-component.md} +133 -128
  62. package/claude/references/sd-simplysm14/manuals/client-crud.md +102 -0
  63. package/claude/references/sd-simplysm14/manuals/client-demo.md +128 -0
  64. package/claude/references/sd-simplysm14/manuals/client-rules.md +7 -0
  65. package/claude/references/sd-simplysm14/{client-setup.md → manuals/client-setup.md} +2 -2
  66. package/claude/references/sd-simplysm14/{client-tab.md → manuals/client-tab.md} +13 -11
  67. package/claude/references/sd-simplysm14/{orm-union.md → manuals/orm-union.md} +1 -1
  68. package/claude/references/sd-simplysm14/manuals/orm.md +75 -0
  69. package/claude/rules/sd-base-rules.md +172 -79
  70. package/claude/sd-check-bash.py +5 -0
  71. package/claude/sd-statusline.py +7 -12
  72. package/claude/skills/sd-commit/SKILL.md +7 -3
  73. package/claude/skills/sd-demo/SKILL.md +81 -62
  74. package/claude/skills/sd-demo/evals/fixtures/empty/.specs/260513120000_warehouse/spec.md +45 -0
  75. package/claude/skills/sd-demo/evals/fixtures/with-existing-screen/.specs/260513120000_warehouse/spec.md +42 -0
  76. package/claude/skills/sd-demo/evals/fixtures/with-existing-screen/packages/app/src/screens/dashboard/dashboard.view.ts +33 -0
  77. package/claude/skills/sd-demo/evals/fixtures/with-master-screen/.specs/260513120000_warehouse/spec.md +45 -0
  78. package/claude/skills/sd-demo/evals/fixtures/with-master-screen/packages/app/src/screens/dashboard/dashboard.view.ts +33 -0
  79. package/claude/skills/sd-demo/evals/fixtures/with-modal/.specs/260513120000_warehouse/spec.md +75 -0
  80. package/claude/skills/sd-demo/evals/fixtures/with-modal/packages/app/src/screens/dashboard/dashboard.view.ts +33 -0
  81. package/claude/skills/sd-demo/evals/fixtures/with-screens/.specs/260513120000_warehouse/spec.md +45 -0
  82. package/claude/skills/sd-demo/evals/fixtures/with-screens/packages/app/src/screens/dashboard/dashboard.view.ts +33 -0
  83. package/claude/skills/sd-demo/evals/golden.jsonl +5 -3
  84. package/claude/skills/sd-dev/SKILL.md +33 -63
  85. package/claude/skills/sd-dev/evals/fixtures/case-add/package.json +13 -0
  86. package/claude/skills/sd-dev/evals/fixtures/case-add/src/index.ts +10 -0
  87. package/claude/skills/sd-dev/evals/fixtures/case-add/tests/index.test.ts +11 -0
  88. package/claude/skills/sd-dev/evals/fixtures/case-add/tsconfig.json +12 -0
  89. package/claude/skills/sd-dev/evals/fixtures/case-bug/package.json +13 -0
  90. package/claude/skills/sd-dev/evals/fixtures/case-bug/src/index.ts +10 -0
  91. package/claude/skills/sd-dev/evals/fixtures/case-bug/tests/index.test.ts +11 -0
  92. package/claude/skills/sd-dev/evals/fixtures/case-bug/tsconfig.json +12 -0
  93. package/claude/skills/sd-dev/evals/fixtures/case-modify/package.json +13 -0
  94. package/claude/skills/sd-dev/evals/fixtures/case-modify/src/index.ts +10 -0
  95. package/claude/skills/sd-dev/evals/fixtures/case-modify/tests/index.test.ts +11 -0
  96. package/claude/skills/sd-dev/evals/fixtures/case-modify/tsconfig.json +12 -0
  97. package/claude/skills/sd-dev/evals/golden.jsonl +3 -3
  98. package/claude/skills/sd-docs/SKILL.md +53 -0
  99. package/claude/skills/sd-docs/evals/fixtures/new-write/.claude/references/sd-simplysm14/README.md +7 -0
  100. package/claude/skills/sd-docs/evals/fixtures/new-write/packages/bar/package.json +5 -0
  101. package/claude/skills/sd-docs/evals/fixtures/new-write/packages/bar/src/index.ts +3 -0
  102. package/claude/skills/sd-docs/evals/fixtures/new-write/packages/baz/package.json +6 -0
  103. package/claude/skills/sd-docs/evals/fixtures/new-write/packages/baz/src/index.ts +1 -0
  104. package/claude/skills/sd-docs/evals/fixtures/new-write/packages/foo/package.json +5 -0
  105. package/claude/skills/sd-docs/evals/fixtures/new-write/packages/foo/src/index.ts +8 -0
  106. package/claude/skills/sd-docs/evals/fixtures/update-mixed/.claude/references/sd-simplysm14/README.md +7 -0
  107. package/claude/skills/sd-docs/evals/fixtures/update-mixed/.claude/references/sd-simplysm14/apis/foo/README.md +3 -0
  108. package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/bar/package.json +5 -0
  109. package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/bar/src/index.ts +3 -0
  110. package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/baz/package.json +6 -0
  111. package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/baz/src/index.ts +1 -0
  112. package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/foo/package.json +5 -0
  113. package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/foo/src/index.ts +8 -0
  114. package/claude/skills/sd-docs/evals/golden.jsonl +2 -0
  115. package/claude/skills/sd-docs/references/subagent-prompt.md +100 -0
  116. package/claude/skills/sd-impl/SKILL.md +125 -46
  117. package/claude/skills/sd-impl/evals/fixtures/case-new/.specs/260514120000_/352/261/260/353/236/230/354/262/230/spec.md +101 -0
  118. package/claude/skills/sd-impl/evals/fixtures/case-update/.specs/260514120000_/352/261/260/353/236/230/354/262/230/spec.md +101 -0
  119. package/claude/skills/sd-impl/evals/fixtures/case-update/src//352/261/260/353/236/230/354/262/230//352/261/260/353/236/230/354/262/230-/353/252/250/353/215/270.txt +1 -0
  120. package/claude/skills/sd-impl/evals/fixtures/case-update/src//352/261/260/353/236/230/354/262/230//352/261/260/353/236/230/354/262/230-/353/252/251/353/241/235.txt +1 -0
  121. package/claude/skills/sd-impl/evals/golden.jsonl +2 -3
  122. package/claude/skills/sd-skill/SKILL.md +3 -3
  123. package/claude/skills/sd-skill/evals/golden.jsonl +0 -1
  124. package/claude/skills/sd-skill/references/eval-authoring.md +15 -0
  125. package/claude/skills/sd-skill/references/eval-run.md +1 -1
  126. package/claude/skills/sd-skill/references/skill-authoring.md +8 -5
  127. package/claude/skills/sd-skill/scripts/run_eval.py +39 -60
  128. package/claude/skills/sd-spec/SKILL.md +163 -105
  129. package/claude/skills/sd-spec/references/example-spec.md +585 -0
  130. package/claude/skills/sd-spec/references/spec-authoring.md +287 -0
  131. package/claude/skills/sd-spec/references/spec-md-template.md +15 -93
  132. package/claude/skills/sd-unpack/SKILL.md +7 -1
  133. package/claude/skills/sd-unpack/scripts/handlers/_common.py +10 -0
  134. package/claude/skills/sd-unpack/scripts/handlers/eml_handler.py +5 -13
  135. package/claude/skills/sd-unpack/scripts/handlers/msg_handler.py +3 -12
  136. package/claude/skills/sd-unpack/scripts/handlers/office_com.py +23 -37
  137. package/claude/skills/sd-unpack/scripts/handlers/office_worker.py +1 -4
  138. package/claude/skills/sd-unpack/scripts/handlers/pdf_handler.py +4 -13
  139. package/claude/skills/sd-unpack/scripts/unpack.py +4 -4
  140. package/claude/skills/sd-use/SKILL.md +1 -0
  141. package/claude/skills/sd-wip/SKILL.md +38 -0
  142. package/claude/skills/sd-wip/evals/fixtures/with-artifact/projects/acct/_wip.md +3 -0
  143. package/claude/skills/sd-wip/evals/fixtures/with-artifact/projects/acct/spec.md +15 -0
  144. package/claude/skills/sd-wip/evals/fixtures/with-existing-wip/.wips/260101120000_acct.md +6 -0
  145. package/claude/skills/sd-wip/evals/fixtures/with-existing-wip-for-compact/.wips/260101120000_acct.md +14 -0
  146. package/claude/skills/sd-wip/evals/golden.jsonl +4 -0
  147. package/claude/skills/sd-wip/references/compact.md +79 -0
  148. package/package.json +1 -1
  149. package/claude/references/sd-simplysm14/orm.md +0 -11
  150. package/claude/skills/sd-demo/evals/fixtures/basic-single-req/.specs/260503143025/REQ-001-/354/236/205/352/263/240/354/247/200/354/213/234/354/204/234/352/270/264/352/270/211/355/221/234/354/213/234/spec.md +0 -27
  151. package/claude/skills/sd-demo/evals/fixtures/basic-single-req/.specs/260503143025/overview.md +0 -12
  152. package/claude/skills/sd-demo/evals/fixtures/basic-single-req/src/components/Button.tsx +0 -12
  153. package/claude/skills/sd-demo/evals/fixtures/basic-single-req/src/components/Input.tsx +0 -27
  154. package/claude/skills/sd-demo/evals/fixtures/mock-data-policy/.specs/260503143025/REQ-001-/352/261/260/353/236/230/354/262/230/353/252/251/353/241/235/355/231/224/353/251/264/spec.md +0 -25
  155. package/claude/skills/sd-demo/evals/fixtures/mock-data-policy/.specs/260503143025/overview.md +0 -12
  156. package/claude/skills/sd-demo/evals/fixtures/mock-data-policy/src/components/Input.tsx +0 -25
  157. package/claude/skills/sd-demo/evals/fixtures/mock-data-policy/src/pages/.gitkeep +0 -0
  158. package/claude/skills/sd-demo/evals/fixtures/multi-req-domain/.specs/260503143025/REQ-001-RTP/354/236/205/353/240/245/355/231/224/353/251/264/spec.md +0 -19
  159. package/claude/skills/sd-demo/evals/fixtures/multi-req-domain/.specs/260503143025/REQ-002-RTP/354/266/234/353/240/245/355/231/224/353/251/264/spec.md +0 -20
  160. package/claude/skills/sd-demo/evals/fixtures/multi-req-domain/.specs/260503143025/overview.md +0 -16
  161. package/claude/skills/sd-demo/evals/fixtures/multi-req-domain/src/components/Button.tsx +0 -6
  162. package/claude/skills/sd-demo/evals/fixtures/multi-req-domain/src/components/Input.tsx +0 -15
  163. package/claude/skills/sd-demo/evals/fixtures/multi-req-domain/src/pages/.gitkeep +0 -0
  164. package/claude/skills/sd-demo/references/demo-md-template.md +0 -92
  165. package/claude/skills/sd-dev/evals/fixtures/multi-req-stop-after-spec/.docs/20260301_/352/263/240/352/260/235/354/202/254_/354/236/205/352/263/240/354/232/224/354/262/255.eml +0 -5
  166. package/claude/skills/sd-dev/evals/fixtures/multi-req-stop-after-spec/.docs/20260305_/352/263/240/352/260/235/354/202/254_/354/266/234/352/263/240/354/232/224/354/262/255.eml +0 -6
  167. package/claude/skills/sd-dev/evals/fixtures/multi-req-stop-after-spec/.docs/20260310_/352/263/240/352/260/235/354/202/254_/352/266/214/355/225/234.eml +0 -6
  168. package/claude/skills/sd-dev/evals/fixtures/single-req-auto-chain/src/lib/.gitkeep +0 -0
  169. package/claude/skills/sd-dev/evals/fixtures/start-from-plan/.specs/260503143025/REQ-001-/355/225/240/354/235/270/352/263/204/354/202/260/spec.md +0 -12
  170. package/claude/skills/sd-dev/evals/fixtures/start-from-plan/src/lib/.gitkeep +0 -0
  171. package/claude/skills/sd-impl/evals/fixtures/basic-single-r/.specs/260503143025/REQ-001-/354/236/205/352/263/240/354/247/200/354/213/234/354/204/234/352/270/264/352/270/211/355/221/234/354/213/234/plan.md +0 -28
  172. package/claude/skills/sd-impl/evals/fixtures/basic-single-r/.specs/260503143025/REQ-001-/354/236/205/352/263/240/354/247/200/354/213/234/354/204/234/352/270/264/352/270/211/355/221/234/354/213/234/spec.md +0 -14
  173. package/claude/skills/sd-impl/evals/fixtures/basic-single-r/src/components/Input.tsx +0 -6
  174. package/claude/skills/sd-impl/evals/fixtures/basic-single-r/src/pages/.gitkeep +0 -0
  175. package/claude/skills/sd-impl/evals/fixtures/multi-r/.specs/260503143025/REQ-001-/352/261/260/353/236/230/354/262/230/353/252/251/353/241/235/plan.md +0 -40
  176. package/claude/skills/sd-impl/evals/fixtures/multi-r/.specs/260503143025/REQ-001-/352/261/260/353/236/230/354/262/230/353/252/251/353/241/235/spec.md +0 -13
  177. package/claude/skills/sd-impl/evals/fixtures/multi-r/src/components/Input.tsx +0 -25
  178. package/claude/skills/sd-impl/evals/fixtures/multi-r/src/pages/.gitkeep +0 -0
  179. package/claude/skills/sd-impl/evals/fixtures/with-test-file/.specs/260503143025/REQ-001-/355/225/240/354/235/270/352/263/204/354/202/260/plan.md +0 -26
  180. package/claude/skills/sd-impl/evals/fixtures/with-test-file/.specs/260503143025/REQ-001-/355/225/240/354/235/270/352/263/204/354/202/260/spec.md +0 -12
  181. package/claude/skills/sd-impl/evals/fixtures/with-test-file/src/lib/.gitkeep +0 -0
  182. package/claude/skills/sd-impl/references/impl-md-template.md +0 -87
  183. package/claude/skills/sd-impl/references/modes-and-failure.md +0 -65
  184. package/claude/skills/sd-plan/SKILL.md +0 -130
  185. package/claude/skills/sd-plan/evals/fixtures/already-implemented/.specs/260503143025/REQ-001-/354/202/254/354/232/251/354/236/220/353/252/251/353/241/235/spec.md +0 -14
  186. package/claude/skills/sd-plan/evals/fixtures/already-implemented/src/api/user.ts +0 -13
  187. package/claude/skills/sd-plan/evals/fixtures/already-implemented/src/components/Input.tsx +0 -15
  188. package/claude/skills/sd-plan/evals/fixtures/already-implemented/src/pages/UserList.tsx +0 -29
  189. package/claude/skills/sd-plan/evals/fixtures/basic-greenfield/.specs/260503143025/REQ-001-/354/236/205/352/263/240/354/247/200/354/213/234/354/204/234/352/270/264/352/270/211/355/221/234/354/213/234/spec.md +0 -17
  190. package/claude/skills/sd-plan/evals/fixtures/basic-greenfield/src/components/Input.tsx +0 -6
  191. package/claude/skills/sd-plan/evals/fixtures/basic-greenfield/src/pages/.gitkeep +0 -0
  192. package/claude/skills/sd-plan/evals/fixtures/demo-built/.specs/260503143025/DEMO-001-/352/261/260/353/236/230/354/262/230/demo.md +0 -26
  193. package/claude/skills/sd-plan/evals/fixtures/demo-built/.specs/260503143025/REQ-001-/352/261/260/353/236/230/354/262/230/353/252/251/353/241/235/355/231/224/353/251/264/spec.md +0 -15
  194. package/claude/skills/sd-plan/evals/fixtures/demo-built/src/components/Input.tsx +0 -25
  195. package/claude/skills/sd-plan/evals/fixtures/demo-built/src/data/mock-customer.ts +0 -16
  196. package/claude/skills/sd-plan/evals/fixtures/demo-built/src/pages/CustomerList.tsx +0 -25
  197. package/claude/skills/sd-plan/evals/golden.jsonl +0 -3
  198. package/claude/skills/sd-plan/references/plan-md-template.md +0 -138
  199. package/claude/skills/sd-spec/evals/fixtures/bulk-multi-source/.docs/20260122_/352/263/240/352/260/235/354/202/254_/354/236/205/352/263/240/354/247/200/354/213/234/354/204/234/352/260/234/354/204/240/354/232/224/354/262/255.eml +0 -20
  200. package/claude/skills/sd-spec/evals/fixtures/bulk-multi-source/.docs/20260205_/352/263/240/352/260/235/354/202/254_/354/266/234/352/263/240/355/231/224/353/251/264/352/264/200/353/240/250.eml +0 -17
  201. package/claude/skills/sd-spec/evals/fixtures/bulk-multi-source/.docs/20260301_/352/263/240/352/260/235/354/202/254_/352/266/214/355/225/234/354/262/264/352/263/204.eml +0 -18
  202. package/claude/skills/sd-spec/evals/fixtures/conflict-detection/.docs/20260317_/352/263/240/352/260/235/354/202/254_/352/270/264/352/270/211/354/262/230/353/246/254/353/260/251/354/271/250.eml +0 -17
  203. package/claude/skills/sd-spec/evals/fixtures/conflict-detection/.docs//355/232/214/354/235/230/353/214/200/353/263/270_20260320.txt +0 -13
  204. package/claude/skills/sd-spec/evals/fixtures/direct-simple/.gitkeep +0 -0
  205. package/claude/skills/sd-spec/evals/fixtures/spec-md-input/.specs/260101120000/REQ-001-test/spec.md +0 -19
  206. package/claude/skills/sd-spec/evals/fixtures/spec-md-input/.specs/260101120000/overview.md +0 -18
  207. package/claude/skills/sd-spec/evals/golden.jsonl +0 -4
  208. package/claude/skills/sd-spec/references/overview-md-template.md +0 -89
  209. package/claude/skills/sd-spec/references/raw-input-handling.md +0 -96
  210. package/claude/skills/sd-verify/SKILL.md +0 -96
  211. package/claude/skills/sd-verify/evals/fixtures/all-satisfied/.specs/260503143025/REQ-001-/355/225/240/354/235/270/352/263/204/354/202/260/impl.md +0 -20
  212. package/claude/skills/sd-verify/evals/fixtures/all-satisfied/.specs/260503143025/REQ-001-/355/225/240/354/235/270/352/263/204/354/202/260/plan.md +0 -14
  213. package/claude/skills/sd-verify/evals/fixtures/all-satisfied/.specs/260503143025/REQ-001-/355/225/240/354/235/270/352/263/204/354/202/260/spec.md +0 -12
  214. package/claude/skills/sd-verify/evals/fixtures/all-satisfied/src/lib/discount.test.ts +0 -11
  215. package/claude/skills/sd-verify/evals/fixtures/all-satisfied/src/lib/discount.ts +0 -7
  216. package/claude/skills/sd-verify/evals/fixtures/partial-mismatch/.specs/260503143025/REQ-001-/354/202/254/354/232/251/354/236/220/353/252/251/353/241/235/impl.md +0 -21
  217. package/claude/skills/sd-verify/evals/fixtures/partial-mismatch/.specs/260503143025/REQ-001-/354/202/254/354/232/251/354/236/220/353/252/251/353/241/235/plan.md +0 -21
  218. package/claude/skills/sd-verify/evals/fixtures/partial-mismatch/.specs/260503143025/REQ-001-/354/202/254/354/232/251/354/236/220/353/252/251/353/241/235/spec.md +0 -12
  219. package/claude/skills/sd-verify/evals/fixtures/partial-mismatch/src/pages/UserList.tsx +0 -19
  220. package/claude/skills/sd-verify/evals/fixtures/tdd-only/.specs/260503143025/REQ-001-/352/270/210/354/225/241/352/262/200/354/246/235/impl.md +0 -19
  221. package/claude/skills/sd-verify/evals/fixtures/tdd-only/.specs/260503143025/REQ-001-/352/270/210/354/225/241/352/262/200/354/246/235/plan.md +0 -14
  222. package/claude/skills/sd-verify/evals/fixtures/tdd-only/.specs/260503143025/REQ-001-/352/270/210/354/225/241/352/262/200/354/246/235/spec.md +0 -12
  223. package/claude/skills/sd-verify/evals/fixtures/tdd-only/src/lib/validate-amount.test.ts +0 -10
  224. package/claude/skills/sd-verify/evals/fixtures/tdd-only/src/lib/validate-amount.ts +0 -7
  225. package/claude/skills/sd-verify/evals/golden.jsonl +0 -3
  226. package/claude/skills/sd-verify/references/verify-md-template.md +0 -99
  227. /package/claude/skills/{sd-demo/evals/fixtures/basic-single-req/src/pages → sd-wip/evals/fixtures/empty}/.gitkeep +0 -0
@@ -0,0 +1,71 @@
1
+ # @simplysm/service-server — define-service
2
+
3
+ 서비스 정의 + 인증 래퍼 + 컨텍스트. `ServiceServerOptions.services` 에 들어갈 단위를 만든다.
4
+
5
+ ## `defineService(name, factory) → ServiceDefinition<TMethods>`
6
+
7
+ ```ts
8
+ defineService<TMethods extends Record<string, (...args: any[]) => any>>(
9
+ name: string | string[], // 다중 이름 = alias (예: ["Orm", "SdOrmService"])
10
+ factory: (ctx: ServiceContext) => TMethods,
11
+ ): ServiceDefinition<TMethods>;
12
+ ```
13
+
14
+ `factory` 는 호출마다 실행돼 메서드 객체를 생성한다 (요청별 컨텍스트 캡처). `factory` 가 `auth(...)` 로 감싸져 있으면 서비스 수준 인증으로 승격된다.
15
+
16
+ ## `auth(...)` — 인증 래퍼
17
+
18
+ ```ts
19
+ auth(fn) // 로그인 필요
20
+ auth(["admin", "owner"], fn) // 해당 역할 중 하나 필요 (OR)
21
+ ```
22
+
23
+ - 서비스 수준: `defineService("X", auth((ctx) => ({ ... })))`
24
+ - 메서드 수준: 팩토리 안에서 메서드를 `auth(["admin"], () => result)` 로 감싼다. 메서드 권한이 있으면 서비스 권한을 **덮어쓴다**.
25
+ - `auth: false` 옵션 시 검증 스킵, `auth: undefined` 인데 auth 필요 서비스 등록 시 `listen()` throw, auth 설정됐는데 토큰 없거나 권한 부족 시 메서드 호출에서 throw (`로그인이 필요합니다.` / `권한이 부족합니다.`).
26
+
27
+ ## `ServiceContext<TAuthInfo>`
28
+
29
+ 서비스 메서드가 받는 요청별 컨텍스트.
30
+
31
+ ```ts
32
+ interface ServiceContext<TAuthInfo = unknown> {
33
+ server: ServiceServer<TAuthInfo>;
34
+ socket?: ServiceSocket; // WS 경로일 때만
35
+ http?: { clientName: string; authTokenPayload?: AuthTokenPayload<TAuthInfo> };
36
+ legacy?: { clientName?: string }; // V1 레거시 경로
37
+
38
+ get authInfo(): TAuthInfo | undefined; // payload.data
39
+ get clientName(): string | undefined; // socket → http → legacy 순. 위험문자(.. / \) throw
40
+ get clientPath(): string | undefined; // <rootPath>/www/<clientName>
41
+ getConfig<T>(section: string): Promise<T>; // root + clientPath 의 .config.json merge, 누락 시 throw
42
+ }
43
+ ```
44
+
45
+ ## `ServiceMethods<TDefinition>`
46
+
47
+ 클라이언트에서 메서드 시그니처만 공유하기 위한 추출 타입.
48
+
49
+ ```ts
50
+ export const UserService = defineService("User", (ctx) => ({ ... }));
51
+ export type UserServiceType = ServiceMethods<typeof UserService>;
52
+ // 클라이언트: client.getService<UserServiceType>("User")
53
+ ```
54
+
55
+ ## 보조
56
+
57
+ - `createServiceContext(server, socket?, http?, legacy?)` — 컨텍스트 직조 (커스텀 라우트용).
58
+ - `getServiceAuthPermissions(fn)` — `auth()` 가 함수에 심볼로 심어둔 권한 배열을 읽는다 (내부 executor 가 사용).
59
+
60
+ ## 예제
61
+
62
+ ```ts
63
+ const HealthService = defineService("Health", () => ({
64
+ check: () => ({ status: "ok" }),
65
+ }));
66
+
67
+ const UserService = defineService("User", auth((ctx) => ({
68
+ me: () => ctx.authInfo,
69
+ adminOnly: auth(["admin"], () => "ok"),
70
+ })));
71
+ ```
@@ -0,0 +1,41 @@
1
+ # @simplysm/service-server — internals
2
+
3
+ `ServiceServer.listen()` 이 자동으로 endpoint 에 연결하는 핸들러·소켓·프로토콜·레거시 유틸의 표면. 표준 부트스트랩에서는 호출할 필요가 없으며, 자체 Fastify 인스턴스에 라우트를 직접 부착하거나 비표준 전송을 만들 때만 직접 import 한다.
4
+
5
+ ## HTTP 핸들러
6
+
7
+ ### `handleHttpRequest(req, reply, jwtSecret, runMethod)`
8
+
9
+ `/api/:service/:method` 라우트 핸들러. `x-sd-client-name` 헤더 필수. `Authorization: Bearer <token>` 있으면 `verifyJwt` 검증 (실패 401). GET 은 `?json=<배열>`, POST 는 본문이 배열이어야 함. 결과를 `runMethod({serviceName, methodName, params, http})` 로 위임.
10
+
11
+ ### `handleUpload(req, reply, rootPath, jwtSecret)`
12
+
13
+ multipart 업로드. `Authorization` 필수 (없거나 검증 실패 시 401). 각 파일을 `<rootPath>/www/uploads/<uuid><ext>` 로 저장 후 `ServiceUploadResult[]` 반환. 중간 에러 시 이미 저장된 모든 파일 삭제 후 500.
14
+
15
+ ### `handleStaticFile(req, reply, rootPath, urlPath)`
16
+
17
+ `<rootPath>/www/<urlPath>` 정적 서빙. 경로 탐색 가드(`pathx.isChildPath`), 디렉토리 슬래시 리다이렉트 후 `index.html` 폴백, 숨김 파일(`.` 시작) 403, ENOENT 404, 기타 500.
18
+
19
+ ## WebSocket / 소켓
20
+
21
+ ### `createWebSocketHandler(runMethod, jwtSecret) → WebSocketHandler`
22
+
23
+ 여러 WS 연결 풀 관리. `addSocket(socket, clientId, clientName, req)` 으로 등록(기존 동일 `clientId` 강제 교체). 메시지를 디코드해 `runMethod` 로 RPC, `evt:add/remove/gets/emit`, `auth` 메시지를 처리. `emit(name, infoSelector, data)` 로 매칭 클라이언트 푸시(`ServiceServer.emitEvent` 의 백엔드).
24
+
25
+ ### `createServiceSocket(socket, clientId, clientName, connReq) → ServiceSocket`
26
+
27
+ 단일 WS 래퍼. 5초 ping/pong 헬스체크, `createServerProtocolWrapper` 로 메시지 인코딩, 이벤트 리스너 키 보관, `on("error"|"close"|"message")`.
28
+
29
+ ## 프로토콜
30
+
31
+ ### `createServerProtocolWrapper() → ServerProtocolWrapper`
32
+
33
+ `@simplysm/service-common` 의 프로토콜을 worker(`service-protocol.worker`) 에 위임할지 메인 스레드에서 처리할지 자동 분기. encode 는 body 에 `Uint8Array` 가 있으면 worker, decode 는 30KB 초과 시 worker. `dispose()` 로 메인 스레드 프로토콜 정리.
34
+
35
+ ## V1 레거시
36
+
37
+ ### `handleV1Connection(socket, optionsOrMethods, clientNameSetter?)`
38
+
39
+ `ver` 쿼리가 `"2"` 가 아닌 클라이언트를 처리. JSON 텍스트 프로토콜로 `{ uuid, command, params, clientName }` 수신. 사용자 `handlers` 가 `handled: true` 를 반환하면 그 결과, 아니면 `SdAutoUpdateService.getLastVersion` fallback, 그것도 아니면 `UPGRADE_REQUIRED` 에러 반환.
40
+
41
+ 타입: `V1Request`, `V1Response`, `V1AutoUpdateMethods`, `V1RequestHandler`, `V1RequestHandlerContext`, `V1RequestHandlerResult`, `V1ConnectionOptions`. `ServiceServerOptions.legacyV1Handlers` 에 `V1RequestHandler[]` 를 넘기면 `ServiceServer` 가 자동 사용.
@@ -0,0 +1,66 @@
1
+ # @simplysm/service-server — server
2
+
3
+ 서버 인스턴스 부트스트랩 표면. `ServiceServer` 가 Fastify · WebSocket · JWT · 정적/업로드/API 라우트를 일괄 등록하고 SIGINT/SIGTERM 정상 종료까지 처리한다.
4
+
5
+ ## `ServiceServerOptions`
6
+
7
+ ```ts
8
+ interface ServiceServerOptions {
9
+ rootPath: string; // 정적/업로드/설정 루트 (www, .config.json 기준)
10
+ port: number;
11
+ ssl?: { pfxBytes: Uint8Array; passphrase: string };
12
+ auth?: { jwtSecret: string } | false; // undefined: auth 미설정(=auth 서비스 등록 시 에러)
13
+ // false: 의도적 비활성화 (검사 스킵)
14
+ services: ServiceDefinition[]; // defineService 결과
15
+ legacyV1Handlers?: V1RequestHandler[]; // V1 클라이언트 fallback
16
+ }
17
+ ```
18
+
19
+ ## `createServiceServer<TAuthInfo>(opts) → ServiceServer<TAuthInfo>`
20
+
21
+ `new ServiceServer(opts)` 단순 래퍼. `TAuthInfo` 는 JWT `data` 페이로드 타입.
22
+
23
+ ## `ServiceServer<TAuthInfo>`
24
+
25
+ ```ts
26
+ class ServiceServer<TAuthInfo = unknown> extends EventEmitter<{ ready: void; close: void }> {
27
+ readonly fastify: FastifyInstance;
28
+ readonly options: ServiceServerOptions;
29
+ isOpen: boolean;
30
+
31
+ listen(): Promise<void>; // 플러그인 등록 + 0.0.0.0 listen + SIGINT/SIGTERM 훅
32
+ close(): Promise<void>; // 모든 WS close + fastify.close
33
+ signAuthToken(payload: AuthTokenPayload<TAuthInfo>): Promise<string>; // HS256, 12h
34
+ verifyAuthToken(token: string): Promise<AuthTokenPayload<TAuthInfo>>;
35
+ getEvent<TEventDef>(eventName): ServerEventProxy<TEventDef>;
36
+ emitEvent<TEventDef>(name, infoSelector, data): Promise<void>;
37
+ }
38
+ ```
39
+
40
+ `listen()` 동작 — 한 번 호출로 모두 등록: helmet/cors/multipart/websocket/@fastify/static, `POST /api/:service/:method` (HTTP RPC), `ALL /upload` (multipart 업로드), `GET /` 및 `GET /ws` (WebSocket: `?ver=2&clientId=&clientName=` 필요. ver 누락 시 V1 레거시 분기), 와일드카드 `/*` (`<rootPath>/www` 정적 파일).
41
+
42
+ `auth == null` 인데 `services` 중 하나라도 `authPermissions != null` 이면 `listen()` 에서 즉시 throw.
43
+
44
+ 이벤트 브로드캐스트 — 서버는 클라이언트가 `evt:add` 로 등록한 리스너만 안다. `getEvent(name).emit(infoSelector, data)` 로 매칭된 리스너에게만 푸시:
45
+
46
+ ```ts
47
+ server.getEvent<{ $info: { tenantId: string }; $data: { id: string } }>("orderCreated")
48
+ .emit((info) => info.tenantId === "T1", { id: "O-100" });
49
+ ```
50
+
51
+ ## `ServerEventProxy<TEventDef>`
52
+
53
+ `getEvent()` 가 반환하는 핸들. `emit(infoSelector, data)` 만 노출.
54
+
55
+ ## 최소 예제
56
+
57
+ ```ts
58
+ const server = createServiceServer<MyAuth>({
59
+ rootPath: process.cwd(),
60
+ port: 50080,
61
+ auth: { jwtSecret: env("JWT_SECRET")! },
62
+ services: [OrmService, AutoUpdateService, MyService],
63
+ });
64
+ server.on("ready", () => console.log("up"));
65
+ await server.listen();
66
+ ```
@@ -0,0 +1,69 @@
1
+ # @simplysm/storage
2
+
3
+ FTP/FTPS/SFTP 원격 스토리지에 동일 인터페이스(`StorageClient`)로 파일을 읽고/쓰는 Node 라이브러리.
4
+
5
+ ## 사용 트리거 인덱스
6
+
7
+ - **`StorageFactory.connect`** — FTP/FTPS/SFTP 어느 것이든 연결→작업→자동 종료를 한 번에 처리할 때 (권장 진입점).
8
+ - **`StorageClient`** — 콜백 안에서 사용하는 공통 파일 작업 인터페이스(mkdir/list/readFile/put/uploadDir/remove/rename/exists).
9
+ - **`FtpStorageClient` / `SftpStorageClient`** — 연결 생명주기를 직접 관리해야 할 때만 (장기 연결 풀 등). 그 외엔 `StorageFactory.connect` 사용.
10
+ - **`StorageConnConfig`, `StorageProtocol`, `FileInfo`** — 위 API 호출 시 타입.
11
+
12
+ ## StorageFactory.connect
13
+
14
+ ```ts
15
+ class StorageFactory {
16
+ static connect<R>(
17
+ type: StorageProtocol, // "ftp" | "ftps" | "sftp"
18
+ config: StorageConnConfig,
19
+ fn: (storage: StorageClient) => R | Promise<R>,
20
+ ): Promise<R>;
21
+ }
22
+ ```
23
+
24
+ - `type` 에 따라 `FtpStorageClient(secure=false)` / `FtpStorageClient(secure=true)` / `SftpStorageClient` 생성.
25
+ - 콜백 실행 전 `connect`, 콜백 종료(예외 포함) 후 `close` 자동 호출. `close` 실패는 무시.
26
+
27
+ ```ts
28
+ const list = await StorageFactory.connect("sftp", { host, user, password }, async (s) => {
29
+ await s.mkdir("/up/2026");
30
+ await s.put(Buffer.from("hi"), "/up/2026/a.txt");
31
+ return await s.list("/up/2026");
32
+ });
33
+ ```
34
+
35
+ ## StorageClient
36
+
37
+ ```ts
38
+ interface StorageClient {
39
+ connect(config: StorageConnConfig): Promise<void>;
40
+ mkdir(dirPath: string): Promise<void>; // 부모 디렉토리 자동 생성
41
+ rename(fromPath: string, toPath: string): Promise<void>;
42
+ list(dirPath: string): Promise<FileInfo[]>; // { name, isFile }
43
+ readFile(filePath: string): Promise<Bytes>; // Bytes = Uint8Array (@simplysm/core-common)
44
+ exists(filePath: string): Promise<boolean>; // 모든 예외 시 false
45
+ put(localPathOrBuffer: string | Bytes, storageFilePath: string): Promise<void>; // string=로컬 경로, Bytes=메모리 버퍼
46
+ uploadDir(fromPath: string, toPath: string): Promise<void>; // 디렉토리 통째로 업로드
47
+ remove(filePath: string): Promise<void>;
48
+ close(): Promise<void>; // 이미 종료돼 있어도 안전
49
+ }
50
+
51
+ interface StorageConnConfig { host: string; port?: number; user?: string; password?: string; }
52
+ interface FileInfo { name: string; isFile: boolean; }
53
+ type StorageProtocol = "ftp" | "ftps" | "sftp";
54
+ ```
55
+
56
+ ## FtpStorageClient / SftpStorageClient
57
+
58
+ `StorageClient` 구현체. 직접 사용 시 주의:
59
+
60
+ - `connect` 후 반드시 `close`. 동일 인스턴스에서 `connect` 중복 호출 금지(연결 누수 → `SdError` throw). `close` 후 재연결은 가능.
61
+ - `FtpStorageClient(secure: boolean)` — `secure=true` 가 FTPS.
62
+ - `SftpStorageClient` — `config.password` 가 있으면 비밀번호 인증. 없으면 `~/.ssh/id_ed25519` 키 + (있으면) `SSH_AUTH_SOCK` agent 로 시도, 키 파싱 실패 시 agent 단독 재시도.
63
+ - `FtpStorageClient.exists` — 파일은 `size()` 로 O(1), 디렉토리는 부모 `list()` 스캔. 슬래시 없는 경로는 `/` 기준.
64
+
65
+ ```ts
66
+ const client = new SftpStorageClient();
67
+ await client.connect({ host, user, password });
68
+ try { /* ... */ } finally { await client.close(); }
69
+ ```
@@ -1,14 +1,12 @@
1
1
  # 클라이언트 화면 작성 매뉴얼
2
2
 
3
- 이 문서는 `@simplysm/angular` v14 패키지로 화면을 만들 때 따르는 컨벤션을 모은다.
4
-
5
3
  ## 파일명·역할·위치
6
4
 
7
5
  화면 파일은 `<domain>.<역할>.ts` 형식, 역할 접미사로 책임을 표시한다.
8
6
 
9
7
  | 파일명 형식 | 역할 |
10
8
  | ---------------------------- | ------------------------------------------------------------------------- |
11
- | `<domain>.view.ts` | list/detail 로 깔끔히 나뉘지 않는 화면. 보통 list/detail 의 orchestrator. |
9
+ | `<domain>.view.ts` | list/detail 로 깔끔히 나뉘지 않는 화면. (예시: list/detail 의 orchestrator) |
12
10
  | `<domain>.list.ts` | 목록. `sd-crud-list` 사용. |
13
11
  | `<domain>.detail.ts` | 단건 보기/편집. `sd-crud-detail` 사용. |
14
12
  | `<domain>.modal.ts` | 모달 전용 화면. |
@@ -65,6 +63,8 @@ Angular 기본과 다른 부분만 명시:
65
63
 
66
64
  화면이 list 또는 detail 하나로 끝나면 view 를 만들지 않고 list/detail 자체가 라우팅 진입 단위가 된다.
67
65
 
66
+ ### list + detail 합성
67
+
68
68
  view 의 합성 패턴 (예: `outbound-instruction.view.ts`):
69
69
 
70
70
  ```html
@@ -74,7 +74,7 @@ view 의 합성 패턴 (예: `outbound-instruction.view.ts`):
74
74
  <app-outbound-instruction-list #headerSheet selectMode="single" class="flex-min" />
75
75
 
76
76
  @let _selectedId = headerSheet.selectedKeys().first(); @if (_selectedId == null) {
77
- <div class="flex-fill ...">선택하세요.</div>
77
+ <div class="flex-fill p-default">선택하세요.</div>
78
78
  } @else {
79
79
  <app-outbound-instruction-detail
80
80
  class="flex-fill"
@@ -93,9 +93,40 @@ view 의 합성 패턴 (예: `outbound-instruction.view.ts`):
93
93
  - detail 의 단건 변경·삭제는 list 가 표시하는 같은 데이터에 반영돼야 하므로, detail 의 `submitted` → list 의 `doRefresh()` 호출로 동기화한다.
94
94
  - view 는 `sd-base-container` 를 루트로 두고, 내부 컨텐츠는 `#contentTpl` 슬롯에 둔다.
95
95
 
96
+ ### list + list 합성 (마스터-라인)
97
+
98
+ 좌 list 가 마스터(헤더), 우 list 가 디테일(라인) 역할로 합성:
99
+
100
+ ```html
101
+ <sd-base-container [(ready)]="ready" [initialized]="initialized()" [(busyCount)]="busyCount" ...>
102
+ <ng-template #contentTpl>
103
+ <div class="flex-row fill">
104
+ <app-master-list #headerSheet selectMode="single" class="flex-min" />
105
+
106
+ @let _selectedId = headerSheet.selectedKeys().first();
107
+ @if (_selectedId == null) {
108
+ <div class="flex-fill p-default">선택하세요.</div>
109
+ } @else {
110
+ <app-line-list
111
+ class="flex-fill"
112
+ [headerId]="_selectedId"
113
+ (submitted)="headerSheet.doRefresh()"
114
+ />
115
+ }
116
+ </div>
117
+ </ng-template>
118
+ </sd-base-container>
119
+ ```
120
+
121
+ 핵심 약속:
122
+
123
+ - 우 list 는 좌의 선택 키를 `input` 으로 받아 자동 재조회. (외부 input → filter 머지 패턴은 §"외부 input 을 filter 에 반영" 참조.)
124
+ - 우 list 의 저장·삭제 후 좌 헤더 목록까지 갱신해야 하면 우 list 가 `submitted` output 을 emit, view 가 받아 `#headerSheet.doRefresh()` 호출.
125
+ - 우 list 안에 추가 분기(탭 등)는 [client-tab.md](./client-tab.md) 매뉴얼 채택.
126
+
96
127
  ## 화면 컴포넌트의 표준 시그널
97
128
 
98
- 화면 컴포넌트(view/list/detail/modal) 가 공통으로 쓰는 시그널 5종. **필요한 것만 채택**하되, 채택할 때는 아래 약속된 이름·의미·전파를 그대로 따른다.
129
+ 화면 컴포넌트(view/list/detail/modal) 가 공통으로 쓰는 시그널 4종. **필요한 것만 채택**하되, 채택할 때는 아래 약속된 이름·의미·전파를 그대로 따른다.
99
130
 
100
131
  | 이름 | 종류 | 의미 |
101
132
  | ------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
@@ -103,16 +134,9 @@ view 의 합성 패턴 (예: `outbound-instruction.view.ts`):
103
134
  | `initialized` | `signal(false)` | 첫 데이터 로드까지 끝났는지. 자식이 자기 로드 끝낸 뒤 set true. |
104
135
  | `busyCount` | `signal(0)` | 진행 중인 비동기 작업 수. 시작 시 `+1`, 끝나면 `-1`. > 0 이면 화면이 busy 표시. |
105
136
  | `viewType` | `injectViewTypeSignal()` | 화면이 page / control / modal 어느 컨텍스트에서 동작 중인지. 라우팅 진입이면 `'page'`, view 자식이면 `'control'`, 모달이면 `'modal'`. |
106
- | `restricted` | `computed` | 권한 부족 등으로 컴포넌트를 비활성화할지. 보통 `!perms().includes('use')`. |
107
137
 
108
138
  **전파**: 부모가 자식에게 위 시그널들을 그대로 흘려보낸다. `sd-base-container` / `sd-crud-list` / `sd-crud-detail` 가 이들을 입력으로 받는 표준.
109
139
 
110
- **`viewType` 결정**:
111
-
112
- - **라우팅 진입 단위**(view 또는 단독 list/detail) — `viewType = injectViewTypeSignal()` 로 받음.
113
- - **view 의 자식**(view 안에 임베드되는 list/detail) — 보통 `'control'` 고정값으로 전달.
114
- - **모달 진입** — modal 컴포넌트의 `viewType` 은 `'modal'` 로 자동 주입됨.
115
-
116
140
  **`busyCount` 사용 패턴**:
117
141
 
118
142
  ```ts
@@ -141,7 +165,7 @@ perms = injectPermsSignal(
141
165
 
142
166
  - 단순한 권한 체크는 `this.perms().includes("use")` 를 템플릿·코드에 인라인으로 쓴다. 별도 computed 로 묶지 않는다.
143
167
  - `restricted` 입력은 `[restricted]="!perms().includes('use')"` 형태로 인라인 전달. (별도 `canUse` / `restricted` computed 만들지 않는다.)
144
- - 권한 체크 뒤에 추가 조건(데이터 상태 등)이 결합되어 **빈번히 쓰일 때만** computed 로 묶는다.
168
+ - 권한 체크 뒤에 추가 조건(데이터 상태 등)이 결합되어 **같은 결합이 2회 이상 참조될 때만** computed 로 묶는다.
145
169
 
146
170
  ```ts
147
171
  canEdit = computed(() => this.perms().includes("edit") && this.data().state === "작성");
@@ -224,7 +248,7 @@ if (!result) return;
224
248
  - **`type`** — `SdModal` 을 구현(상속)한 컴포넌트 클래스.
225
249
  - **`title`** — 모달 헤더 제목.
226
250
  - **`inputs`** — 모달 컴포넌트가 받을 input 시그널 값. 없으면 `{}`.
227
- - **반환값** — 모달 컴포넌트가 close 시 emit 한 페이로드. 사용자가 X/취소로 닫으면 `undefined`.
251
+ - **반환값** — 모달 컴포넌트가 close 시 emit 한 페이로드. 사용자가 닫기(X)/취소로 닫으면 `undefined`.
228
252
 
229
253
  ## `mark` 헬퍼
230
254
 
@@ -329,6 +353,25 @@ private async _refresh(): Promise<void> {
329
353
 
330
354
  `_search` 는 ORM 쿼리 실행. 자세한 사용은 [orm.md](./orm.md) 참조.
331
355
 
356
+ ### 페이지네이션
357
+
358
+ 두 패턴 중 택일. 데이터 규모와 검색·정렬 책임에 따라 화면 작성자가 판단(명확한 컷오프 없음).
359
+
360
+ **서버 페이징** — 한 페이지 분량만 매번 서버에서 가져옴. 위 §시그널 구성/effect/`_refresh` 가 가정한 디폴트 패턴.
361
+
362
+ - `pageLength` 시그널을 두고 `_refresh` 에서 서버 응답의 총 페이지 수로 set.
363
+ - `page` / `sortingDefs` / `lastFilter` 모두 effect 의존성. 변경 시 재조회.
364
+ - `<sd-crud-list>` 에 `[totalPageCount]="pageLength()"` 전달. `[itemsPerPage]` 는 생략(=0).
365
+
366
+ **클라이언트 페이징** — 전체 데이터를 한 번에 로드, 시트가 자체 slice/sort.
367
+
368
+ - `pageLength` 시그널·`sortingDefs` effect 의존성 불필요. (정렬은 시트 내부에서 처리.)
369
+ - `_refresh` 는 전체 아이템을 한 번에 `items.set(all)`.
370
+ - `<sd-crud-list>` 에 `[itemsPerPage]="<페이지당 행 수>"` 전달. `[totalPageCount]` 는 생략(=0).
371
+ - `[(sorts)]` 는 화면이 정렬 상태를 들고 있어야 할 때만 묶음. 아니면 생략.
372
+
373
+ **`[visiblePageCount]`** (기본 10) — 페이지네이터가 한 번에 표시하는 페이지 번호 개수. 두 패턴 모두 사용자가 명시 지시한 경우에만 명시.
374
+
332
375
  ### 외부 input 을 filter 에 반영
333
376
 
334
377
  list 가 다른 화면 안에 임베드되어 외부에서 filter 의 일부를 input 으로 받을 때, effect 로 input → filter → lastFilter 흐름을 만든다.
@@ -466,17 +509,23 @@ async onSubmit(): Promise<void> {
466
509
  ## 시트 컬럼·셀 표준
467
510
 
468
511
  ```html
469
- <sd-sheet-column [key]="'name'" [header]="'이름'" [width]="'160px'">
512
+ <sd-sheet-column [key]="'name'" [header]="'이름'">
470
513
  <ng-template [cell]="items()" let-item="item">
471
514
  <div class="p-xs-sm">{{ item.name }}</div>
472
515
  </ng-template>
473
516
  </sd-sheet-column>
474
517
  ```
475
518
 
519
+ **폭 약속**:
520
+
521
+ - `[width]` 는 **명시하지 않음이 기본** — 자동. px 지정은 사용자가 명시 지시한 경우에만.
522
+ - 영역 폭(`flex-min` 의 `style="width: ..."` 등) 도 동일.
523
+
476
524
  **셀 본문 약속**:
477
525
 
478
526
  - 시트 셀에는 패딩이 없으므로 본문 div 에 `p-xs-sm` 클래스를 붙이는 게 기본.
479
- - 정렬은 `tx-right` (숫자), `tx-center` (라벨/아이콘), 기본 left.
527
+ - 정렬 클래스(`tx-right` / `tx-center` / `tx-left`) **사용자가 명시 지시한 경우에만** 사용. 기본은 미지정(브라우저 기본 left). "라벨은 가운데" 같은 자동 휴리스틱 적용 금지.
528
+ - 단, **숫자 셀은 `tx-right` 기본 적용** (수량·금액·단가·합계 등 숫자값 컬럼).
480
529
  - `[cell]="items()"` 는 타입 추론용 더미 — 실제 행 데이터는 `<sd-sheet>` 의 `[items]` 가 들고 있다.
481
530
  - 셀 컨텍스트: `let-item="item"` / `let-index="index"` / `let-depth="depth"` / `let-edit="edit"`.
482
531
 
@@ -492,21 +541,47 @@ async onSubmit(): Promise<void> {
492
541
  </sd-crud-list>
493
542
  ```
494
543
 
544
+ ### 요약 행
545
+
546
+ 컬럼에 `<ng-template #summaryTpl>` 을 두면 시트의 헤더 영역 하단(`thead` 내부)에 요약 행이 렌더된다. 스크롤 시 헤더와 함께 상단 고정되며, 배경은 warning 계열로 자동 강조된다.
547
+
548
+ ```html
549
+ <sd-sheet-column [key]="'quantity'" [header]="'수량'">
550
+ <ng-template #summaryTpl>
551
+ <div class="p-xs-sm tx-right">{{ totalQuantity() }}</div>
552
+ </ng-template>
553
+ <ng-template [cell]="items()" let-item="item">
554
+ <div class="p-xs-sm tx-right">{{ item.quantity }}</div>
555
+ </ng-template>
556
+ </sd-sheet-column>
557
+ ```
558
+
559
+ - 컬럼 중 하나라도 `#summaryTpl` 을 가지면 요약 행 전체가 활성화된다. 정의 없는 컬럼은 빈 셀.
560
+ - 셀 본문 약속(`p-xs-sm`, 정렬 클래스 등)은 요약 셀에도 동일하게 적용.
561
+ - 합계·평균 등 집계 값은 시트가 계산해주지 않는다. 화면 컴포넌트에서 `computed` 로 직접 만들어 노출한다.
562
+
563
+ ```ts
564
+ totalQuantity = computed(() => this.items().sum((i) => i.quantity) ?? 0);
565
+ ```
566
+
495
567
  ## 폼·입력 컨트롤
496
568
 
497
569
  ### 폼 항목 레이아웃
498
570
 
499
- 여러 입력 항목을 가로로 나열할 `form-box-inline` 컨테이너 + 항목별 `form-box-item`:
571
+ label + 입력 그룹을 묶는 전용 클래스 3종:
572
+
573
+ - `form-box` — 세로 스택. `> div` 또는 `> div` 안에 `<label>` + 입력. 항목 사이 `gap-default`.
574
+ - `form-box-inline` — 가로 인라인 flex (wrap). 라벨이 입력 옆에 붙음. 검색·필터폼에 사용. 라벨 없는 `form-box-item` 도 허용 (버튼 등).
575
+ - `form-table` — `<table>` 기반. `<th>` 가 우측 정렬 라벨, `<td>` 가 입력. `<th class="form-table-header">` 는 섹션 헤더(좌측 정렬, 회색, 위쪽 여백 큼). 라벨/입력 폭을 정렬해야 하는 등록·편집 폼에 사용.
500
576
 
501
577
  ```html
502
578
  <div class="form-box-inline">
503
- <div class="form-box-item">
504
- <label>이름</label>
505
- <sd-textfield [(value)]="data().name" (valueChange)="mark(data)" />
579
+ <div>
580
+ <label>기준 일자</label>
581
+ <sd-modal-select-button [(value)]="baseDate" ...>{{ baseDate() ?? "선택" }}</sd-modal-select-button>
506
582
  </div>
507
- <div class="form-box-item">
508
- <label>등록일</label>
509
- <sd-textfield [type]="'date'" [(value)]="data().createdAt" (valueChange)="mark(data)" />
583
+ <div>
584
+ <sd-button [theme]="'primary'" (click)="onCompareButtonClick()">비교</sd-button>
510
585
  </div>
511
586
  </div>
512
587
  ```
@@ -531,6 +606,16 @@ async onSubmit(): Promise<void> {
531
606
  | 라벨/배지 | `<sd-label [theme]>` |
532
607
  | 버튼/액션 | `<sd-button>`, `<sd-anchor>` |
533
608
 
609
+ ### 버튼 스타일
610
+
611
+ 화면 액션 `<sd-button>` 은 역할별로 `theme`·`size` 를 구분.
612
+
613
+ | 역할 | `[theme]` | `[size]` |
614
+ | ------------------------------------------------------------- | --------------------------------------------------------------------------- | -------- |
615
+ | 데이터 자체를 통으로 변경하는 최상위 액션 (저장·삭제·생성 등) | 일반 시리즈 (`primary` / `danger` / `success` / `warning` 등 의미에 맞춰) | 기본 |
616
+ | 위 액션 옆 유틸리티 버튼 (양식 다운로드·인쇄 등) | link 시리즈 (`link-primary` 등) | 기본 |
617
+ | 시트 위(또는 시트 셀 안)에 나열되는 버튼 | link 시리즈 또는 `link` | `sm` |
618
+
534
619
  ### `<sd-form>` 으로 감싸기
535
620
 
536
621
  폼 안 입력에서 enter 키 → submit 자동 처리되게 하려면 `<sd-form>` 으로 감싸고 `(formSubmit)` 로 받는다. `sd-crud-list` / `sd-crud-detail` 는 내부에 이미 `sd-form` 을 갖고 있어 별도 래핑 불필요.
@@ -578,18 +663,33 @@ sharedCustomers = useSharedSignal("고객사");
578
663
 
579
664
  ## 레이아웃·유틸 클래스
580
665
 
581
- **화면 레이아웃** (영역 분할 — 상단/하단/좌/우/중앙): `<sd-dock-container>` + `<sd-dock>`.
666
+ **화면 레이아웃** (영역 분할): flex 유틸 클래스.
667
+
668
+ 상하 분할 (상단 고정 + 본문 fill):
582
669
 
583
670
  ```html
584
- <sd-dock-container>
585
- <sd-dock class="pb-sm">
671
+ <div class="flex-column fill">
672
+ <div class="pb-sm">
586
673
  <!-- 상단 고정 영역 -->
587
- </sd-dock>
588
- <!-- 본문 (남은 공간 자동) -->
589
- </sd-dock-container>
674
+ </div>
675
+ <div class="flex-fill">
676
+ <!-- 본문 (남은 공간 자동) -->
677
+ </div>
678
+ </div>
590
679
  ```
591
680
 
592
- **아이템 레이아웃** (영역 항목 배치): flex 유틸 클래스.
681
+ 좌우 분할 (좌측 콘텐츠 + 우측 fill):
682
+
683
+ ```html
684
+ <div class="flex-row fill">
685
+ <div class="flex-min">
686
+ <!-- 좌측 -->
687
+ </div>
688
+ <div class="flex-fill">
689
+ <!-- 우측 -->
690
+ </div>
691
+ </div>
692
+ ```
593
693
 
594
694
  자주 쓰는 유틸:
595
695
 
@@ -602,7 +702,7 @@ sharedCustomers = useSharedSignal("고객사");
602
702
 
603
703
  **약속**:
604
704
 
605
- - 영역 분할은 `sd-dock-container` / `sd-dock`, 영역 안 배치는 flex 유틸 클래스를 우선한다. 자체 styles 작성은 마지막 수단.
705
+ - 영역 분할·배치 모두 flex 유틸 클래스 우선. 자체 styles 작성은 마지막 수단.
606
706
  - 글로벌 클래스 정의는 `@simplysm/angular/scss/commons/`.
607
707
 
608
708
  ## 아이콘
@@ -631,101 +731,6 @@ export class SomeComponent {
631
731
  - 아이콘 셋트는 `tabler-icons` 통일.
632
732
  - 사용할 아이콘은 컴포넌트 클래스에 `protected readonly tablerXxx = tablerXxx` 로 노출 후 템플릿에서 `[svg]` 바인딩.
633
733
 
634
- ## `sd-crud-list`
635
-
636
- 목록 화면의 표준 골격. 시트 + 검색 폼 + 등록/삭제/복구 버튼 + CTRL+S 저장 + 모달 선택 모드를 한꺼번에 처리.
637
-
638
- ### 표준 호출
639
-
640
- ```html
641
- <sd-crud-list
642
- [(ready)]="ready"
643
- [initialized]="initialized()"
644
- [(busyCount)]="busyCount"
645
- [restricted]="!perms().includes('use')"
646
- [readonly]="!canEdit()"
647
- [viewType]="viewType()"
648
- [selectMode]="selectMode() ?? 'multi'"
649
- [key]="'<도메인-키>'"
650
- [items]="items()"
651
- [trackByFn]="trackByFn"
652
- [(selectedKeys)]="selectedKeys"
653
- [(currentPage)]="page"
654
- [totalPageCount]="pageLength()"
655
- [(sorts)]="sortingDefs"
656
- (filterSubmit)="onFilterSubmit()"
657
- (submit)="onSubmit()"
658
- (create)="onCreate()"
659
- (delete)="onDelete($event)"
660
- (restore)="onRestore($event)"
661
- >
662
- <ng-template #filterTpl>...</ng-template>
663
- <ng-template #toolTpl>...</ng-template>
664
-
665
- <sd-sheet-column ...>
666
- <ng-template [cell]="items()" let-item="item">...</ng-template>
667
- </sd-sheet-column>
668
- </sd-crud-list>
669
- ```
670
-
671
- ### 슬롯 약속
672
-
673
- | 슬롯 | 용도 |
674
- | ------------------- | -------------------------------------------------------------------------- |
675
- | `#filterTpl` | 검색 폼 필드. 있으면 상단에 조회 버튼과 함께 노출. |
676
- | `#toolTpl` | 등록/삭제 버튼 옆 추가 도구 버튼. |
677
- | `#commandTpl` | 상단(또는 modal/control 모드의 명령 영역) 추가 액션 버튼. |
678
- | `#bottomCommandTpl` | modal 하단 좌측 영역. modal + selectMode 면 "선택 해제/확인" 과 함께 표시. |
679
-
680
- `<sd-sheet-column>` 은 `<sd-crud-list>` 의 직속 자식으로 두면 내부 시트로 자동 투영된다.
681
-
682
- ### viewType 별 동작
683
-
684
- - **`'page'`** — 라우팅 진입 단위. 상단에 저장 버튼.
685
- - **`'control'`** — view 안에 임베드. 명령 영역에 저장 버튼.
686
- - **`'modal'`** — 모달. `selectMode` 와 함께 쓰면 close 페이로드 `{ selectedKeys }` 자동 처리.
687
-
688
- ### 모달 선택 모드
689
-
690
- `viewType="modal"` + `selectMode` 지정 시:
691
-
692
- - `single` — 행 클릭 즉시 modal close.
693
- - `multi` — 하단 "확인(N)" 버튼이 close 발생.
694
-
695
- 호출측은 `_sdModal.showAsync(...)` 결과로 `{ selectedKeys }` 페이로드 회수.
696
-
697
- ## `sd-crud-detail`
698
-
699
- 단일 레코드 편집 화면의 표준 골격. 폼 래핑 + CTRL+S/저장 버튼 + 모달의 "확인" 버튼 자동 처리.
700
-
701
- ### 표준 호출
702
-
703
- ```html
704
- <sd-crud-detail
705
- [(ready)]="ready"
706
- [initialized]="initialized()"
707
- [(busyCount)]="busyCount"
708
- [restricted]="!perms().includes('use')"
709
- [readonly]="!canEdit()"
710
- [viewType]="viewType()"
711
- (submit)="onSubmit()"
712
- >
713
- <ng-template #contentTpl>
714
- <!-- 폼 본문 -->
715
- </ng-template>
716
- </sd-crud-detail>
717
- ```
718
-
719
- ### 슬롯 약속
720
-
721
- | 슬롯 | 용도 |
722
- | -------------------- | ----------------------------------------------------------------- |
723
- | `#contentTpl` (필수) | 폼 본문. `readonly` 면 `<sd-form>` 래핑 없이 그냥 표시된다. |
724
- | `#commandTpl` | 상단/명령 영역 추가 버튼. |
725
- | `#bottomCommandTpl` | modal 하단 좌측. modal 일 때 우측 "확인" 버튼이 항상 자동 추가됨. |
726
-
727
- ### viewType 별 동작
734
+ ## sd-crud-* 컴포넌트
728
735
 
729
- - **`'page'`** 라우팅 진입 단위. 상단에 저장 버튼.
730
- - **`'control'`** — view 안에 임베드. 명령 영역에 저장 버튼.
731
- - **`'modal'`** — 모달. 하단 우측 "확인" 버튼이 자동.
736
+ 목록 화면 표준 골격 `sd-crud-list`, 단건 편집 화면 표준 골격 `sd-crud-detail`. 화면 작성 시 이 둘을 채택할지 결정한다. 채택 시 사용법은 [client-crud.md](./client-crud.md).