@stackonward/cli 1.6.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/dist/index.js +8 -8
  2. package/dist/index.js.map +1 -1
  3. package/dist/resources/components/nuxt/site-source/app/assets/css/site-shell.css +14 -2
  4. package/dist/resources/components/nuxt/site-source/app/components/content/CallToAction.vue +3 -1
  5. package/dist/resources/components/nuxt/site-source/app/components/content/Faq.vue +2 -2
  6. package/dist/resources/components/nuxt/site-source/app/components/content/Reviews.vue +2 -2
  7. package/dist/resources/components/nuxt/site-source/app/components/steps/Showcase.vue +2 -2
  8. package/dist/resources/components/nuxt/site-source/app/composables/useWebPush.ts +4 -0
  9. package/dist/resources/components/nuxt/site-source/app/plugins/web-push.ts +52 -0
  10. package/dist/resources/components/nuxt/site-source/app/theme/button.ts +18 -22
  11. package/dist/resources/components/nuxt/site-source/app/theme/index.ts +24 -0
  12. package/dist/resources/components/nuxt/site-source/app/theme/input.ts +32 -17
  13. package/dist/resources/components/nuxt/site-source/app/theme/modal.ts +1 -1
  14. package/dist/resources/components/nuxt/site-source/app/theme/select.ts +62 -18
  15. package/dist/resources/components/nuxt/site-source/app/theme/textarea.ts +73 -1
  16. package/dist/resources/components/nuxt/site-source/app/web-push/worker.ts +8 -0
  17. package/dist/resources/components/registry.json +11 -1
  18. package/dist/resources/registry.json +15 -3
  19. package/dist/resources/templates/base/{.claude → .codex}/docs/architecture-template.md +1 -1
  20. package/dist/resources/templates/base/{.claude → .codex}/docs/architecture.md +1 -1
  21. package/dist/resources/templates/base/{.claude → .codex}/docs/index.md +3 -3
  22. package/dist/resources/templates/base/{.claude → .codex}/docs/verification.md +1 -1
  23. package/dist/resources/templates/base/AGENTS.md +24 -24
  24. package/dist/resources/templates/go-backend/{.claude → .codex}/docs/api-design.md +1 -1
  25. package/dist/resources/templates/go-backend/{.claude → .codex}/docs/architecture-template.md +1 -1
  26. package/dist/resources/templates/go-backend/{.claude → .codex}/docs/check-list.md +3 -3
  27. package/dist/resources/templates/go-backend/{.claude → .codex}/docs/database.md +1 -1
  28. package/dist/resources/templates/go-backend/{.claude → .codex}/docs/error-handling.md +1 -1
  29. package/dist/resources/templates/go-backend/{.claude → .codex}/docs/index.md +1 -1
  30. package/dist/resources/templates/go-backend/{.claude → .codex}/docs/logging.md +1 -1
  31. package/dist/resources/templates/go-backend/{.claude → .codex}/docs/mediagen-provider-onboarding-sop.md +2 -2
  32. package/dist/resources/templates/go-backend/{.claude → .codex}/docs/mediagen-provider-quick-reference.md +1 -1
  33. package/dist/resources/templates/go-backend/{.claude → .codex}/docs/module-standards.md +1 -1
  34. package/dist/resources/templates/go-backend/.dockerignore +1 -2
  35. package/dist/resources/templates/go-backend/AGENTS.md +19 -19
  36. package/dist/resources/templates/go-backend/README.md +5 -1
  37. package/dist/resources/templates/go-backend/deployments/onex/product-onboarding.example.json +201 -0
  38. package/dist/resources/templates/go-backend/docs/onex-notifications.md +85 -0
  39. package/dist/resources/templates/go-backend/docs/openapi-provenance.json +1 -1
  40. package/dist/resources/templates/go-backend/docs/runtime-provenance.json +23 -2
  41. package/dist/resources/templates/go-backend/docs/source-provenance.json +124 -6
  42. package/dist/resources/templates/go-backend/docs/standards-provenance.json +30 -30
  43. package/dist/resources/templates/go-backend/internal/common/middleware/cors.go +1 -0
  44. package/dist/resources/templates/go-backend/internal/common/middleware/middleware_test.go +1 -0
  45. package/dist/resources/templates/go-backend/internal/core/httpclient/safe_post.go +28 -0
  46. package/dist/resources/templates/go-backend/internal/core/httpclient/safe_post_test.go +56 -0
  47. package/dist/resources/templates/go-backend/internal/integrations/onex/v1/client.go +453 -0
  48. package/dist/resources/templates/go-backend/internal/integrations/onex/v1/client_test.go +772 -0
  49. package/dist/resources/templates/go-backend/internal/integrations/onex/v1/codec.go +33 -0
  50. package/dist/resources/templates/go-backend/internal/integrations/onex/v1/error.go +146 -0
  51. package/dist/resources/templates/go-backend/internal/integrations/onex/v1/error_notification_test.go +29 -0
  52. package/dist/resources/templates/go-backend/internal/integrations/onex/v1/notification/client.go +132 -0
  53. package/dist/resources/templates/go-backend/internal/integrations/onex/v1/notification/client_test.go +222 -0
  54. package/dist/resources/templates/go-backend/internal/integrations/onex/v1/notification/contracts.go +219 -0
  55. package/dist/resources/templates/go-backend/internal/integrations/onex/v1/resilience.go +23 -0
  56. package/dist/resources/templates/go-backend/internal/integrations/onex/v1/transport_policy.go +96 -0
  57. package/dist/resources/templates/go-backend/internal/products/README.md +1 -1
  58. package/dist/resources/templates/go-backend/internal/sharedevents/README.md +1 -1
  59. package/dist/resources/templates/go-backend/scripts/check-openapi-clean-deletion.sh +3 -3
  60. package/dist/resources/templates/mcp-server/.prettierignore +1 -0
  61. package/dist/resources/templates/nuxt/{.claude → .codex}/docs/stack.md +17 -1
  62. package/dist/resources/templates/nuxt/.dockerignore +1 -1
  63. package/dist/resources/templates/nuxt/.env.example +6 -0
  64. package/dist/resources/templates/nuxt/.prettierignore +2 -1
  65. package/dist/resources/templates/nuxt/DESIGN.md +55 -10
  66. package/dist/resources/templates/nuxt/README.md +8 -0
  67. package/dist/resources/templates/nuxt/nuxt.config.ts +13 -1
  68. package/dist/resources/templates/nuxt/package.json +4 -2
  69. package/package.json +1 -1
  70. package/dist/resources/templates/base/CLAUDE.md +0 -437
  71. package/dist/resources/templates/go-backend/CLAUDE.md +0 -1139
  72. /package/dist/resources/templates/base/{.claude → .codex}/docs/security-lifecycle.md +0 -0
  73. /package/dist/resources/templates/go-backend/{.claude → .codex}/docs/e2e-standards.md +0 -0
  74. /package/dist/resources/templates/go-backend/{.claude → .codex}/docs/event-runtime-boundary.md +0 -0
  75. /package/dist/resources/templates/go-backend/{.claude → .codex}/docs/identifier-contract.md +0 -0
  76. /package/dist/resources/templates/go-backend/{.claude → .codex}/docs/openapi-contract.md +0 -0
  77. /package/dist/resources/templates/go-backend/{.claude → .codex}/docs/payment-gateway-onboarding.md +0 -0
  78. /package/dist/resources/templates/go-backend/{.claude → .codex}/docs/platform-boundary.md +0 -0
  79. /package/dist/resources/templates/go-backend/{.claude → .codex}/docs/quality-gates.md +0 -0
  80. /package/dist/resources/templates/mcp-server/{.claude → .codex}/docs/stack.md +0 -0
