@simplysm/sd-claude 14.0.65 → 14.0.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/claude/references/sd-requirement-source-handling.md +64 -0
- package/claude/references/sd-simplysm14/README.md +44 -40
- package/claude/references/sd-simplysm14/apis/angular/README.md +95 -0
- package/claude/references/sd-simplysm14/apis/angular/app-structure.md +49 -0
- package/claude/references/sd-simplysm14/apis/angular/buttons.md +42 -0
- package/claude/references/sd-simplysm14/apis/angular/crud.md +35 -0
- package/claude/references/sd-simplysm14/apis/angular/forms.md +63 -0
- package/claude/references/sd-simplysm14/apis/angular/infrastructure.md +80 -0
- package/claude/references/sd-simplysm14/apis/angular/kanban.md +33 -0
- package/claude/references/sd-simplysm14/apis/angular/layout.md +41 -0
- package/claude/references/sd-simplysm14/apis/angular/modal.md +63 -0
- package/claude/references/sd-simplysm14/apis/angular/routing.md +45 -0
- package/claude/references/sd-simplysm14/apis/angular/select-dropdown.md +35 -0
- package/claude/references/sd-simplysm14/apis/angular/selection-managers.md +50 -0
- package/claude/references/sd-simplysm14/apis/angular/shared-data.md +42 -0
- package/claude/references/sd-simplysm14/apis/angular/sheet.md +52 -0
- package/claude/references/sd-simplysm14/apis/angular/toast.md +46 -0
- package/claude/references/sd-simplysm14/apis/angular/visual.md +41 -0
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +76 -0
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-file-system/README.md +83 -0
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +80 -0
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +39 -0
- package/claude/references/sd-simplysm14/apis/core-browser/README.md +112 -0
- package/claude/references/sd-simplysm14/apis/core-common/README.md +53 -0
- package/claude/references/sd-simplysm14/apis/core-common/extensions.md +123 -0
- package/claude/references/sd-simplysm14/apis/core-common/features.md +46 -0
- package/claude/references/sd-simplysm14/apis/core-common/types.md +114 -0
- package/claude/references/sd-simplysm14/apis/core-common/utils.md +158 -0
- package/claude/references/sd-simplysm14/apis/core-node/README.md +12 -0
- package/claude/references/sd-simplysm14/apis/core-node/consola.md +64 -0
- package/claude/references/sd-simplysm14/apis/core-node/cpx.md +52 -0
- package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +53 -0
- package/claude/references/sd-simplysm14/apis/core-node/fsx.md +81 -0
- package/claude/references/sd-simplysm14/apis/core-node/pathx.md +55 -0
- package/claude/references/sd-simplysm14/apis/core-node/worker.md +111 -0
- package/claude/references/sd-simplysm14/apis/excel/README.md +81 -0
- package/claude/references/sd-simplysm14/apis/lint/README.md +80 -0
- package/claude/references/sd-simplysm14/apis/orm-common/README.md +33 -0
- package/claude/references/sd-simplysm14/apis/orm-common/db-context.md +77 -0
- package/claude/references/sd-simplysm14/apis/orm-common/executable.md +20 -0
- package/claude/references/sd-simplysm14/apis/orm-common/expr.md +92 -0
- package/claude/references/sd-simplysm14/apis/orm-common/queryable.md +98 -0
- package/claude/references/sd-simplysm14/apis/orm-common/schema-builders.md +128 -0
- package/claude/references/sd-simplysm14/apis/orm-node/README.md +59 -0
- package/claude/references/sd-simplysm14/apis/sd-claude/README.md +9 -0
- package/claude/references/sd-simplysm14/apis/sd-cli/README.md +50 -0
- package/claude/references/sd-simplysm14/apis/sd-cli/sd-config.md +155 -0
- package/claude/references/sd-simplysm14/apis/service-client/README.md +92 -0
- package/claude/references/sd-simplysm14/apis/service-common/README.md +29 -0
- package/claude/references/sd-simplysm14/apis/service-common/app-structure.md +63 -0
- package/claude/references/sd-simplysm14/apis/service-common/messages.md +56 -0
- package/claude/references/sd-simplysm14/apis/service-common/protocol.md +64 -0
- package/claude/references/sd-simplysm14/apis/service-common/service-types.md +43 -0
- package/claude/references/sd-simplysm14/apis/service-server/README.md +20 -0
- package/claude/references/sd-simplysm14/apis/service-server/auth.md +31 -0
- package/claude/references/sd-simplysm14/apis/service-server/builtin-services.md +47 -0
- package/claude/references/sd-simplysm14/apis/service-server/define-service.md +71 -0
- package/claude/references/sd-simplysm14/apis/service-server/internals.md +41 -0
- package/claude/references/sd-simplysm14/apis/service-server/server.md +66 -0
- package/claude/references/sd-simplysm14/apis/storage/README.md +69 -0
- package/claude/references/sd-simplysm14/{client-component.md → manuals/client-component.md} +134 -128
- package/claude/references/sd-simplysm14/manuals/client-crud.md +102 -0
- package/claude/references/sd-simplysm14/manuals/client-demo.md +128 -0
- package/claude/references/sd-simplysm14/manuals/client-rules.md +7 -0
- package/claude/references/sd-simplysm14/{client-setup.md → manuals/client-setup.md} +2 -2
- package/claude/references/sd-simplysm14/{client-tab.md → manuals/client-tab.md} +13 -11
- package/claude/references/sd-simplysm14/{orm-union.md → manuals/orm-union.md} +1 -1
- package/claude/references/sd-simplysm14/manuals/orm.md +75 -0
- package/claude/rules/sd-base-rules.md +191 -79
- package/claude/scripts/sd_paths.py +22 -0
- package/claude/sd-check-bash.py +19 -0
- package/claude/sd-statusline.py +7 -12
- package/claude/skills/sd-commit/SKILL.md +8 -3
- package/claude/skills/sd-demo/SKILL.md +103 -62
- package/claude/skills/sd-demo/evals/fixtures/empty/.specs/260513120000_warehouse/spec.md +45 -0
- package/claude/skills/sd-demo/evals/fixtures/with-existing-screen/.specs/260513120000_warehouse/spec.md +42 -0
- package/claude/skills/sd-demo/evals/fixtures/with-existing-screen/packages/app/src/screens/dashboard/dashboard.view.ts +33 -0
- package/claude/skills/sd-demo/evals/fixtures/with-master-screen/.specs/260513120000_warehouse/spec.md +45 -0
- package/claude/skills/sd-demo/evals/fixtures/with-master-screen/packages/app/src/screens/dashboard/dashboard.view.ts +33 -0
- package/claude/skills/sd-demo/evals/fixtures/with-modal/.specs/260513120000_warehouse/spec.md +75 -0
- package/claude/skills/sd-demo/evals/fixtures/with-modal/packages/app/src/screens/dashboard/dashboard.view.ts +33 -0
- package/claude/skills/sd-demo/evals/fixtures/with-screens/.specs/260513120000_warehouse/spec.md +45 -0
- package/claude/skills/sd-demo/evals/fixtures/with-screens/packages/app/src/screens/dashboard/dashboard.view.ts +33 -0
- package/claude/skills/sd-demo/evals/golden.jsonl +5 -3
- package/claude/skills/sd-dev/SKILL.md +33 -63
- package/claude/skills/sd-dev/evals/fixtures/case-add/package.json +13 -0
- package/claude/skills/sd-dev/evals/fixtures/case-add/src/index.ts +10 -0
- package/claude/skills/sd-dev/evals/fixtures/case-add/tests/index.test.ts +11 -0
- package/claude/skills/sd-dev/evals/fixtures/case-add/tsconfig.json +12 -0
- package/claude/skills/sd-dev/evals/fixtures/case-bug/package.json +13 -0
- package/claude/skills/sd-dev/evals/fixtures/case-bug/src/index.ts +10 -0
- package/claude/skills/sd-dev/evals/fixtures/case-bug/tests/index.test.ts +11 -0
- package/claude/skills/sd-dev/evals/fixtures/case-bug/tsconfig.json +12 -0
- package/claude/skills/sd-dev/evals/fixtures/case-modify/package.json +13 -0
- package/claude/skills/sd-dev/evals/fixtures/case-modify/src/index.ts +10 -0
- package/claude/skills/sd-dev/evals/fixtures/case-modify/tests/index.test.ts +11 -0
- package/claude/skills/sd-dev/evals/fixtures/case-modify/tsconfig.json +12 -0
- package/claude/skills/sd-dev/evals/golden.jsonl +3 -3
- package/claude/skills/sd-docs/SKILL.md +53 -0
- package/claude/skills/sd-docs/evals/fixtures/new-write/.claude/references/sd-simplysm14/README.md +7 -0
- package/claude/skills/sd-docs/evals/fixtures/new-write/packages/bar/package.json +5 -0
- package/claude/skills/sd-docs/evals/fixtures/new-write/packages/bar/src/index.ts +3 -0
- package/claude/skills/sd-docs/evals/fixtures/new-write/packages/baz/package.json +6 -0
- package/claude/skills/sd-docs/evals/fixtures/new-write/packages/baz/src/index.ts +1 -0
- package/claude/skills/sd-docs/evals/fixtures/new-write/packages/foo/package.json +5 -0
- package/claude/skills/sd-docs/evals/fixtures/new-write/packages/foo/src/index.ts +8 -0
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/.claude/references/sd-simplysm14/README.md +7 -0
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/.claude/references/sd-simplysm14/apis/foo/README.md +3 -0
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/bar/package.json +5 -0
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/bar/src/index.ts +3 -0
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/baz/package.json +6 -0
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/baz/src/index.ts +1 -0
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/foo/package.json +5 -0
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/foo/src/index.ts +8 -0
- package/claude/skills/sd-docs/evals/golden.jsonl +2 -0
- package/claude/skills/sd-docs/references/subagent-prompt.md +100 -0
- package/claude/skills/sd-impl/SKILL.md +149 -46
- package/claude/skills/sd-impl/evals/fixtures/case-001-new-screen/spec.md +55 -0
- package/claude/skills/sd-impl/evals/fixtures/case-002-auto-process/spec.md +55 -0
- package/claude/skills/sd-impl/evals/fixtures/case-003-update-screen/packages/client/src/pages/book-list.ts +22 -0
- package/claude/skills/sd-impl/evals/fixtures/case-003-update-screen/spec.md +57 -0
- package/claude/skills/sd-impl/evals/fixtures/case-004-ambiguous-spec/spec.md +58 -0
- package/claude/skills/sd-impl/evals/fixtures/case-005-id-mismatch/spec.md +52 -0
- package/claude/skills/sd-impl/evals/fixtures/case-006-with-reference-units/packages/client/src/pages//352/261/260/353/236/230/354/262/230//352/261/260/353/236/230/354/262/230-/353/252/251/353/241/235.test.ts +10 -0
- package/claude/skills/sd-impl/evals/fixtures/case-006-with-reference-units/packages/client/src/pages//352/261/260/353/236/230/354/262/230//352/261/260/353/236/230/354/262/230-/353/252/251/353/241/235.ts +11 -0
- package/claude/skills/sd-impl/evals/fixtures/case-006-with-reference-units/packages/server/src/data-access//352/261/260/353/236/230/354/262/230-/354/240/221/352/267/274.ts +12 -0
- package/claude/skills/sd-impl/evals/fixtures/case-006-with-reference-units/packages/server/src/models//352/261/260/353/236/230/354/262/230.ts +8 -0
- package/claude/skills/sd-impl/evals/fixtures/case-006-with-reference-units/spec.md +77 -0
- package/claude/skills/sd-impl/evals/fixtures/case-new/.specs/260514120000_/352/261/260/353/236/230/354/262/230/spec.md +101 -0
- package/claude/skills/sd-impl/evals/fixtures/case-update/.specs/260514120000_/352/261/260/353/236/230/354/262/230/spec.md +101 -0
- 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
- 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
- package/claude/skills/sd-impl/evals/golden.jsonl +6 -3
- package/claude/skills/sd-impl/references/spec-cross-check.md +82 -0
- package/claude/skills/sd-skill/SKILL.md +4 -4
- package/claude/skills/sd-skill/evals/golden.jsonl +1 -2
- package/claude/skills/sd-skill/references/eval-authoring.md +31 -0
- package/claude/skills/sd-skill/references/eval-run.md +1 -1
- package/claude/skills/sd-skill/references/skill-authoring.md +8 -5
- package/claude/skills/sd-skill/scripts/run_eval.py +39 -60
- package/claude/skills/sd-spec/SKILL.md +163 -105
- package/claude/skills/sd-spec/references/example-spec.md +585 -0
- package/claude/skills/sd-spec/references/spec-authoring.md +287 -0
- package/claude/skills/sd-spec/references/spec-md-template.md +15 -93
- package/claude/skills/sd-unpack/SKILL.md +7 -1
- package/claude/skills/sd-unpack/scripts/handlers/_common.py +10 -0
- package/claude/skills/sd-unpack/scripts/handlers/eml_handler.py +5 -13
- package/claude/skills/sd-unpack/scripts/handlers/msg_handler.py +3 -12
- package/claude/skills/sd-unpack/scripts/handlers/office_com.py +23 -37
- package/claude/skills/sd-unpack/scripts/handlers/office_worker.py +1 -4
- package/claude/skills/sd-unpack/scripts/handlers/pdf_handler.py +4 -13
- package/claude/skills/sd-unpack/scripts/unpack.py +4 -4
- package/claude/skills/sd-use/SKILL.md +1 -0
- package/claude/skills/sd-wip/SKILL.md +38 -0
- package/claude/skills/sd-wip/evals/fixtures/with-artifact/projects/acct/_wip.md +3 -0
- package/claude/skills/sd-wip/evals/fixtures/with-artifact/projects/acct/spec.md +15 -0
- package/claude/skills/sd-wip/evals/fixtures/with-existing-wip/.wips/260101120000_acct.md +6 -0
- package/claude/skills/sd-wip/evals/fixtures/with-existing-wip-for-compact/.wips/260101120000_acct.md +14 -0
- package/claude/skills/sd-wip/evals/golden.jsonl +4 -0
- package/claude/skills/sd-wip/references/compact.md +79 -0
- package/package.json +1 -1
- package/scripts/sd-entries.mjs +2 -2
- package/claude/references/sd-simplysm14/orm.md +0 -11
- 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
- package/claude/skills/sd-demo/evals/fixtures/basic-single-req/.specs/260503143025/overview.md +0 -12
- package/claude/skills/sd-demo/evals/fixtures/basic-single-req/src/components/Button.tsx +0 -12
- package/claude/skills/sd-demo/evals/fixtures/basic-single-req/src/components/Input.tsx +0 -27
- 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
- package/claude/skills/sd-demo/evals/fixtures/mock-data-policy/.specs/260503143025/overview.md +0 -12
- package/claude/skills/sd-demo/evals/fixtures/mock-data-policy/src/components/Input.tsx +0 -25
- package/claude/skills/sd-demo/evals/fixtures/mock-data-policy/src/pages/.gitkeep +0 -0
- 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
- 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
- package/claude/skills/sd-demo/evals/fixtures/multi-req-domain/.specs/260503143025/overview.md +0 -16
- package/claude/skills/sd-demo/evals/fixtures/multi-req-domain/src/components/Button.tsx +0 -6
- package/claude/skills/sd-demo/evals/fixtures/multi-req-domain/src/components/Input.tsx +0 -15
- package/claude/skills/sd-demo/evals/fixtures/multi-req-domain/src/pages/.gitkeep +0 -0
- package/claude/skills/sd-demo/references/demo-md-template.md +0 -92
- 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
- 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
- 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
- package/claude/skills/sd-dev/evals/fixtures/single-req-auto-chain/src/lib/.gitkeep +0 -0
- 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
- package/claude/skills/sd-dev/evals/fixtures/start-from-plan/src/lib/.gitkeep +0 -0
- 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
- 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
- package/claude/skills/sd-impl/evals/fixtures/basic-single-r/src/components/Input.tsx +0 -6
- package/claude/skills/sd-impl/evals/fixtures/basic-single-r/src/pages/.gitkeep +0 -0
- 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
- 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
- package/claude/skills/sd-impl/evals/fixtures/multi-r/src/components/Input.tsx +0 -25
- package/claude/skills/sd-impl/evals/fixtures/multi-r/src/pages/.gitkeep +0 -0
- 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
- 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
- package/claude/skills/sd-impl/evals/fixtures/with-test-file/src/lib/.gitkeep +0 -0
- package/claude/skills/sd-impl/references/impl-md-template.md +0 -87
- package/claude/skills/sd-impl/references/modes-and-failure.md +0 -65
- package/claude/skills/sd-plan/SKILL.md +0 -130
- 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
- package/claude/skills/sd-plan/evals/fixtures/already-implemented/src/api/user.ts +0 -13
- package/claude/skills/sd-plan/evals/fixtures/already-implemented/src/components/Input.tsx +0 -15
- package/claude/skills/sd-plan/evals/fixtures/already-implemented/src/pages/UserList.tsx +0 -29
- 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
- package/claude/skills/sd-plan/evals/fixtures/basic-greenfield/src/components/Input.tsx +0 -6
- package/claude/skills/sd-plan/evals/fixtures/basic-greenfield/src/pages/.gitkeep +0 -0
- 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
- 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
- package/claude/skills/sd-plan/evals/fixtures/demo-built/src/components/Input.tsx +0 -25
- package/claude/skills/sd-plan/evals/fixtures/demo-built/src/data/mock-customer.ts +0 -16
- package/claude/skills/sd-plan/evals/fixtures/demo-built/src/pages/CustomerList.tsx +0 -25
- package/claude/skills/sd-plan/evals/golden.jsonl +0 -3
- package/claude/skills/sd-plan/references/plan-md-template.md +0 -138
- 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
- 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
- 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
- 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
- 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
- package/claude/skills/sd-spec/evals/fixtures/direct-simple/.gitkeep +0 -0
- package/claude/skills/sd-spec/evals/fixtures/spec-md-input/.specs/260101120000/REQ-001-test/spec.md +0 -19
- package/claude/skills/sd-spec/evals/fixtures/spec-md-input/.specs/260101120000/overview.md +0 -18
- package/claude/skills/sd-spec/evals/golden.jsonl +0 -4
- package/claude/skills/sd-spec/references/overview-md-template.md +0 -89
- package/claude/skills/sd-spec/references/raw-input-handling.md +0 -96
- package/claude/skills/sd-verify/SKILL.md +0 -96
- 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
- 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
- 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
- package/claude/skills/sd-verify/evals/fixtures/all-satisfied/src/lib/discount.test.ts +0 -11
- package/claude/skills/sd-verify/evals/fixtures/all-satisfied/src/lib/discount.ts +0 -7
- 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
- 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
- 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
- package/claude/skills/sd-verify/evals/fixtures/partial-mismatch/src/pages/UserList.tsx +0 -19
- 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
- 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
- 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
- package/claude/skills/sd-verify/evals/fixtures/tdd-only/src/lib/validate-amount.test.ts +0 -10
- package/claude/skills/sd-verify/evals/fixtures/tdd-only/src/lib/validate-amount.ts +0 -7
- package/claude/skills/sd-verify/evals/golden.jsonl +0 -3
- package/claude/skills/sd-verify/references/verify-md-template.md +0 -99
- /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 로 깔끔히 나뉘지 않는 화면.
|
|
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
|
|
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) 가 공통으로 쓰는 시그널
|
|
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
|
-
- 권한 체크 뒤에 추가 조건(데이터 상태 등)이 결합되어
|
|
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,19 +509,26 @@ async onSubmit(): Promise<void> {
|
|
|
466
509
|
## 시트 컬럼·셀 표준
|
|
467
510
|
|
|
468
511
|
```html
|
|
469
|
-
<sd-sheet-column [key]="'name'" [header]="'이름'"
|
|
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
|
-
-
|
|
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"`.
|
|
531
|
+
- 셀 안 div 에 배경색 클래스(`bg-theme-*-lightest` 등)를 토글할 때는 빈 값 자리에 ` ` 등을 채워 div 가 셀 높이를 유지하게 한다. (table cell 자식 div 가 콘텐츠 없을 때 높이 0 → bg 가 셀에 차지 않음.)
|
|
482
532
|
|
|
483
533
|
**list 안에서**: `<sd-crud-list>` 의 직속 자식으로 `<sd-sheet-column>` 을 두면 내부 시트로 자동 투영된다.
|
|
484
534
|
|
|
@@ -492,21 +542,47 @@ async onSubmit(): Promise<void> {
|
|
|
492
542
|
</sd-crud-list>
|
|
493
543
|
```
|
|
494
544
|
|
|
545
|
+
### 요약 행
|
|
546
|
+
|
|
547
|
+
컬럼에 `<ng-template #summaryTpl>` 을 두면 시트의 헤더 영역 하단(`thead` 내부)에 요약 행이 렌더된다. 스크롤 시 헤더와 함께 상단 고정되며, 배경은 warning 계열로 자동 강조된다.
|
|
548
|
+
|
|
549
|
+
```html
|
|
550
|
+
<sd-sheet-column [key]="'quantity'" [header]="'수량'">
|
|
551
|
+
<ng-template #summaryTpl>
|
|
552
|
+
<div class="p-xs-sm tx-right">{{ totalQuantity() }}</div>
|
|
553
|
+
</ng-template>
|
|
554
|
+
<ng-template [cell]="items()" let-item="item">
|
|
555
|
+
<div class="p-xs-sm tx-right">{{ item.quantity }}</div>
|
|
556
|
+
</ng-template>
|
|
557
|
+
</sd-sheet-column>
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
- 컬럼 중 하나라도 `#summaryTpl` 을 가지면 요약 행 전체가 활성화된다. 정의 없는 컬럼은 빈 셀.
|
|
561
|
+
- 셀 본문 약속(`p-xs-sm`, 정렬 클래스 등)은 요약 셀에도 동일하게 적용.
|
|
562
|
+
- 합계·평균 등 집계 값은 시트가 계산해주지 않는다. 화면 컴포넌트에서 `computed` 로 직접 만들어 노출한다.
|
|
563
|
+
|
|
564
|
+
```ts
|
|
565
|
+
totalQuantity = computed(() => this.items().sum((i) => i.quantity) ?? 0);
|
|
566
|
+
```
|
|
567
|
+
|
|
495
568
|
## 폼·입력 컨트롤
|
|
496
569
|
|
|
497
570
|
### 폼 항목 레이아웃
|
|
498
571
|
|
|
499
|
-
|
|
572
|
+
label + 입력 그룹을 묶는 전용 클래스 3종:
|
|
573
|
+
|
|
574
|
+
- `form-box` — 세로 스택. `> div` 또는 `> div` 안에 `<label>` + 입력. 항목 사이 `gap-default`.
|
|
575
|
+
- `form-box-inline` — 가로 인라인 flex (wrap). 라벨이 입력 옆에 붙음. 검색·필터폼에 사용. 라벨 없는 `form-box-item` 도 허용 (버튼 등).
|
|
576
|
+
- `form-table` — `<table>` 기반. `<th>` 가 우측 정렬 라벨, `<td>` 가 입력. `<th class="form-table-header">` 는 섹션 헤더(좌측 정렬, 회색, 위쪽 여백 큼). 라벨/입력 폭을 정렬해야 하는 등록·편집 폼에 사용.
|
|
500
577
|
|
|
501
578
|
```html
|
|
502
579
|
<div class="form-box-inline">
|
|
503
|
-
<div
|
|
504
|
-
<label
|
|
505
|
-
<sd-
|
|
580
|
+
<div>
|
|
581
|
+
<label>기준 일자</label>
|
|
582
|
+
<sd-modal-select-button [(value)]="baseDate" ...>{{ baseDate() ?? "선택" }}</sd-modal-select-button>
|
|
506
583
|
</div>
|
|
507
|
-
<div
|
|
508
|
-
<
|
|
509
|
-
<sd-textfield [type]="'date'" [(value)]="data().createdAt" (valueChange)="mark(data)" />
|
|
584
|
+
<div>
|
|
585
|
+
<sd-button [theme]="'primary'" (click)="onCompareButtonClick()">비교</sd-button>
|
|
510
586
|
</div>
|
|
511
587
|
</div>
|
|
512
588
|
```
|
|
@@ -531,6 +607,16 @@ async onSubmit(): Promise<void> {
|
|
|
531
607
|
| 라벨/배지 | `<sd-label [theme]>` |
|
|
532
608
|
| 버튼/액션 | `<sd-button>`, `<sd-anchor>` |
|
|
533
609
|
|
|
610
|
+
### 버튼 스타일
|
|
611
|
+
|
|
612
|
+
화면 액션 `<sd-button>` 은 역할별로 `theme`·`size` 를 구분.
|
|
613
|
+
|
|
614
|
+
| 역할 | `[theme]` | `[size]` |
|
|
615
|
+
| ------------------------------------------------------------- | --------------------------------------------------------------------------- | -------- |
|
|
616
|
+
| 데이터 자체를 통으로 변경하는 최상위 액션 (저장·삭제·생성 등) | 일반 시리즈 (`primary` / `danger` / `success` / `warning` 등 의미에 맞춰) | 기본 |
|
|
617
|
+
| 위 액션 옆 유틸리티 버튼 (양식 다운로드·인쇄 등) | link 시리즈 (`link-primary` 등) | 기본 |
|
|
618
|
+
| 시트 위(또는 시트 셀 안)에 나열되는 버튼 | link 시리즈 또는 `link` | `sm` |
|
|
619
|
+
|
|
534
620
|
### `<sd-form>` 으로 감싸기
|
|
535
621
|
|
|
536
622
|
폼 안 입력에서 enter 키 → submit 자동 처리되게 하려면 `<sd-form>` 으로 감싸고 `(formSubmit)` 로 받는다. `sd-crud-list` / `sd-crud-detail` 는 내부에 이미 `sd-form` 을 갖고 있어 별도 래핑 불필요.
|
|
@@ -578,18 +664,33 @@ sharedCustomers = useSharedSignal("고객사");
|
|
|
578
664
|
|
|
579
665
|
## 레이아웃·유틸 클래스
|
|
580
666
|
|
|
581
|
-
**화면 레이아웃** (영역 분할
|
|
667
|
+
**화면 레이아웃** (영역 분할): flex 유틸 클래스.
|
|
668
|
+
|
|
669
|
+
상하 분할 (상단 고정 + 본문 fill):
|
|
582
670
|
|
|
583
671
|
```html
|
|
584
|
-
<
|
|
585
|
-
<
|
|
672
|
+
<div class="flex-column fill">
|
|
673
|
+
<div class="pb-sm">
|
|
586
674
|
<!-- 상단 고정 영역 -->
|
|
587
|
-
</
|
|
588
|
-
|
|
589
|
-
|
|
675
|
+
</div>
|
|
676
|
+
<div class="flex-fill">
|
|
677
|
+
<!-- 본문 (남은 공간 자동) -->
|
|
678
|
+
</div>
|
|
679
|
+
</div>
|
|
590
680
|
```
|
|
591
681
|
|
|
592
|
-
|
|
682
|
+
좌우 분할 (좌측 콘텐츠 폭 + 우측 fill):
|
|
683
|
+
|
|
684
|
+
```html
|
|
685
|
+
<div class="flex-row fill">
|
|
686
|
+
<div class="flex-min">
|
|
687
|
+
<!-- 좌측 -->
|
|
688
|
+
</div>
|
|
689
|
+
<div class="flex-fill">
|
|
690
|
+
<!-- 우측 -->
|
|
691
|
+
</div>
|
|
692
|
+
</div>
|
|
693
|
+
```
|
|
593
694
|
|
|
594
695
|
자주 쓰는 유틸:
|
|
595
696
|
|
|
@@ -602,7 +703,7 @@ sharedCustomers = useSharedSignal("고객사");
|
|
|
602
703
|
|
|
603
704
|
**약속**:
|
|
604
705
|
|
|
605
|
-
- 영역
|
|
706
|
+
- 영역 분할·배치 모두 flex 유틸 클래스 우선. 자체 styles 작성은 마지막 수단.
|
|
606
707
|
- 글로벌 클래스 정의는 `@simplysm/angular/scss/commons/`.
|
|
607
708
|
|
|
608
709
|
## 아이콘
|
|
@@ -631,101 +732,6 @@ export class SomeComponent {
|
|
|
631
732
|
- 아이콘 셋트는 `tabler-icons` 통일.
|
|
632
733
|
- 사용할 아이콘은 컴포넌트 클래스에 `protected readonly tablerXxx = tablerXxx` 로 노출 후 템플릿에서 `[svg]` 바인딩.
|
|
633
734
|
|
|
634
|
-
##
|
|
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 별 동작
|
|
735
|
+
## sd-crud-* 컴포넌트
|
|
728
736
|
|
|
729
|
-
-
|
|
730
|
-
- **`'control'`** — view 안에 임베드. 명령 영역에 저장 버튼.
|
|
731
|
-
- **`'modal'`** — 모달. 하단 우측 "확인" 버튼이 자동.
|
|
737
|
+
목록 화면 표준 골격 `sd-crud-list`, 단건 편집 화면 표준 골격 `sd-crud-detail`. 화면 작성 시 이 둘을 채택할지 결정한다. 채택 시 사용법은 [client-crud.md](./client-crud.md).
|