@stackonward/cli 1.7.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 (69) 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/composables/useWebPush.ts +4 -0
  4. package/dist/resources/components/nuxt/site-source/app/plugins/web-push.ts +52 -0
  5. package/dist/resources/components/nuxt/site-source/app/web-push/worker.ts +8 -0
  6. package/dist/resources/components/registry.json +11 -1
  7. package/dist/resources/registry.json +15 -3
  8. package/dist/resources/templates/base/{.claude → .codex}/docs/architecture-template.md +1 -1
  9. package/dist/resources/templates/base/{.claude → .codex}/docs/architecture.md +1 -1
  10. package/dist/resources/templates/base/{.claude → .codex}/docs/index.md +3 -3
  11. package/dist/resources/templates/base/{.claude → .codex}/docs/verification.md +1 -1
  12. package/dist/resources/templates/base/AGENTS.md +24 -24
  13. package/dist/resources/templates/go-backend/{.claude → .codex}/docs/api-design.md +1 -1
  14. package/dist/resources/templates/go-backend/{.claude → .codex}/docs/architecture-template.md +1 -1
  15. package/dist/resources/templates/go-backend/{.claude → .codex}/docs/check-list.md +3 -3
  16. package/dist/resources/templates/go-backend/{.claude → .codex}/docs/database.md +1 -1
  17. package/dist/resources/templates/go-backend/{.claude → .codex}/docs/error-handling.md +1 -1
  18. package/dist/resources/templates/go-backend/{.claude → .codex}/docs/index.md +1 -1
  19. package/dist/resources/templates/go-backend/{.claude → .codex}/docs/logging.md +1 -1
  20. package/dist/resources/templates/go-backend/{.claude → .codex}/docs/mediagen-provider-onboarding-sop.md +2 -2
  21. package/dist/resources/templates/go-backend/{.claude → .codex}/docs/mediagen-provider-quick-reference.md +1 -1
  22. package/dist/resources/templates/go-backend/{.claude → .codex}/docs/module-standards.md +1 -1
  23. package/dist/resources/templates/go-backend/.dockerignore +1 -2
  24. package/dist/resources/templates/go-backend/AGENTS.md +19 -19
  25. package/dist/resources/templates/go-backend/README.md +5 -1
  26. package/dist/resources/templates/go-backend/deployments/onex/product-onboarding.example.json +201 -0
  27. package/dist/resources/templates/go-backend/docs/onex-notifications.md +85 -0
  28. package/dist/resources/templates/go-backend/docs/openapi-provenance.json +1 -1
  29. package/dist/resources/templates/go-backend/docs/runtime-provenance.json +23 -2
  30. package/dist/resources/templates/go-backend/docs/source-provenance.json +124 -6
  31. package/dist/resources/templates/go-backend/docs/standards-provenance.json +30 -30
  32. package/dist/resources/templates/go-backend/internal/common/middleware/cors.go +1 -0
  33. package/dist/resources/templates/go-backend/internal/common/middleware/middleware_test.go +1 -0
  34. package/dist/resources/templates/go-backend/internal/core/httpclient/safe_post.go +28 -0
  35. package/dist/resources/templates/go-backend/internal/core/httpclient/safe_post_test.go +56 -0
  36. package/dist/resources/templates/go-backend/internal/integrations/onex/v1/client.go +453 -0
  37. package/dist/resources/templates/go-backend/internal/integrations/onex/v1/client_test.go +772 -0
  38. package/dist/resources/templates/go-backend/internal/integrations/onex/v1/codec.go +33 -0
  39. package/dist/resources/templates/go-backend/internal/integrations/onex/v1/error.go +146 -0
  40. package/dist/resources/templates/go-backend/internal/integrations/onex/v1/error_notification_test.go +29 -0
  41. package/dist/resources/templates/go-backend/internal/integrations/onex/v1/notification/client.go +132 -0
  42. package/dist/resources/templates/go-backend/internal/integrations/onex/v1/notification/client_test.go +222 -0
  43. package/dist/resources/templates/go-backend/internal/integrations/onex/v1/notification/contracts.go +219 -0
  44. package/dist/resources/templates/go-backend/internal/integrations/onex/v1/resilience.go +23 -0
  45. package/dist/resources/templates/go-backend/internal/integrations/onex/v1/transport_policy.go +96 -0
  46. package/dist/resources/templates/go-backend/internal/products/README.md +1 -1
  47. package/dist/resources/templates/go-backend/internal/sharedevents/README.md +1 -1
  48. package/dist/resources/templates/go-backend/scripts/check-openapi-clean-deletion.sh +3 -3
  49. package/dist/resources/templates/mcp-server/.prettierignore +1 -0
  50. package/dist/resources/templates/nuxt/{.claude → .codex}/docs/stack.md +10 -0
  51. package/dist/resources/templates/nuxt/.dockerignore +1 -1
  52. package/dist/resources/templates/nuxt/.env.example +6 -0
  53. package/dist/resources/templates/nuxt/.prettierignore +2 -1
  54. package/dist/resources/templates/nuxt/DESIGN.md +1 -1
  55. package/dist/resources/templates/nuxt/README.md +8 -0
  56. package/dist/resources/templates/nuxt/nuxt.config.ts +13 -1
  57. package/dist/resources/templates/nuxt/package.json +4 -2
  58. package/package.json +1 -1
  59. package/dist/resources/templates/base/CLAUDE.md +0 -437
  60. package/dist/resources/templates/go-backend/CLAUDE.md +0 -1139
  61. /package/dist/resources/templates/base/{.claude → .codex}/docs/security-lifecycle.md +0 -0
  62. /package/dist/resources/templates/go-backend/{.claude → .codex}/docs/e2e-standards.md +0 -0
  63. /package/dist/resources/templates/go-backend/{.claude → .codex}/docs/event-runtime-boundary.md +0 -0
  64. /package/dist/resources/templates/go-backend/{.claude → .codex}/docs/identifier-contract.md +0 -0
  65. /package/dist/resources/templates/go-backend/{.claude → .codex}/docs/openapi-contract.md +0 -0
  66. /package/dist/resources/templates/go-backend/{.claude → .codex}/docs/payment-gateway-onboarding.md +0 -0
  67. /package/dist/resources/templates/go-backend/{.claude → .codex}/docs/platform-boundary.md +0 -0
  68. /package/dist/resources/templates/go-backend/{.claude → .codex}/docs/quality-gates.md +0 -0
  69. /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 |
@@ -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
 
@@ -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.7.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",