@@ -0,0 +1,219 @@
1
+ package notification
2
+
3
+ import "time"
4
+
5
+ // InboxQuery 保留原生查询的全部过滤条件,不替产品选择 scope。
6
+ type InboxQuery struct {
7
+ UserID int64 `json:"user_id"`
8
+ Categories []string `json:"categories,omitempty"`
9
+ TargetRef string `json:"target_ref,omitempty"`
10
+ TargetScope string `json:"target_scope"`
11
+ ReadStatus string `json:"read_status,omitempty"`
12
+ DeliveryIDs []int64 `json:"delivery_ids,omitempty"`
13
+ MessageIDs []int64 `json:"message_ids,omitempty"`
14
+ Page int `json:"page,omitempty"`
15
+ PageSize int `json:"page_size,omitempty"`
16
+ }
17
+
18
+ // InboxMessage 是平台投影,不包含产品导航解释。
19
+ type InboxMessage struct {
20
+ DeliveryID int64 `json:"delivery_id"`
21
+ MessageID int64 `json:"message_id"`
22
+ MessageNo string `json:"message_no"`
23
+ SourceVersion int64 `json:"source_version"`
24
+ Title string `json:"title"`
25
+ Subtitle *string `json:"subtitle,omitempty"`
26
+ Content string `json:"content"`
27
+ CoverURL *string `json:"cover_url,omitempty"`
28
+ ActionURL *string `json:"action_url,omitempty"`
29
+ Category string `json:"category"`
30
+ Metadata map[string]string `json:"metadata"`
31
+ Status string `json:"status"`
32
+ TargetRef *string `json:"target_ref,omitempty"`
33
+ TargetKind *string `json:"target_kind,omitempty"`
34
+ ReadAt *time.Time `json:"read_at,omitempty"`
35
+ DeliveredAt time.Time `json:"delivered_at"`
36
+ CreatedAt time.Time `json:"created_at"`
37
+ UpdatedAt time.Time `json:"updated_at"`
38
+ }
39
+
40
+ // InboxPage 保留分页总数和分类未读汇总,供产品自行投影。
41
+ type InboxPage struct {
42
+ Items []InboxMessage `json:"items"`
43
+ Total int64 `json:"total"`
44
+ UnreadCount int64 `json:"unread_count"`
45
+ UnreadSummary map[string]int64 `json:"unread_summary"`
46
+ }
47
+
48
+ // Unread 保留全局与分类未读事实。
49
+ type Unread struct {
50
+ UnreadCount int64 `json:"unread_count"`
51
+ UnreadSummary map[string]int64 `json:"unread_summary"`
52
+ }
53
+
54
+ // MessageVersion 仅确认用户已经查看的消息版本。
55
+ type MessageVersion struct {
56
+ MessageNo string `json:"message_no"`
57
+ SourceVersion int64 `json:"source_version"`
58
+ }
59
+
60
+ // AcknowledgeMessagesRequest 绑定可信接收人与精确消息版本。
61
+ type AcknowledgeMessagesRequest struct {
62
+ UserID int64 `json:"user_id"`
63
+ Messages []MessageVersion `json:"messages"`
64
+ }
65
+
66
+ // Acknowledgement 返回原子确认后的未读快照。
67
+ type Acknowledgement struct {
68
+ Updated int64 `json:"updated"`
69
+ UnreadCount int64 `json:"unread_count"`
70
+ UnreadSummary map[string]int64 `json:"unread_summary"`
71
+ }
72
+
73
+ // DeviceRegistration 的账号与会话来自可信服务端上下文。
74
+ type DeviceRegistration struct {
75
+ UserID int64 `json:"user_id"`
76
+ SessionID int64 `json:"session_id"`
77
+ ApplicationCode string `json:"application_code"`
78
+ Environment string `json:"environment"`
79
+ InstallationID string `json:"installation_id"`
80
+ InstallationKey string `json:"installation_key"`
81
+ RegistrationRevision int64 `json:"registration_revision"`
82
+ DeviceToken string `json:"device_token"`
83
+ Platform string `json:"platform"`
84
+ Provider string `json:"provider"`
85
+ AppVersion string `json:"app_version,omitempty"`
86
+ DeviceModel string `json:"device_model,omitempty"`
87
+ OSVersion string `json:"os_version,omitempty"`
88
+ }
89
+
90
+ // DeviceRevocation 使用安装能力,不要求保留已退出账号的身份。
91
+ type DeviceRevocation struct {
92
+ ApplicationCode string `json:"application_code"`
93
+ Environment string `json:"environment"`
94
+ InstallationID string `json:"installation_id"`
95
+ InstallationKey string `json:"installation_key"`
96
+ RegistrationRevision int64 `json:"registration_revision"`
97
+ }
98
+
99
+ // Device 保留平台安全设备投影,不返回安装密钥或推送令牌。
100
+ type Device struct {
101
+ ID int64 `json:"id"`
102
+ InstallationID string `json:"installation_id"`
103
+ ApplicationCode string `json:"application_code"`
104
+ Environment string `json:"environment"`
105
+ RegistrationRevision int64 `json:"registration_revision"`
106
+ Provider string `json:"provider"`
107
+ Platform string `json:"platform"`
108
+ AppVersion *string `json:"app_version"`
109
+ DeviceModel *string `json:"device_model"`
110
+ OSVersion *string `json:"os_version"`
111
+ Status string `json:"status"`
112
+ LastActiveAt *time.Time `json:"last_active_at"`
113
+ CreatedAt time.Time `json:"created_at"`
114
+ }
115
+
116
+ // DeviceList 保留当前接收人的有效设备集合。
117
+ type DeviceList struct {
118
+ Devices []Device `json:"devices"`
119
+ }
120
+
121
+ // QuietHours 表达调用方选择的静默时段,不设置产品默认值。
122
+ type QuietHours struct {
123
+ Enabled bool `json:"enabled"`
124
+ Start string `json:"start"`
125
+ End string `json:"end"`
126
+ Timezone string `json:"timezone"`
127
+ }
128
+
129
+ // PushPreferences 同时保留渠道、分类和静默时段政策。
130
+ type PushPreferences struct {
131
+ Enabled bool `json:"enabled"`
132
+ Categories map[string]bool `json:"categories"`
133
+ QuietHours QuietHours `json:"quiet_hours"`
134
+ }
135
+
136
+ // PreferenceQuery 显式携带产品分类与默认值。
137
+ type PreferenceQuery struct {
138
+ UserID int64 `json:"user_id"`
139
+ Categories []string `json:"categories"`
140
+ Defaults PushPreferences `json:"defaults"`
141
+ }
142
+
143
+ // PreferenceUpdate 提交完整的产品作用域渠道偏好。
144
+ type PreferenceUpdate struct {
145
+ UserID int64 `json:"user_id"`
146
+ Preferences PushPreferences `json:"preferences"`
147
+ }
148
+
149
+ // TestPushRequest 仅供显式诊断调用,不参与构造或配置检查。
150
+ type TestPushRequest struct {
151
+ UserID int64 `json:"user_id"`
152
+ Title string `json:"title"`
153
+ Body string `json:"body"`
154
+ DeviceIDs []int64 `json:"device_ids"`
155
+ }
156
+
157
+ // TestPushResult 仅表示诊断发送的逐设备处理结果。
158
+ type TestPushResult struct {
159
+ Succeeded int `json:"succeeded"`
160
+ Failed int `json:"failed"`
161
+ }
162
+
163
+ // SourceEvent 由产品持久事件 owner 提供,不在协议层合成。
164
+ type SourceEvent struct {
165
+ ID string `json:"id"`
166
+ Type string `json:"type"`
167
+ SubjectRef string `json:"subject_ref"`
168
+ SubjectVersion int64 `json:"subject_version"`
169
+ OccurredAt time.Time `json:"occurred_at"`
170
+ }
171
+
172
+ // Recipient 使用平台公开用户引用,不接受调用方提供内部账号 ID。
173
+ type Recipient struct {
174
+ UserPublicID string `json:"user_public_id"`
175
+ }
176
+
177
+ // InputContext 保留产品 owner 冻结的输入上下文。
178
+ type InputContext struct {
179
+ Type string `json:"type"`
180
+ Reference string `json:"reference"`
181
+ }
182
+
183
+ // Target 保留产品 target,不推导导航 URL。
184
+ type Target struct {
185
+ Kind string `json:"kind"`
186
+ Reference string `json:"reference"`
187
+ }
188
+
189
+ // DispatchStartRequest 不允许指定模板、提供商或发件身份。
190
+ type DispatchStartRequest struct {
191
+ SourceEvent SourceEvent `json:"source_event"`
192
+ CommandKey string `json:"command_key"`
193
+ Channel string `json:"channel"`
194
+ Context InputContext `json:"context"`
195
+ Recipient Recipient `json:"recipient"`
196
+ Locale string `json:"locale"`
197
+ Target Target `json:"target"`
198
+ BusinessVariables map[string]any `json:"business_variables"`
199
+ }
200
+
201
+ // DispatchStartResult 表示持久入队与重放状态,不宣称设备已收到。
202
+ type DispatchStartResult struct {
203
+ CommandUID string `json:"command_uid"`
204
+ Status string `json:"status"`
205
+ ExpiresAt time.Time `json:"expires_at"`
206
+ Replay bool `json:"replay"`
207
+ }
208
+
209
+ // SourceAdvanceRequest 仅推进事项版本,不携带发送内容。
210
+ type SourceAdvanceRequest struct {
211
+ SourceEvent SourceEvent `json:"source_event"`
212
+ Recipient Recipient `json:"recipient"`
213
+ }
214
+
215
+ // SourceAdvanceResult 返回平台当前接受的事项版本。
216
+ type SourceAdvanceResult struct {
217
+ Status string `json:"status"`
218
+ SourceVersion int64 `json:"source_version"`
219
+ }
@@ -0,0 +1,23 @@
1
+ package v1
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "regexp"
7
+ "strings"
8
+ )
9
+
10
+ var dependencyOperationPartPattern = regexp.MustCompile(`^[a-z][a-z0-9_]{1,63}$`)
11
+
12
+ // DependencyExecutor 在共享 transport 内按依赖操作隔离 bulkhead 与 circuit。
13
+ type DependencyExecutor interface {
14
+ Execute(context.Context, string, func(context.Context) error) (bool, error)
15
+ }
16
+
17
+ func dependencyOperationKey(dependency, operation string) (string, error) {
18
+ if dependency != strings.TrimSpace(dependency) || operation != strings.TrimSpace(operation) ||
19
+ !dependencyOperationPartPattern.MatchString(dependency) || !dependencyOperationPartPattern.MatchString(operation) {
20
+ return "", fmt.Errorf("OneX dependency operation is invalid")
21
+ }
22
+ return dependency + "." + operation, nil
23
+ }
@@ -0,0 +1,96 @@
1
+ package v1
2
+
3
+ import (
4
+ "context"
5
+ "math"
6
+ "math/rand/v2"
7
+ "net/http"
8
+ "strconv"
9
+ "time"
10
+ )
11
+
12
+ const maximumSafeReadRetries = 10
13
+
14
+ // SafeReadRetryConfig 定义仅适用于 GET 的有界内联重试策略。
15
+ type SafeReadRetryConfig struct {
16
+ MaxRetries int
17
+ InitialBackoff time.Duration
18
+ MaxBackoff time.Duration
19
+ JitterRatio float64
20
+ }
21
+
22
+ // RetryWaiter 隔离重试等待,使 transport 合同可以使用确定性时钟验证。
23
+ type RetryWaiter interface {
24
+ Wait(ctx context.Context, delay time.Duration) error
25
+ }
26
+
27
+ type timerRetryWaiter struct{}
28
+
29
+ func (timerRetryWaiter) Wait(ctx context.Context, delay time.Duration) error {
30
+ timer := time.NewTimer(delay)
31
+ defer timer.Stop()
32
+ select {
33
+ case <-ctx.Done():
34
+ return ctx.Err()
35
+ case <-timer.C:
36
+ return nil
37
+ }
38
+ }
39
+
40
+ func validateSafeReadRetryConfig(config SafeReadRetryConfig) error {
41
+ if config.MaxRetries < 0 || config.MaxRetries > maximumSafeReadRetries {
42
+ return NewValidationError("onex_safe_read_retry_limit_invalid")
43
+ }
44
+ if config.MaxRetries == 0 {
45
+ return nil
46
+ }
47
+ if config.InitialBackoff <= 0 || config.MaxBackoff < config.InitialBackoff || config.JitterRatio < 0 || config.JitterRatio > 1 {
48
+ return NewValidationError("onex_safe_read_retry_config_invalid")
49
+ }
50
+ return nil
51
+ }
52
+
53
+ func shouldRetrySafeReadStatus(status int) bool {
54
+ switch status {
55
+ case http.StatusTooManyRequests, http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout:
56
+ return true
57
+ default:
58
+ return false
59
+ }
60
+ }
61
+
62
+ func safeReadRetryDelay(config SafeReadRetryConfig, retryNumber int, retryAfter string, now time.Time) time.Duration {
63
+ if retryAfterDelay, ok := parseRetryAfter(retryAfter, now); ok {
64
+ return retryAfterDelay
65
+ }
66
+ delay := float64(config.InitialBackoff) * math.Pow(2, float64(retryNumber-1))
67
+ if delay > float64(config.MaxBackoff) {
68
+ delay = float64(config.MaxBackoff)
69
+ }
70
+ if config.JitterRatio > 0 {
71
+ minimum := delay * (1 - config.JitterRatio)
72
+ delay = minimum + rand.Float64()*(delay-minimum) //nolint:gosec // 仅用于重试抖动,不生成安全令牌。
73
+ }
74
+ return time.Duration(delay)
75
+ }
76
+
77
+ func parseRetryAfter(value string, now time.Time) (time.Duration, bool) {
78
+ if value == "" {
79
+ return 0, false
80
+ }
81
+ if seconds, err := strconv.ParseInt(value, 10, 32); err == nil {
82
+ if seconds < 0 {
83
+ return 0, false
84
+ }
85
+ return time.Duration(seconds) * time.Second, true
86
+ }
87
+ when, err := http.ParseTime(value)
88
+ if err != nil {
89
+ return 0, false
90
+ }
91
+ delay := when.Sub(now)
92
+ if delay < 0 {
93
+ delay = 0
94
+ }
95
+ return delay, true
96
+ }
@@ -1,5 +1,5 @@
1
1
  # Product modules
