@open-mercato/cli 0.6.7-develop.6862.1.c11a64ce0a → 0.6.7
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/.turbo/turbo-build.log +4 -4
- package/build.mjs +7 -24
- package/dist/agentic/claude-code/hooks/entity-migration-check.ts +88 -32
- package/dist/agentic/codex/enforcement-rules.md +16 -2
- package/dist/agentic/cursor/hooks/entity-migration-check.mjs +50 -19
- package/dist/agentic/cursor/rules/entity-guard.mdc +21 -9
- package/dist/agentic/cursor/rules/generated-guard.mdc +5 -3
- package/dist/agentic/cursor/rules/open-mercato.mdc +29 -3
- package/dist/agentic/guides/cache.md +45 -0
- package/dist/agentic/guides/core.md +453 -0
- package/dist/agentic/guides/events.md +117 -0
- package/dist/agentic/guides/module-facts.json +3833 -115419
- package/dist/agentic/guides/module-system.md +127 -12
- package/dist/agentic/guides/modules/ai_assistant.md +28 -227
- package/dist/agentic/guides/modules/api_docs.md +2 -58
- package/dist/agentic/guides/modules/api_keys.md +7 -92
- package/dist/agentic/guides/modules/attachments.md +14 -119
- package/dist/agentic/guides/modules/audit_logs.md +10 -131
- package/dist/agentic/guides/modules/auth.md +33 -276
- package/dist/agentic/guides/modules/business_rules.md +14 -149
- package/dist/agentic/guides/modules/catalog.md +43 -475
- package/dist/agentic/guides/modules/channel_gmail.md +3 -82
- package/dist/agentic/guides/modules/channel_imap.md +3 -82
- package/dist/agentic/guides/modules/checkout.md +27 -299
- package/dist/agentic/guides/modules/communication_channels.md +33 -315
- package/dist/agentic/guides/modules/configs.md +8 -104
- package/dist/agentic/guides/modules/content.md +2 -57
- package/dist/agentic/guides/modules/currencies.md +17 -152
- package/dist/agentic/guides/modules/customer_accounts.md +45 -387
- package/dist/agentic/guides/modules/customers.md +85 -746
- package/dist/agentic/guides/modules/dashboards.md +10 -155
- package/dist/agentic/guides/modules/data_sync.md +16 -149
- package/dist/agentic/guides/modules/dictionaries.md +12 -107
- package/dist/agentic/guides/modules/directory.md +15 -148
- package/dist/agentic/guides/modules/entities.md +13 -145
- package/dist/agentic/guides/modules/events.md +2 -59
- package/dist/agentic/guides/modules/feature_toggles.md +9 -119
- package/dist/agentic/guides/modules/gateway_stripe.md +4 -92
- package/dist/agentic/guides/modules/generators.md +2 -45
- package/dist/agentic/guides/modules/inbox_ops.md +27 -207
- package/dist/agentic/guides/modules/integrations.md +16 -146
- package/dist/agentic/guides/modules/messages.md +24 -223
- package/dist/agentic/guides/modules/notifications.md +8 -96
- package/dist/agentic/guides/modules/onboarding.md +6 -77
- package/dist/agentic/guides/modules/payment_gateways.md +21 -164
- package/dist/agentic/guides/modules/perspectives.md +7 -77
- package/dist/agentic/guides/modules/planner.md +18 -136
- package/dist/agentic/guides/modules/portal.md +2 -93
- package/dist/agentic/guides/modules/progress.md +14 -104
- package/dist/agentic/guides/modules/query_index.md +15 -154
- package/dist/agentic/guides/modules/record_locks.md +21 -206
- package/dist/agentic/guides/modules/resources.md +30 -197
- package/dist/agentic/guides/modules/sales.md +84 -678
- package/dist/agentic/guides/modules/scheduler.md +14 -133
- package/dist/agentic/guides/modules/search.md +6 -170
- package/dist/agentic/guides/modules/security.md +35 -294
- package/dist/agentic/guides/modules/shipping_carriers.md +18 -134
- package/dist/agentic/guides/modules/sso.md +26 -151
- package/dist/agentic/guides/modules/staff.md +57 -369
- package/dist/agentic/guides/modules/storage_s3.md +5 -95
- package/dist/agentic/guides/modules/sync_akeneo.md +3 -85
- package/dist/agentic/guides/modules/sync_excel.md +6 -75
- package/dist/agentic/guides/modules/system_status_overlays.md +2 -60
- package/dist/agentic/guides/modules/translations.md +10 -104
- package/dist/agentic/guides/modules/webhooks.md +24 -203
- package/dist/agentic/guides/modules/wms.md +46 -441
- package/dist/agentic/guides/modules/workflows.md +43 -302
- package/dist/agentic/guides/queue.md +53 -0
- package/dist/agentic/guides/search.md +115 -0
- package/dist/agentic/guides/shared.md +108 -0
- package/dist/agentic/guides/ui.md +347 -0
- package/dist/agentic/shared/AGENTS.md.template +311 -124
- package/dist/agentic/shared/ai/agentic.config.json +0 -2
- package/dist/agentic/shared/ai/lessons.md +3 -26
- package/dist/agentic/shared/ai/skills/om-auto-continue-pr-loop/SKILL.md +27 -0
- package/dist/agentic/shared/ai/skills/om-auto-create-pr-loop/SKILL.md +27 -0
- package/dist/agentic/shared/ai/skills/om-auto-review-pr/SKILL.md +0 -4
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/SKILL.md +241 -20
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/ui-components.md +260 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/SKILL.md +601 -16
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/mikro-orm-cheatsheet.md +146 -0
- package/dist/agentic/shared/ai/skills/om-eject-and-customize/SKILL.md +310 -14
- package/dist/agentic/shared/ai/skills/om-help/SKILL.md +97 -15
- package/dist/agentic/shared/ai/skills/om-help/references/skills-catalog.md +84 -0
- package/dist/agentic/shared/ai/skills/om-help/references/workflow-sequences.md +193 -0
- package/dist/agentic/shared/ai/skills/om-implement-spec/SKILL.md +152 -33
- package/dist/agentic/shared/ai/skills/om-integration-builder/SKILL.md +708 -24
- package/dist/agentic/shared/ai/skills/om-integration-builder/STANDALONE.md +34 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/adapter-contracts.md +762 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/SKILL.md +820 -20
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/naming-conventions.md +50 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/navigation-patterns.md +97 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/SKILL.md +843 -19
- package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-contracts.md +271 -0
- package/dist/agentic/shared/ai/skills/om-trim-unused-modules/SKILL.md +63 -15
- package/dist/agentic/shared/ai/skills/om-troubleshooter/SKILL.md +467 -16
- package/dist/agentic/shared/ai/skills/om-troubleshooter/references/diagnostic-commands.md +101 -0
- package/dist/agentic/shared/ai/skills/tiers.json +21 -143
- package/dist/agentic/shared/ai/skills/tiers.schema.json +40 -52
- package/dist/agentic/shared/ai/specs/README.md +3 -4
- package/dist/agentic/shared/ai/specs/SPEC-000-template.md +10 -226
- package/dist/agentic/shared/ai/trackers/github.md +4 -108
- package/dist/agentic/shared/scripts/install-skills.sh +694 -4
- package/dist/bin.js +1 -13
- package/dist/bin.js.map +2 -2
- package/dist/lib/__integration__/TC-INT-008.spec.js +8 -88
- package/dist/lib/__integration__/TC-INT-008.spec.js.map +2 -2
- package/dist/lib/agentic-init.js +3 -13
- package/dist/lib/agentic-init.js.map +2 -2
- package/dist/lib/agentic-setup.js +186 -418
- package/dist/lib/agentic-setup.js.map +2 -2
- package/dist/lib/generators/extensions/injection-widgets.js +0 -20
- package/dist/lib/generators/extensions/injection-widgets.js.map +2 -2
- package/dist/lib/generators/module-facts-discovery.js +7 -41
- package/dist/lib/generators/module-facts-discovery.js.map +2 -2
- package/dist/lib/generators/module-facts.js +45 -1387
- package/dist/lib/generators/module-facts.js.map +3 -3
- package/dist/lib/generators/module-registry.js +5 -11
- package/dist/lib/generators/module-registry.js.map +2 -2
- package/dist/lib/resolver.js +0 -2
- package/dist/lib/resolver.js.map +2 -2
- package/dist/lib/testing/integration.js +0 -5
- package/dist/lib/testing/integration.js.map +2 -2
- package/dist/mercato.js +1 -23
- package/dist/mercato.js.map +2 -2
- package/package.json +11 -13
- package/src/bin.ts +1 -33
- package/src/lib/__integration__/TC-INT-008.spec.ts +9 -107
- package/src/lib/__tests__/agentic-init.test.ts +5 -41
- package/src/lib/agentic-init.ts +3 -15
- package/src/lib/agentic-setup.ts +224 -530
- package/src/lib/generators/__tests__/module-facts.bc-guard.test.ts +2 -88
- package/src/lib/generators/__tests__/module-facts.customers.fixture.test.ts +0 -1
- package/src/lib/generators/__tests__/module-facts.discovery.test.ts +10 -54
- package/src/lib/generators/__tests__/module-subset.test.ts +0 -18
- package/src/lib/generators/extensions/injection-widgets.ts +0 -51
- package/src/lib/generators/module-facts-discovery.ts +7 -58
- package/src/lib/generators/module-facts.ts +46 -1851
- package/src/lib/generators/module-registry.ts +6 -16
- package/src/lib/resolver.ts +0 -3
- package/src/lib/testing/__tests__/integration.test.ts +0 -4
- package/src/lib/testing/integration.ts +0 -5
- package/src/mercato.ts +1 -26
- package/dist/agentic/guides/ai-workflows.md +0 -76
- package/dist/agentic/guides/architecture.md +0 -107
- package/dist/agentic/guides/backend-ui.md +0 -80
- package/dist/agentic/guides/contracts.md +0 -92
- package/dist/agentic/guides/extensions.md +0 -121
- package/dist/agentic/guides/framework-extension-points.md +0 -36
- package/dist/agentic/guides/integrations.md +0 -81
- package/dist/agentic/guides/modules/design_system.md +0 -102
- package/dist/agentic/guides/spec-delivery.md +0 -22
- package/dist/agentic/guides/testing-debugging.md +0 -80
- package/dist/agentic/guides/upstream/AGENTS.md +0 -298
- package/dist/agentic/guides/upstream/BACKWARD_COMPATIBILITY.md +0 -316
- package/dist/agentic/guides/upstream/manifest.json +0 -8
- package/dist/agentic/shared/ai/harness/README.md +0 -50
- package/dist/agentic/shared/ai/harness/RELEASE.md +0 -57
- package/dist/agentic/shared/ai/harness/cases.json +0 -13680
- package/dist/agentic/shared/ai/harness/cases.schema.json +0 -133
- package/dist/agentic/shared/ai/harness/fixtures/index.json +0 -49
- package/dist/agentic/shared/ai/harness/fixtures/seeds.json +0 -162
- package/dist/agentic/shared/ai/harness/generated-code-review-policy.md +0 -21
- package/dist/agentic/shared/ai/harness/generated-code-review-response.schema.json +0 -96
- package/dist/agentic/shared/ai/harness/generated-code-review-result.schema.json +0 -172
- package/dist/agentic/shared/ai/harness/release-matrix.json +0 -158
- package/dist/agentic/shared/ai/harness/release-result.schema.json +0 -219
- package/dist/agentic/shared/ai/harness/result.schema.json +0 -76
- package/dist/agentic/shared/ai/harness/routing-response.schema.json +0 -12
- package/dist/agentic/shared/ai/harness/target-validation-result.schema.json +0 -73
- package/dist/agentic/shared/ai/harness/validators.json +0 -78
- package/dist/agentic/shared/ai/harness/writable-ast-oracles.mjs +0 -2121
- package/dist/agentic/shared/ai/harness/writable-behavior-oracles.mjs +0 -1096
- package/dist/agentic/shared/ai/lessons/_template.md +0 -16
- package/dist/agentic/shared/ai/review-checklist.md +0 -36
- package/dist/agentic/shared/ai/skills/om-auto-implement-spec/SKILL.md +0 -21
- package/dist/agentic/shared/ai/skills/om-auto-upgrade-0.6.6-to-0.6.7/SKILL.md +0 -98
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/crud-surfaces.md +0 -16
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/frontend-and-design-system.md +0 -53
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/page-and-navigation.md +0 -13
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/quality-states.md +0 -11
- package/dist/agentic/shared/ai/skills/om-build-workflow/SKILL.md +0 -30
- package/dist/agentic/shared/ai/skills/om-build-workflow/references/activity-contracts.md +0 -12
- package/dist/agentic/shared/ai/skills/om-build-workflow/references/durability-and-progress.md +0 -13
- package/dist/agentic/shared/ai/skills/om-build-workflow/references/workflow-design.md +0 -14
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/SKILL.md +0 -28
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/attachments-and-overrides.md +0 -12
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/module-agents-and-tools.md +0 -22
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/orchestrator-agents.md +0 -12
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/surface-selector.md +0 -19
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/integrity-and-concurrency.md +0 -19
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/migration-workflow.md +0 -13
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/schema-design.md +0 -14
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/sensitive-data.md +0 -10
- package/dist/agentic/shared/ai/skills/om-eject-and-customize/references/decision-and-procedure.md +0 -17
- package/dist/agentic/shared/ai/skills/om-evolve-harness/SKILL.md +0 -29
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-template.md +0 -100
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-workflow.md +0 -16
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/owner-selection.md +0 -17
- package/dist/agentic/shared/ai/skills/om-framework-context/SKILL.md +0 -25
- package/dist/agentic/shared/ai/skills/om-framework-context/references/resolver-procedure.md +0 -11
- package/dist/agentic/shared/ai/skills/om-framework-context/references/skew-and-escalation.md +0 -13
- package/dist/agentic/shared/ai/skills/om-help/references/delivery-workflows.md +0 -18
- package/dist/agentic/shared/ai/skills/om-help/references/task-families.md +0 -20
- package/dist/agentic/shared/ai/skills/om-implement-spec/references/phases-and-gates.md +0 -33
- package/dist/agentic/shared/ai/skills/om-implement-spec/references/planning-and-progress.md +0 -40
- package/dist/agentic/shared/ai/skills/om-implement-spec/references/report-templates.md +0 -27
- package/dist/agentic/shared/ai/skills/om-implement-spec/references/spec-resolution.md +0 -24
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/package-and-activation.md +0 -16
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/provider-families.md +0 -14
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/security-and-reliability.md +0 -15
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/sync-and-files.md +0 -12
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/SKILL.md +0 -34
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/agents/openai.yaml +0 -4
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/agentic-setup.md +0 -11
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/input-normalization.md +0 -31
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/judge-workflow.md +0 -39
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/report-template.md +0 -43
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/rules.md +0 -11
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/api-and-domain.md +0 -19
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/business-one-shot-blueprints.md +0 -172
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/data-and-migrations.md +0 -12
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/discovery-surface-catalog.md +0 -83
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/module-surfaces.md +0 -21
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/planning.md +0 -11
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/runtime-cache-and-queues.md +0 -22
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/verification.md +0 -13
- package/dist/agentic/shared/ai/skills/om-share-this-session/SKILL.md +0 -41
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/agentic-setup.md +0 -19
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/bundle-preparation.md +0 -44
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/consent-and-review.md +0 -39
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/publication.md +0 -20
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/report-templates.md +0 -57
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/rules.md +0 -11
- package/dist/agentic/shared/ai/skills/om-share-this-session/scripts/prepare-share-bundle.mjs +0 -486
- package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-branches.md +0 -18
- package/dist/agentic/shared/ai/skills/om-system-extension/references/mechanism-selector.md +0 -27
- package/dist/agentic/shared/ai/skills/om-system-extension/references/read-write-roundtrip.md +0 -13
- package/dist/agentic/shared/ai/skills/om-system-extension/references/unified-overrides.md +0 -41
- package/dist/agentic/shared/ai/skills/om-system-extension/references/verification.md +0 -11
- package/dist/agentic/shared/ai/skills/om-trim-unused-modules/references/dependency-and-redirect.md +0 -15
- package/dist/agentic/shared/ai/skills/om-troubleshooter/references/diagnosis-map.md +0 -16
- package/dist/agentic/shared/ai/skills/om-troubleshooter/references/regression-oracles.md +0 -14
- package/dist/agentic/shared/scripts/agent-harness-tool-server.mjs +0 -187
- package/dist/agentic/shared/scripts/check-lessons.mjs +0 -178
- package/dist/agentic/shared/scripts/evaluate-agent-harness.mjs +0 -2958
- package/dist/agentic/shared/scripts/execution-sandbox.mjs +0 -329
- package/dist/agentic/shared/scripts/framework-context.mjs +0 -888
- package/dist/agentic/shared/scripts/install-skills.mjs +0 -1029
- package/dist/agentic/shared/scripts/prepare-agent-harness-fixture.mjs +0 -222
- package/dist/agentic/shared/scripts/run-agent-harness-release.mjs +0 -1774
- package/dist/lib/generators/module-extension-facts.js +0 -1840
- package/dist/lib/generators/module-extension-facts.js.map +0 -7
- package/dist/lib/generators/module-fact-sources.js +0 -70
- package/dist/lib/generators/module-fact-sources.js.map +0 -7
- package/dist/lib/generators/module-override-targets.js +0 -688
- package/dist/lib/generators/module-override-targets.js.map +0 -7
- package/dist/lib/load-env.js +0 -24
- package/dist/lib/load-env.js.map +0 -7
- package/dist/lib/telemetry-init.js +0 -358
- package/dist/lib/telemetry-init.js.map +0 -7
- package/src/lib/__tests__/agentic-setup.ownership.test.ts +0 -386
- package/src/lib/__tests__/injection-widget-id-validation.test.ts +0 -184
- package/src/lib/__tests__/load-env.test.ts +0 -65
- package/src/lib/__tests__/telemetry-init.test.ts +0 -298
- package/src/lib/generators/__tests__/activation-adapter-coverage.test.ts +0 -92
- package/src/lib/generators/__tests__/module-extension-activations.test.ts +0 -762
- package/src/lib/generators/__tests__/module-extension-facts.test.ts +0 -404
- package/src/lib/generators/__tests__/module-facts.extension-hosts.test.ts +0 -108
- package/src/lib/generators/__tests__/module-facts.owned-contracts.test.ts +0 -561
- package/src/lib/generators/__tests__/module-facts.source-links.test.ts +0 -119
- package/src/lib/generators/__tests__/module-override-targets.roundtrip.test.ts +0 -149
- package/src/lib/generators/__tests__/module-override-targets.test.ts +0 -304
- package/src/lib/generators/module-extension-facts.ts +0 -2365
- package/src/lib/generators/module-fact-sources.ts +0 -108
- package/src/lib/generators/module-override-targets.ts +0 -863
- package/src/lib/load-env.ts +0 -30
- package/src/lib/telemetry-init.ts +0 -461
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Shared Package — Standalone Developer Guide
|
|
2
|
+
|
|
3
|
+
`@open-mercato/shared` provides cross-cutting utilities, types, and DSL helpers. Import from here for infrastructure concerns — never from `@open-mercato/core`.
|
|
4
|
+
|
|
5
|
+
## Import Map
|
|
6
|
+
|
|
7
|
+
| Need | Import |
|
|
8
|
+
|------|--------|
|
|
9
|
+
| Client-side translations | `import { useT } from '@open-mercato/shared/lib/i18n/context'` |
|
|
10
|
+
| Server-side translations | `import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'` |
|
|
11
|
+
| Encrypted queries | `import { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'` |
|
|
12
|
+
| Boolean parsing | `import { parseBooleanToken, parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'` |
|
|
13
|
+
| Command pattern | `import { registerCommand } from '@open-mercato/shared/lib/commands'` |
|
|
14
|
+
| Safe entity flush | `import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'` |
|
|
15
|
+
| Data/Query engine types | `import type { DataEngine, QueryEngine } from '@open-mercato/shared/lib/data/engine'` |
|
|
16
|
+
| CRUD multi-ID filtering | `import { parseIdsParam, mergeIdFilter } from '@open-mercato/shared/lib/crud/ids'` |
|
|
17
|
+
| CRUD OpenAPI factory | `import { createCrudOpenApiFactory } from '@open-mercato/shared/lib/openapi/crud'` |
|
|
18
|
+
| Scoped API payloads | `import { withScopedPayload } from '@open-mercato/shared/lib/api/scoped'` |
|
|
19
|
+
| DI setup (Awilix) | `import { ... } from '@open-mercato/shared/lib/di'` |
|
|
20
|
+
| Custom field helpers | `import { splitCustomFieldPayload, normalizeCustomFieldValues } from '@open-mercato/shared/lib/custom-fields'` |
|
|
21
|
+
| DSL helpers | `import { defineLink, entityId, cf } from '@open-mercato/shared/modules/dsl'` |
|
|
22
|
+
| Event declarations | `import { createModuleEvents } from '@open-mercato/shared/modules/events'` |
|
|
23
|
+
| Module setup types | `import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'` |
|
|
24
|
+
| Search config types | `import type { SearchModuleConfig } from '@open-mercato/shared/modules/search'` |
|
|
25
|
+
| Widget injection position | `import { InjectionPosition } from '@open-mercato/shared/modules/widgets/injection-position'` |
|
|
26
|
+
| API interceptor types | `import type { ApiInterceptor } from '@open-mercato/shared/lib/crud/api-interceptor'` |
|
|
27
|
+
| Response enricher types | `import type { ResponseEnricher } from '@open-mercato/shared/lib/crud/response-enricher'` |
|
|
28
|
+
| Broadcast event check | `import { isBroadcastEvent } from '@open-mercato/shared/modules/events'` |
|
|
29
|
+
|
|
30
|
+
## i18n — All User-Facing Strings
|
|
31
|
+
|
|
32
|
+
Never hard-code user-facing text. Use locale files and translation helpers.
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
// Client-side (React components)
|
|
36
|
+
const t = useT()
|
|
37
|
+
return <span>{t('my_module.labels.title')}</span>
|
|
38
|
+
|
|
39
|
+
// Server-side (API routes, commands)
|
|
40
|
+
const { t } = await resolveTranslations()
|
|
41
|
+
const label = t('my_module.labels.title')
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Add translations to `i18n/<locale>.json` files in your module.
|
|
45
|
+
|
|
46
|
+
## Encryption — Query Encrypted Entities
|
|
47
|
+
|
|
48
|
+
MUST use these instead of raw `em.find`/`em.findOne` when the entity may contain encrypted fields:
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
const results = await findWithDecryption(em, 'Entity', filter, { tenantId, organizationId })
|
|
52
|
+
const record = await findOneWithDecryption(em, 'Entity', filter, { tenantId, organizationId })
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Boolean Parsing
|
|
56
|
+
|
|
57
|
+
For env vars and query params, never use `=== 'true'`:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
const isEnabled = parseBooleanToken(process.env.MY_FLAG) // true | false | undefined
|
|
61
|
+
const withDefault = parseBooleanWithDefault(query.active, true) // defaults to true
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Safe Entity Flush
|
|
65
|
+
|
|
66
|
+
When a command mutates entities across phases that include queries on the same `EntityManager`, use `withAtomicFlush` to prevent silent data loss:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
|
|
70
|
+
|
|
71
|
+
await withAtomicFlush(em, [
|
|
72
|
+
() => { record.name = 'New'; record.status = 'active' },
|
|
73
|
+
() => syncEntityTags(em, record, tags), // internal em.find() won't lose changes
|
|
74
|
+
], { transaction: true })
|
|
75
|
+
|
|
76
|
+
// Side effects AFTER the atomic flush
|
|
77
|
+
await emitCrudSideEffects({ ... })
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Never** run `em.find`/`em.findOne` between scalar mutations and `em.flush()` without `withAtomicFlush`.
|
|
81
|
+
|
|
82
|
+
## CRUD Multi-ID Filtering
|
|
83
|
+
|
|
84
|
+
Filter list APIs by multiple IDs using `?ids=uuid1,uuid2`:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
import { parseIdsParam, mergeIdFilter } from '@open-mercato/shared/lib/crud/ids'
|
|
88
|
+
const ids = parseIdsParam(query.ids) // string[] | undefined
|
|
89
|
+
const filter = mergeIdFilter(existingFilter, ids) // intersects with existing id filter
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Custom Field Helpers
|
|
93
|
+
|
|
94
|
+
When your entity uses custom fields:
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import { splitCustomFieldPayload, normalizeCustomFieldValues, normalizeCustomFieldResponse }
|
|
98
|
+
from '@open-mercato/shared/lib/custom-fields'
|
|
99
|
+
|
|
100
|
+
// In create/update: split cf:* fields from standard fields
|
|
101
|
+
const { standard, custom } = splitCustomFieldPayload(body)
|
|
102
|
+
|
|
103
|
+
// Normalize for storage
|
|
104
|
+
const normalized = normalizeCustomFieldValues(custom, fieldDefinitions)
|
|
105
|
+
|
|
106
|
+
// Normalize for API response
|
|
107
|
+
const response = normalizeCustomFieldResponse(record, customFields)
|
|
108
|
+
```
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
# UI Package — Standalone Developer Guide
|
|
2
|
+
|
|
3
|
+
`@open-mercato/ui` provides all admin/backend UI components. Use these instead of building from scratch.
|
|
4
|
+
|
|
5
|
+
## Key Imports
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
// Buttons (MUST use — never raw <button>)
|
|
9
|
+
import { Button } from '@open-mercato/ui/primitives/button'
|
|
10
|
+
import { IconButton } from '@open-mercato/ui/primitives/icon-button'
|
|
11
|
+
|
|
12
|
+
// CRUD forms
|
|
13
|
+
import { CrudForm, createCrud, updateCrud, deleteCrud } from '@open-mercato/ui/backend/crud'
|
|
14
|
+
import { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'
|
|
15
|
+
|
|
16
|
+
// API calls (MUST use — never raw fetch)
|
|
17
|
+
import { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
|
|
18
|
+
|
|
19
|
+
// Page structure
|
|
20
|
+
import { FormHeader, FormFooter } from '@open-mercato/ui/backend/forms'
|
|
21
|
+
import { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'
|
|
22
|
+
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
23
|
+
|
|
24
|
+
// Widget injection
|
|
25
|
+
import { useInjectionDataWidgets } from '@open-mercato/ui/backend/injection/useInjectionDataWidgets'
|
|
26
|
+
import { useInjectedMenuItems } from '@open-mercato/ui/backend/injection/useInjectedMenuItems'
|
|
27
|
+
import { mergeMenuItems } from '@open-mercato/ui/backend/injection/mergeMenuItems'
|
|
28
|
+
import { useRegisteredComponent } from '@open-mercato/ui/backend/injection/useRegisteredComponent'
|
|
29
|
+
import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
|
|
30
|
+
|
|
31
|
+
// Real-time events
|
|
32
|
+
import { useAppEvent } from '@open-mercato/ui/backend/injection/useAppEvent'
|
|
33
|
+
import { useOperationProgress } from '@open-mercato/ui/backend/injection/useOperationProgress'
|
|
34
|
+
|
|
35
|
+
// Custom fields
|
|
36
|
+
import { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## MUST Rules
|
|
40
|
+
|
|
41
|
+
1. **MUST use `Button`/`IconButton`** — never raw `<button>` elements
|
|
42
|
+
2. **MUST pass `type="button"` explicitly** on non-submit buttons (HTML defaults to `type="submit"`)
|
|
43
|
+
3. **MUST use `apiCall`/`apiCallOrThrow`** — never raw `fetch`
|
|
44
|
+
4. **MUST use `LoadingMessage`/`ErrorMessage`** for loading/error states
|
|
45
|
+
5. **MUST NOT hard-code user-facing strings** — use `useT()` for all labels
|
|
46
|
+
6. **Every dialog**: `Cmd/Ctrl+Enter` to submit, `Escape` to cancel
|
|
47
|
+
7. **Keep `pageSize`** at or below 100
|
|
48
|
+
|
|
49
|
+
## CrudForm — Create/Edit Flows
|
|
50
|
+
|
|
51
|
+
Default for all create/edit pages and dialog forms:
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
<CrudForm
|
|
55
|
+
entityId="my_module:my_entity"
|
|
56
|
+
mode="create" // or "edit"
|
|
57
|
+
fields={fields}
|
|
58
|
+
groups={groups}
|
|
59
|
+
onSubmit={async (data) => {
|
|
60
|
+
await createCrud('/api/my-module/items', data)
|
|
61
|
+
flash(t('my_module.created'), 'success')
|
|
62
|
+
}}
|
|
63
|
+
/>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
- Drive validation with Zod schemas; surface errors via `createCrudFormError(message, fieldErrors?)`
|
|
67
|
+
- Pass `entityIds` when custom fields are involved
|
|
68
|
+
- Use `createCrud`/`updateCrud`/`deleteCrud` for submit actions
|
|
69
|
+
- Keep fields and groups in memoized helpers
|
|
70
|
+
|
|
71
|
+
## Non-CrudForm Write Operations
|
|
72
|
+
|
|
73
|
+
When a page can't use `CrudForm`, wrap every write in `useGuardedMutation`:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
const { runMutation } = useGuardedMutation()
|
|
77
|
+
await runMutation({ operation: 'update', context: { entityId, recordId }, mutationPayload: data })
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## DataTable — List Views
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
<DataTable
|
|
84
|
+
columns={columns}
|
|
85
|
+
data={items}
|
|
86
|
+
extensionTableId="my-module-items" // stable ID for widget injection
|
|
87
|
+
rowClickActionIds={['edit', 'open']} // default
|
|
88
|
+
/>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
- Set `meta.truncate` and `meta.maxWidth` on columns for truncation
|
|
92
|
+
- Use `FilterBar`/`FilterOverlay` with async option loaders
|
|
93
|
+
- Use stable `extensionTableId` — widget injection spots depend on it
|
|
94
|
+
- Extension spots: `data-table:<tableId>:columns`, `:row-actions`, `:bulk-actions`, `:filters`
|
|
95
|
+
|
|
96
|
+
## Buttons — Quick Reference
|
|
97
|
+
|
|
98
|
+
| Use case | Component | Example |
|
|
99
|
+
|----------|-----------|---------|
|
|
100
|
+
| Button with text | `Button` | Save, Cancel, Create |
|
|
101
|
+
| Icon-only button | `IconButton` | Close, Settings, Trash |
|
|
102
|
+
| Link as button | `IconButton asChild` | `<IconButton asChild><Link href="...">...</Link></IconButton>` |
|
|
103
|
+
|
|
104
|
+
**Button variants**: `default`, `destructive`, `outline`, `secondary`, `ghost`, `muted`, `link`
|
|
105
|
+
**IconButton variants**: `outline` (default), `ghost`
|
|
106
|
+
|
|
107
|
+
## FormHeader Modes
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
// Edit mode (compact, used by CrudForm)
|
|
111
|
+
<FormHeader mode="edit" title="Edit Item" />
|
|
112
|
+
|
|
113
|
+
// Detail mode (large title with status badge)
|
|
114
|
+
<FormHeader mode="detail" title="Item #123" entityTypeLabel="Item" statusBadge={<Badge>Active</Badge>} menuActions={[...]} />
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Menu Injection
|
|
118
|
+
|
|
119
|
+
Load and merge injected menu items for sidebar/topbar surfaces:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
const injectedItems = useInjectedMenuItems('menu:sidebar:main')
|
|
123
|
+
const merged = mergeMenuItems(builtInItems, injectedItems)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Use `InjectionPosition` + `relativeTo` for deterministic placement.
|
|
127
|
+
|
|
128
|
+
## Real-Time Events
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
// Subscribe to server events (via DOM Event Bridge)
|
|
132
|
+
useAppEvent('my_module.entity.*', (event) => {
|
|
133
|
+
// event.id, event.payload — refresh data, show notifications
|
|
134
|
+
}, [dependencies])
|
|
135
|
+
|
|
136
|
+
// Track long-running operations
|
|
137
|
+
const progress = useOperationProgress('my_module.import.*')
|
|
138
|
+
// progress.status, progress.progress (0-100), progress.processedCount
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Flash Messages
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
flash(t('my_module.saved'), 'success')
|
|
145
|
+
flash(t('my_module.error'), 'error')
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Component Replacement
|
|
149
|
+
|
|
150
|
+
For replacement-aware surfaces, resolve components by handle:
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
const MyComponent = useRegisteredComponent('page:my-module:detail', DefaultDetailPage)
|
|
154
|
+
return <MyComponent {...props} />
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Portal UI
|
|
158
|
+
|
|
159
|
+
### Key Imports
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
// Portal hooks
|
|
163
|
+
import { useCustomerAuth } from '@open-mercato/ui/portal/hooks/useCustomerAuth'
|
|
164
|
+
import { useTenantContext } from '@open-mercato/ui/portal/hooks/useTenantContext'
|
|
165
|
+
import { usePortalAppEvent } from '@open-mercato/ui/portal/hooks/usePortalAppEvent'
|
|
166
|
+
import { usePortalEventBridge } from '@open-mercato/ui/portal/hooks/usePortalEventBridge'
|
|
167
|
+
import { usePortalInjectedMenuItems } from '@open-mercato/ui/portal/hooks/usePortalInjectedMenuItems'
|
|
168
|
+
import { usePortalNotifications } from '@open-mercato/ui/portal/hooks/usePortalNotifications'
|
|
169
|
+
import { usePortalDashboardWidgets } from '@open-mercato/ui/portal/hooks/usePortalDashboardWidgets'
|
|
170
|
+
|
|
171
|
+
// Portal layout
|
|
172
|
+
import { PortalShell } from '@open-mercato/ui/portal/PortalShell'
|
|
173
|
+
import { PortalProvider, usePortalContext } from '@open-mercato/ui/portal/PortalContext'
|
|
174
|
+
|
|
175
|
+
// Portal components
|
|
176
|
+
import {
|
|
177
|
+
PortalCard, PortalCardHeader, PortalStatRow, PortalCardDivider,
|
|
178
|
+
PortalPageHeader, PortalEmptyState, PortalFeatureCard,
|
|
179
|
+
PortalNotificationBell, PortalNotificationPanel,
|
|
180
|
+
} from '@open-mercato/ui/portal/components'
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Portal Hooks Reference
|
|
184
|
+
|
|
185
|
+
| Hook | Purpose |
|
|
186
|
+
|------|---------|
|
|
187
|
+
| `useCustomerAuth(orgSlug?)` | Customer auth state: `{ user, roles, resolvedFeatures, isPortalAdmin, loading, error, logout }` |
|
|
188
|
+
| `useTenantContext(orgSlug)` | Resolve tenant/org from URL slug: `{ tenantId, organizationId, organizationName, loading, error }` |
|
|
189
|
+
| `usePortalAppEvent(pattern, handler, deps?)` | Listen for portal SSE events by glob pattern (e.g., `'sales.order.*'`) |
|
|
190
|
+
| `usePortalEventBridge()` | Establish singleton SSE connection — mount once in shell/layout |
|
|
191
|
+
| `usePortalInjectedMenuItems(surfaceId)` | Load feature-gated menu items for portal nav surfaces: `{ items, isLoading }` |
|
|
192
|
+
| `usePortalNotifications()` | Poll portal notifications: `{ notifications, unreadCount, hasNew, isLoading, refresh, markAsRead, dismiss, markAllRead }` |
|
|
193
|
+
| `usePortalDashboardWidgets(spotId)` | Load UI injection widgets (with `Widget` component) for a portal spot: `{ widgets, isLoading, error }` |
|
|
194
|
+
|
|
195
|
+
### Portal Components
|
|
196
|
+
|
|
197
|
+
| Component | Props | Purpose |
|
|
198
|
+
|-----------|-------|---------|
|
|
199
|
+
| `PortalCard` | `children, className?` | Card container with border and padding |
|
|
200
|
+
| `PortalCardHeader` | `title, description?, label?, action?` | Card header with optional uppercase label and action slot |
|
|
201
|
+
| `PortalStatRow` | `label, value` | Key-value row inside a card (uppercase label, right-aligned value) |
|
|
202
|
+
| `PortalCardDivider` | — | Horizontal divider between stat rows |
|
|
203
|
+
| `PortalPageHeader` | `title, description?, label?, action?` | Page-level header with large title and action slot |
|
|
204
|
+
| `PortalEmptyState` | `title, description?, icon?, action?` | Dashed-border empty state with optional icon and CTA |
|
|
205
|
+
| `PortalFeatureCard` | `title, description?, icon?, href?, onClick?` | Feature grid card — renders as link, button, or static div |
|
|
206
|
+
| `PortalNotificationBell` | `t` | Header bell icon with unread badge |
|
|
207
|
+
| `PortalNotificationPanel` | — | Notification dropdown panel |
|
|
208
|
+
|
|
209
|
+
### Portal Page Structure
|
|
210
|
+
|
|
211
|
+
Every portal page is two files under `frontend/[orgSlug]/portal/<path>/`:
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
page.tsx # Client component ("use client")
|
|
215
|
+
page.meta.ts # PageMetadata — access control + sidebar nav
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Minimal page:
|
|
219
|
+
|
|
220
|
+
```tsx
|
|
221
|
+
"use client"
|
|
222
|
+
import { usePortalContext } from '@open-mercato/ui/portal/PortalContext'
|
|
223
|
+
import { PortalPageHeader } from '@open-mercato/ui/portal/components'
|
|
224
|
+
|
|
225
|
+
export default function MyPortalPage({ params }: { params: { orgSlug: string } }) {
|
|
226
|
+
const { auth } = usePortalContext()
|
|
227
|
+
const { user, resolvedFeatures } = auth
|
|
228
|
+
return <PortalPageHeader title="Orders" />
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Prefer `usePortalContext()` inside pages wrapped by `PortalLayoutShell` — it reads server-hydrated auth and avoids client loading flashes. Reach for `useCustomerAuth(orgSlug)` only when the server wrapper is unavailable.
|
|
233
|
+
|
|
234
|
+
Minimal `page.meta.ts`:
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
import type { PageMetadata } from '@open-mercato/shared/modules/registry'
|
|
238
|
+
|
|
239
|
+
export const metadata: PageMetadata = {
|
|
240
|
+
requireCustomerAuth: true,
|
|
241
|
+
requireCustomerFeatures: ['portal.orders.view'],
|
|
242
|
+
titleKey: 'portal.orders.title',
|
|
243
|
+
title: 'Orders',
|
|
244
|
+
nav: { label: 'Orders', labelKey: 'portal.nav.orders', group: 'main', order: 20 },
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export default metadata
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
- Public pages (login, signup, verify, forgot/reset-password): omit `requireCustomerAuth`; set `navHidden: true`.
|
|
251
|
+
- Authenticated pages without sidebar presence (detail/create/edit): set `requireCustomerAuth: true`, **omit** `nav`.
|
|
252
|
+
- Sidebar-visible pages: include a `nav` block. Feature-gated pages are automatically hidden when the user lacks grants.
|
|
253
|
+
|
|
254
|
+
Reference: `packages/core/src/modules/portal/frontend/[orgSlug]/portal/{dashboard,profile}/page.{tsx,meta.ts}`.
|
|
255
|
+
|
|
256
|
+
### Portal Feature-Gating Contract
|
|
257
|
+
|
|
258
|
+
Single source of truth: `requireCustomerFeatures` in `page.meta.ts`. The same list is enforced in three layers:
|
|
259
|
+
|
|
260
|
+
| Layer | Where | Effect |
|
|
261
|
+
|---|---|---|
|
|
262
|
+
| Page access | `apps/mercato/src/app/(frontend)/[...slug]/page.tsx` | Server-side gate via `CustomerRbacService.userHasAllFeatures()` — missing feature blocks render |
|
|
263
|
+
| Sidebar entry | `/api/customer_accounts/portal/nav` → `buildPortalNav()` at `packages/ui/src/portal/utils/nav.ts` | Same check — missing feature omits the entry |
|
|
264
|
+
| Injection widgets | `usePortalInjectedMenuItems` / `usePortalDashboardWidgets` | `/api/customer_accounts/portal/feature-check` + `hasAllFeatures()` — missing feature filters the widget |
|
|
265
|
+
|
|
266
|
+
Granting a customer role a feature (e.g. `portal.orders.view`) is sufficient to (a) reach the page, (b) see the sidebar entry, (c) see widgets gated by that feature. No separate menu-injection widget is required for sidebar presence when the page is backed by `page.meta.ts` with a `nav` block.
|
|
267
|
+
|
|
268
|
+
**MUST** resolve features via `hasAllFeatures` / `matchFeature` from `@open-mercato/shared/security/features`. Raw `Array.includes()` or `Set.has()` on feature arrays misses wildcards (`portal.*`) and is a bug.
|
|
269
|
+
|
|
270
|
+
Declare features in `acl.ts`; ship defaults per role via `defaultCustomerRoleFeatures` in `setup.ts`. Never rely on client-side checks alone as the access gate.
|
|
271
|
+
|
|
272
|
+
### Portal SPA CSRF Posture
|
|
273
|
+
|
|
274
|
+
Dual cookies set by login (`packages/core/src/modules/customer_accounts/api/login.ts`):
|
|
275
|
+
|
|
276
|
+
| Cookie | Contents | TTL | Flags |
|
|
277
|
+
|---|---|---|---|
|
|
278
|
+
| `customer_auth_token` | Short-lived JWT | 8h | `httpOnly`, `sameSite: 'lax'`, `secure` in prod, `path: '/'` |
|
|
279
|
+
| `customer_session_token` | Raw session token (hashed at rest) | 30d (env: `CUSTOMER_SESSION_TTL_DAYS`) | same as above |
|
|
280
|
+
|
|
281
|
+
Primary CSRF defense: `SameSite=lax` + same-origin deployment. No explicit CSRF token — the browser blocks cross-origin POSTs.
|
|
282
|
+
|
|
283
|
+
Rules:
|
|
284
|
+
- Use `apiCall` for every write — it uses `credentials: 'same-origin'` and sets JSON headers.
|
|
285
|
+
- Never expose either cookie to JS. `httpOnly` is load-bearing; do not add companion cookies that mirror session state.
|
|
286
|
+
- Never accept cross-origin POSTs on portal routes. Cross-origin use cases are explicit exceptions: per-tenant origin allowlist + CSRF token + re-auth.
|
|
287
|
+
- `sameSite: 'lax'` lets GET navigations carry cookies — keep all state-changing side effects behind POST/PUT/PATCH/DELETE.
|
|
288
|
+
- Logout (`api/portal/logout.ts`) clears both cookies with `maxAge: 0`. Mirror this shape for any new logout-style endpoints.
|
|
289
|
+
|
|
290
|
+
Concurrent sessions are capped at `MAX_CUSTOMER_SESSIONS_PER_USER` (default 5) in `customerSessionService.createSession()`. New sessions above the cap soft-delete the oldest active session.
|
|
291
|
+
|
|
292
|
+
### Portal XSS Discipline (Injected Widgets)
|
|
293
|
+
|
|
294
|
+
Third-party widgets render inside the authenticated portal and inherit user cookies. Enforce stricter discipline than first-party code because widgets load from arbitrary modules.
|
|
295
|
+
|
|
296
|
+
- **Forbidden**: `dangerouslySetInnerHTML` anywhere in portal injection widgets. Render structured data, not raw HTML.
|
|
297
|
+
- **Labels and user-facing text**: always through `useT()`; render as text children, never as HTML.
|
|
298
|
+
- **Icons**: Lucide components (`lucide-react`). No inline `<svg>` composed from user-controlled strings.
|
|
299
|
+
- **Asset URLs** (`src`, `href`, `action`, `srcDoc`): must not be user-controlled unless validated server-side against an allowlist.
|
|
300
|
+
- **No `eval`, `new Function`, `setTimeout(string)`**, or similar dynamic code paths.
|
|
301
|
+
- **Event-handler payloads** from SSE: validate shape (`isPortalBroadcastEvent` guards dispatch; never trust `event.data` to be well-formed without schema validation).
|
|
302
|
+
- **Styles**: no user-controlled strings in `style` props, CSS variables, or `className` built from untrusted input.
|
|
303
|
+
|
|
304
|
+
Prefer components that accept structured props over ones accepting `children` / `innerHTML` — the host keeps control of escaping.
|
|
305
|
+
|
|
306
|
+
### PortalShell Usage
|
|
307
|
+
|
|
308
|
+
```tsx
|
|
309
|
+
import { PortalShell } from '@open-mercato/ui/portal/PortalShell'
|
|
310
|
+
import { useCustomerAuth } from '@open-mercato/ui/portal/hooks/useCustomerAuth'
|
|
311
|
+
|
|
312
|
+
function MyPortalPage({ orgSlug }: { orgSlug: string }) {
|
|
313
|
+
const { user, logout } = useCustomerAuth(orgSlug)
|
|
314
|
+
return (
|
|
315
|
+
<PortalShell orgSlug={orgSlug} authenticated={!!user} onLogout={logout} enableEventBridge>
|
|
316
|
+
<PortalPageHeader title="My Orders" description="View and track your orders" />
|
|
317
|
+
{/* page content */}
|
|
318
|
+
</PortalShell>
|
|
319
|
+
)
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
When `PortalProvider` is mounted in a parent layout, `PortalShell` reads auth/tenant state from context automatically — props act as overrides or are used on public pages without a provider.
|
|
324
|
+
|
|
325
|
+
### Portal Widget Injection Spots (FROZEN)
|
|
326
|
+
|
|
327
|
+
| Spot ID | Purpose |
|
|
328
|
+
|---------|---------|
|
|
329
|
+
| `menu:portal:sidebar:main` | Main portal navigation items |
|
|
330
|
+
| `menu:portal:sidebar:account` | Account/settings navigation items |
|
|
331
|
+
| `menu:portal:header:actions` | Header action buttons |
|
|
332
|
+
| `menu:portal:user-dropdown` | User dropdown menu items |
|
|
333
|
+
| `portal:dashboard:sections` | Dashboard section cards |
|
|
334
|
+
| `portal:dashboard:profile` | Dashboard profile area |
|
|
335
|
+
| `portal:dashboard:sidebar` | Dashboard sidebar |
|
|
336
|
+
| `portal:<pageId>:before` | Before page content |
|
|
337
|
+
| `portal:<pageId>:after` | After page content |
|
|
338
|
+
|
|
339
|
+
### Portal Component Replacement Handles (FROZEN)
|
|
340
|
+
|
|
341
|
+
| Handle | Constant | Purpose |
|
|
342
|
+
|--------|----------|---------|
|
|
343
|
+
| `page:portal:layout` | `PORTAL_SHELL_HANDLE` | Entire portal shell |
|
|
344
|
+
| `section:portal:header` | `PORTAL_HEADER_HANDLE` | Header bar |
|
|
345
|
+
| `section:portal:footer` | `PORTAL_FOOTER_HANDLE` | Footer |
|
|
346
|
+
| `section:portal:sidebar` | `PORTAL_SIDEBAR_HANDLE` | Navigation sidebar |
|
|
347
|
+
| `section:portal:user-menu` | `PORTAL_USER_MENU_HANDLE` | User menu / logout area |
|