2
2
 
3
- Each module owns product use cases, models and persistence. Select Full DDD, Simplified DDD or Flat using [module standards](../../.claude/docs/module-standards.md); the directory name describes its business responsibility.
3
+ Each module owns product use cases, models and persistence. Select Full DDD, Simplified DDD or Flat using [module standards](../../.codex/docs/module-standards.md); the directory name describes its business responsibility.
4
4
 
5
5
  Compose modules through Wire in `cmd/`. Depend on generic infrastructure and consumer-defined integration ports. Platform authentication, asset execution and payment services remain upstream owners; access their published contracts without querying their internal tables.
@@ -2,4 +2,4 @@
2
2
 
3
3
  Declare cross-module event names and typed payloads here. Producers and consumers use the same pointer payload contract, with resource identity and audit metadata in the event envelope.
4
4
 
5
- Register persisted payload factories and explicit event schema versions when adding durable delivery. Domain state transitions remain in the owning product module. See [event runtime boundaries](../../.claude/docs/event-runtime-boundary.md) and [backend standards](../../CLAUDE.md).
5
+ Register persisted payload factories and explicit event schema versions when adding durable delivery. Domain state transitions remain in the owning product module. See [event runtime boundaries](../../.codex/docs/event-runtime-boundary.md) and [backend standards](../../AGENTS.md).
@@ -27,10 +27,10 @@ fail_if_matches "Swagger annotations remain" '^\s*//\s*@[[:alpha:]]' "$BACKEND_D
27
27
  fail_if_matches "Swagger-only struct tags remain" '(^|[[:space:]`])(swaggertype|example|format):"' "$BACKEND_DIR" --glob '*.go'
28
28
  fail_if_matches "Swagger runtime registry reads remain" 'swag\.ReadDoc' "$BACKEND_DIR" --glob '*.go'
29
29
  fail_if_matches "Retired Swagger commands remain" 'make swag|generate-swagger|swagger_route_inventory' \
30
- "$BACKEND_DIR/Makefile" "$BACKEND_DIR/CLAUDE.md" "$BACKEND_DIR/.claude/docs" \
30
+ "$BACKEND_DIR/Makefile" "$BACKEND_DIR/AGENTS.md" "$BACKEND_DIR/.codex/docs" \
31
31
  --glob '!**/worktrees/**'
32
32
  fail_if_matches "Retired Swagger guidance remains" 'Swagger/security|Swagger/API docs' \
33
- "$BACKEND_DIR/CLAUDE.md" "$BACKEND_DIR/.claude/docs"
33
+ "$BACKEND_DIR/AGENTS.md" "$BACKEND_DIR/.codex/docs"
34
34
  fail_if_matches "Retired Swagger documentation pipelines remain" \
35
35
  'swaggo/swag|CMS_SWAGGER_URL|/swagger/doc\.json|Go Swagger' "$BACKEND_DIR/docs" --glob '*.md'
36
36
 
@@ -38,7 +38,7 @@ for path in \
38
38
  "$BACKEND_DIR/scripts/generate-swagger.sh" \
39
39
  "$BACKEND_DIR/scripts/swagger_route_inventory.py" \
40
40
  "$BACKEND_DIR/scripts/swagger_route_inventory_test.py" \
41
- "$BACKEND_DIR/.claude/docs/swagger.md"
41
+ "$BACKEND_DIR/.codex/docs/swagger.md"
42
42
  do
43
43
  if [[ -e "$path" ]]; then
44
44
  echo "retired Swagger artifact remains: ${path#"$BACKEND_DIR/"}" >&2
@@ -45,6 +45,16 @@ ESLint composes the Nuxt-generated flat configuration with `@stackonward/eslint-
45
45
 
46
46
  Never place OAuth secrets, service credentials or provider API keys here. An enabled client feature does not prove backend credentials or permission are configured. Validate numeric input at its actual consumer rather than relying on `Number.parseInt` alone.
47
47
 
48
+ ### Optional Web Push
49
+
50
+ <web_push_contract>
51
+
52
+ `runtimeConfig.public.webPush` owns `NUXT_PUBLIC_WEB_PUSH_ENABLED`, `API_ORIGIN`, `PRODUCT_CODE`, `APPLICATION_CODE`, `TOPIC_KEY` and `DATABASE_NAME` under the same `NUXT_PUBLIC_WEB_PUSH_` prefix. The capability MUST remain disabled by default. The optional `@stackonward/nuxt-site-shell/web-push` module bundles the editable `app/web-push/worker.ts` into a same-origin script; building that asset NEVER grants permission or registers a subscription.
53
+
54
+ MUST consume browser/worker primitives from `@stackonward/web-push`, coordination from `@stackonward/composables`, and the named HTTP/storage protocol from `@stackonward/onex-web-push`. Product consent copy, branding, navigation targets and public identifiers remain in the application. MUST preserve an existing database namespace and record key when upgrading; those records contain the capability required for withdrawal. NEVER send cookies/account authorization, follow API redirects, automatically regrant consent, or couple messaging to optional analytics. Locale aliases are validated by the backend owner, not a copied frontend locale enum. Browser permission and real delivery require separate acceptance.
55
+
56
+ </web_push_contract>
57
+
48
58
  ## II. Project Structure and Imports
49
59
 
50
60
  | Owner | Responsibility |
@@ -372,6 +382,12 @@ const emit = defineEmits<{ submit: [] }>();
372
382
 
373
383
  Use Tailwind utilities and theme tokens. Scoped CSS is for behavior that utilities cannot express cleanly. Dark/light modes must use the same semantic tokens; never fix one page with a hardcoded light background that breaks dark mode.
374
384
 
385
+ ### Control Geometry and Scene Selection
386
+
387
+ **MUST** follow [DESIGN.md](../../DESIGN.md#control-geometry-and-visual-scenarios) for control sizes, text/line height, padding, radii, widths and focus treatment. Marketing CTAs and product UI share `UiButton` and the existing Nuxt UI themes. Marketing sections select prominent actions and own their wider layout; product navigation, workbenches and forms select compact or form-sized controls from the same scale.
388
+
389
+ Use `size` for the shared geometry and `block`/`fullWidth` for a full-width button. Keep section-specific CTA minimum widths at their section owner. **FORBIDDEN**: a second button implementation, marketing minimum widths in the generic theme or arbitrary per-page height/font/radius overrides for an existing size. Editable mobile fields retain the theme's readable text size; compact icon actions retain their accessible target.
390
+
375
391
  ### Icon Design Contract
376
392
 
377
393
  **MUST** read [DESIGN.md](../../DESIGN.md) for icon geometry, the size scale, semantic colors and acceptance. The installed system uses local Lucide functional icons and a separate `brand` collection. `UiIcon` owns standalone `name`, `size?: string | number` and `label?: string`; Nuxt UI controls retain their internal icon slots and use the same provider.
@@ -677,7 +693,7 @@ Read [Architecture](architecture.md), [Security and Lifecycle](security-lifecycl
677
693
  - [ ] Private configuration never enters public payloads.
678
694
  - [ ] Every visible action maps to a real implementation and error path.
679
695
  - [ ] All applicable NX-S/A/T/C/H/E/M/P/U rules are checked.
680
- - [ ] Design coverage includes children, overlays, icon sizes/names/targets and loading/empty/error/forbidden states.
696
+ - [ ] Design coverage includes children, overlays, scene-appropriate control geometry, icon sizes/names/targets and loading/empty/error/forbidden states.
681
697
  - [ ] `pnpm typecheck` and `pnpm build` pass for code/contract changes.
682
698
  - [ ] Browser and network evidence proves the touched interactions.
683
699
  - [ ] No uninstalled lint/test/authorization capability is claimed.
@@ -1,6 +1,6 @@
1
1
  .git
2
2
  .github
3
- .claude
3
+ .codex
4
4
  .husky
5
5
  .nuxt
6
6
  node_modules
@@ -1,5 +1,11 @@
1
1
  NUXT_PUBLIC_API_BASE_URL=
2
2
  NUXT_PUBLIC_API_TIMEOUT=30000
3
+ NUXT_PUBLIC_WEB_PUSH_ENABLED=false
4
+ NUXT_PUBLIC_WEB_PUSH_API_ORIGIN=
5
+ NUXT_PUBLIC_WEB_PUSH_PRODUCT_CODE=
6
+ NUXT_PUBLIC_WEB_PUSH_APPLICATION_CODE=
7
+ NUXT_PUBLIC_WEB_PUSH_TOPIC_KEY=
8
+ NUXT_PUBLIC_WEB_PUSH_DATABASE_NAME="{{projectName}}-web-push"
3
9
  NUXT_PUBLIC_SITE_URL=
4
10
  NUXT_PUBLIC_SITE_ENV=development
5
11
  NUXT_PUBLIC_SITE_NAME="{{projectName}}"
@@ -1,4 +1,5 @@
1
- .claude
1
+ .codex
2
+ AGENTS.md
2
3
  .nuxt
3
4
  .output
4
5
  coverage
@@ -1,6 +1,6 @@
1
1
  # Nuxt Design Standard
2
2
 
3
- This document defines the visual contract for the application-owned Nuxt UI foundation. Read it with the [Nuxt Engineering Standard](.claude/docs/stack.md) before changing icons, controls or theme defaults. Application branding remains in its own assets and semantic color tokens.
3
+ This document defines the visual contract for the application-owned Nuxt UI foundation. Read it with the [Nuxt Engineering Standard](.codex/docs/stack.md) before changing icons, controls or theme defaults. Application branding remains in its own assets and semantic color tokens.
4
4
 
5
5
  ## Design Decisions
6
6
 
@@ -13,18 +13,61 @@ This document defines the visual contract for the application-owned Nuxt UI foun
13
13
 
14
14
  ## Ownership
15
15
 
16
- | Owner | Contract |
17
- | ------------------------------------------------ | ------------------------------------------------------------------------------------------- |
18
- | `app/assets/css/site-shell.css` | `--site-icon-*` size tokens, `size-icon-*` Tailwind aliases and semantic colors |
19
- | `app/theme/icon.ts` | Nuxt Icon defaults: 20px token, SVG mode, non-shrinking alignment and decorative attributes |
20
- | `app/app.config.ts` | Composes `icon` defaults and the Nuxt UI theme |
21
- | `app/components/ui/Icon.vue` | Typed `name`, `size` and accessible `label` adapter |
22
- | `app/theme/button.ts`, `input.ts`, `checkbox.ts` | Internal icon slots and the control's geometry |
23
- | `nuxt.config.ts` | Locally bundled `lucide`, `brand` SVG collection and `fallbackToApi: false` |
24
- | `app/assets/icons/brand/` | Reviewed third-party identity assets |
16
+ | Owner | Contract |
17
+ | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
18
+ | `app/assets/css/site-shell.css` | `--site-control-height-*`, icon sizes, radius tokens, Tailwind aliases and semantic colors |
19
+ | `app/theme/icon.ts` | Nuxt Icon defaults: 20px token, SVG mode, non-shrinking alignment and decorative attributes |
20
+ | `app/app.config.ts` | Composes `icon` defaults and the Nuxt UI theme |
21
+ | `app/components/ui/Icon.vue` | Typed `name`, `size` and accessible `label` adapter |
22
+ | `app/theme/button.ts`, `input.ts`, `select.ts`, `textarea.ts`, `checkbox.ts` | Internal icon slots and the control's geometry |
23
+ | `nuxt.config.ts` | Locally bundled `lucide`, `brand` SVG collection and `fallbackToApi: false` |
24
+ | `app/assets/icons/brand/` | Reviewed third-party identity assets |
25
25
 
26
26
  Registry-installed source belongs to this application. **MUST** change the owning token or component when a repeated pattern changes. A one-location size choice stays at its caller. **FORBIDDEN**: adding a second icon component, runtime registry, icon font or icon library for an existing Lucide capability.
27
27
 
28
+ ## Control Geometry and Visual Scenarios
29
+
30
+ Marketing and product UI use the same `UiButton`, Nuxt UI controls and theme tokens. **MUST** choose the size and composition for the action's context; **FORBIDDEN**: separate marketing/product button implementations or campaign dimensions in the generic button default.
31
+
32
+ | Scenario | Controls | Typography and layout ownership |
33
+ | -------------------------------- | ----------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
34
+ | Marketing conversion | Section CTAs use `2xl` for prominence, with a section-owned minimum width of 200px and `max-w-full`. | Content sections own their larger headings, supporting copy, generous spacing and responsive content width. |
35
+ | Product navigation and workbench | Use `sm` for compact labeled actions and `md` for ordinary controls. Buttons fit their content. | App header, sidebar and editor owners control density, alignment and available width; controls retain the shared geometry. |
36
+ | Product authentication and forms | Use `lg` when the form needs a larger field and submission target. Select `block` for a full-width submit action. | The form owns field width, label/help spacing and validation space; it does not inherit a marketing CTA's minimum width. |
37
+
38
+ The control scale uses `--site-control-height-*` tokens and `h-control-*` utilities. Values below assume a 16px root; height, text and padding use rem. Text shows font size / line height. Button labels use medium weight. Input/select padding is the base padding on each side; icon slots add their own reserved space.
39
+
40
+ | Size | Button height | Input/select height | Text | Button horizontal padding | Input/select horizontal padding | Icon |
41
+ | -------------- | ------------- | ------------------- | --------- | ------------------------- | ------------------------------- | ---- |
42
+ | `xs` | 32px | 32px | 12 / 16px | 12px | 12px | 16px |
43
+ | `sm` | 36px | 36px | 14 / 20px | 16px | 12px | 16px |
44
+ | `md` (default) | 42px | 42px | 14 / 20px | 20px | 14px | 20px |
45
+ | `lg` | 48px | 48px | 16 / 24px | 24px | 16px | 20px |
46
+ | `xl` | 52px | 52px | 18 / 28px | 28px | 18px | 24px |
47
+ | `2xl` | 56px | — | 18 / 28px | 32px | — | 24px |
48
+
49
+ Editable inputs and textareas use 16px text below the `md` breakpoint unless the caller explicitly opts into the framework's `fixed` typography behavior. Their desktop typography follows the table. Select triggers retain their selected size. Textareas use rows/content height, not the single-line height scale; their vertical padding belongs to `app/theme/textarea.ts`.
50
+
51
+ Ordinary buttons have intrinsic width with no shared minimum or fixed width. `block` or `fullWidth` explicitly fills the available width. Marketing CTA widths belong to `content/CallToAction.vue`, `content/Faq.vue`, `content/Reviews.vue` and `steps/Showcase.vue`; page widths and heading scales remain at their layout/section owner. Link buttons retain text-flow height. Square icon buttons keep at least 44 × 44px targets even when their selected size is smaller.
52
+
53
+ Buttons, text inputs, selects and textareas use `--site-radius-md` through `rounded-ml` (10px); non-fullscreen dialogs use `--site-radius-lg` through `rounded-2ml` (20px). **MUST** reuse these radius tokens instead of adding a different radius to each field or button. Primary and neutral outline buttons and ordinary outlined fields use one 1px ring, without a second border. Primary solid buttons and primary outlined fields expose a 2px keyboard-focus outline; primary outline buttons use a 2px focus ring. Other semantic variants retain their theme-owned focus behavior. Focus treatment must remain visible and distinct from the resting edge.
54
+
55
+ Custom token utilities **MUST** be registered in `app/theme/index.ts` through `tv.twMergeConfig.extend.theme`: `radius` owns `ml`/`2ml`, and `spacing` owns the `control-*`/`icon-*` scales. This lets Nuxt UI merge framework defaults, theme values and caller overrides consistently. **FORBIDDEN**: relying on CSS source order to resolve conflicting size or radius utilities.
56
+
57
+ ✅ **Select shared sizes; the caller owns emphasis and width.**
58
+
59
+ ```vue
60
+ <UiButton size="sm" color="primary" variant="solid">Sign in</UiButton>
61
+ <UiButton size="lg" block type="submit">Continue</UiButton>
62
+ <UiButton size="2xl" class="min-w-[200px] max-w-full">Start creating</UiButton>
63
+ ```
64
+
65
+ ❌ **A generic width mandate and per-page sizing bypass both scene owners.**
66
+
67
+ ```vue
68
+ <UiButton class="h-[39px] min-w-[200px] rounded-[7px] text-[15px]">Save</UiButton>
69
+ ```
70
+
28
71
  ## Functional Icon Geometry
29
72
 
30
73
  Functional icons **MUST** use `i-lucide-*` names from the installed collection. Their design uses a square 24 × 24 viewBox, 2-unit centered strokes, rounded open-path caps and rounded joins. Preserve the family's edge clearance, internal spacing and visual balance; identical CSS dimensions alone do not establish equal optical weight. These constraints follow the [Lucide specification](https://lucide.dev/contribute/icons/specification).
@@ -136,6 +179,8 @@ Use the existing locale owner for labels, visible focus on controls and the comp
136
179
 
137
180
  ### Acceptance Checklist
138
181
 
182
+ - [ ] Marketing CTAs and product actions use the same controls with the appropriate size and caller-owned width.
183
+ - [ ] Actual control heights, typography, padding, radii, border/focus states and narrow-view input text match the shared contract.
139
184
  - [ ] Functional icons come from the installed Lucide family; brand assets remain separate.
140
185
  - [ ] The 20px default, selected size tokens and explicit `size` override render correctly without competing size declarations.
141
186
  - [ ] Source geometry and proportional strokes are preserved; no arbitrary per-icon offsets or global SVG overrides exist.
@@ -30,6 +30,14 @@ pnpm dlx @stackonward/cli analytics generate \
30
30
 
31
31
  Import the generated JSON into a selected GTM web-container workspace, preview the merge, and publish the reviewed version. The generated Google Tag disables automatic page views because the Nuxt integration owns SPA page-view dispatch.
32
32
 
33
+ ## Optional Web Push
34
+
35
+ Web Push is disabled by default. The generated `app/plugins/web-push.ts` composes the public browser primitives, Vue controller and named OneX adapter; `app/web-push/worker.ts` is bundled by `@stackonward/nuxt-site-shell/web-push` into the same-origin `push-sw.js`. The worker source is editable product code, not an unbundled public script.
36
+
37
+ Configure the public `NUXT_PUBLIC_WEB_PUSH_*` settings in `.env.example` only after the matching OneX product, application, allowed origin, consent topic and VAPID provider are configured. Keep the generated database namespace stable after users subscribe. Product code owns consent wording, the visible subscribe/withdraw controls, notification fallback title and business destinations. There is no automatic visitor subscription, and analytics consent does not authorize Web Push.
38
+
39
+ `useWebPush()` exposes `state`, `subscribe(locale, event)`, `revoke(event)` and `refresh()`. Call subscribe and revoke from real user click or keyboard events, surface the controller's safe error state, and retain recovery actions when a request has an unknown outcome. Locale is forwarded to the server for validation and normalization. Browser permission, subscription persistence, provider delivery and notification-click behavior require separate browser/device acceptance; a successful build does not prove delivery.
40
+
33
41
  ## Engineering workflow
34
42
 
35
43
  ```bash
@@ -6,7 +6,11 @@ const gtmId = process.env.NUXT_PUBLIC_GTM_ID || "";
6
6
 
7
7
  export default defineNuxtConfig({
8
8
  extends: ["@stackonward/nuxt-site-shell"],
9
- modules: ["@nuxt/eslint", "@stackonward/google-tag-manager-nuxt"],
9
+ modules: [
10
+ "@nuxt/eslint",
11
+ "@stackonward/google-tag-manager-nuxt",
12
+ ["@stackonward/nuxt-site-shell/web-push", { entry: "app/web-push/worker.ts" }],
13
+ ],
10
14
  compatibilityDate: "2025-07-15",
11
15
  css: ["~/assets/css/site-shell.css"],
12
16
  i18n: {
@@ -56,6 +60,14 @@ export default defineNuxtConfig({
56
60
  },
57
61
  runtimeConfig: {
58
62
  public: {
63
+ webPush: {
64
+ enabled: false,
65
+ apiOrigin: "",
66
+ productCode: "",
67
+ applicationCode: "",
68
+ topicKey: "",
69
+ databaseName: "{{projectName}}-web-push",
70
+ },
59
71
  api: {
60
72
  baseUrl: process.env.NUXT_PUBLIC_API_BASE_URL || "",
61
73
  timeout: Number.parseInt(process.env.NUXT_PUBLIC_API_TIMEOUT || "30000", 10),
@@ -28,11 +28,13 @@
28
28
  "check": "pnpm format:check && pnpm lint && pnpm typecheck && pnpm test && pnpm build"
29
29
  },
30
30
  "dependencies": {
31
+ "@stackonward/web-push": "^0.0.1",
32
+ "@stackonward/onex-web-push": "^0.0.1",
31
33
  "@stackonward/api-client": "^0.0.3",
32
34
  "@stackonward/google-tag-manager-nuxt": "^0.3.3",
33
35
  "@stackonward/nuxt-layer-base": "^0.0.4",
34
- "@stackonward/nuxt-site-shell": "^0.3.2",
35
- "@stackonward/composables": "^0.0.2"
36
+ "@stackonward/nuxt-site-shell": "^0.4.0",
37
+ "@stackonward/composables": "^0.1.0"
36
38
  },
37
39
  "devDependencies": {
38
40
  "@commitlint/cli": "^21.2.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackonward/cli",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "description": "StackOnward CLI for creating projects and installing reusable components and snippets",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",