feat: new
This commit is contained in:
28
openspec/changes/add-h5-popup-candidate-flow/proposal.md
Normal file
28
openspec/changes/add-h5-popup-candidate-flow/proposal.md
Normal file
@@ -0,0 +1,28 @@
|
||||
## Why
|
||||
|
||||
The August iteration adds backend-driven H5 popups. `GET /api/c/v1/popup-candidates` returns at most one candidate (risk-exchange or operation) for the current page and asset; risk-exchange candidates require the user to submit a shipping address through `POST /api/c/v1/risk-exchanges/{asset_id}/address`. Popups are persisted as personal notifications, so the existing notification contracts change: list items gain `popup_snapshot` and two new `type` values, unread count includes popups, and a popup is only dismissed by marking its notification read (`PUT /api/c/v1/notifications/{id}/read`). The current H5 client-side priority re-sort contradicts the new fixed backend ordering and must be removed.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Query `GET /api/c/v1/popup-candidates` once per page entry on `home` (index), `package_purchase` (package-order), and `asset_wallet_recharge` (my-wallet); `asset_detail` is explicitly out of scope
|
||||
- Render the returned candidate (title/body) in a shared `PopupCandidate` component and keep the popup silent when no candidate exists
|
||||
- Map `action_type` with a frontend whitelist only: `package_purchase` -> package-order page, `asset_wallet_recharge` -> my-wallet page; empty value shows no action
|
||||
- Dismiss or act on a popup only by marking the notification read through `PUT /api/c/v1/notifications/{id}/read`
|
||||
- Risk-exchange popups show a shipping address form and submit it with the candidate `asset_id` (never derived from the H5 identifier); success shows the exchange number and status
|
||||
- Treat invisible assets (HTTP 400 / `code=1180`) uniformly as resource-invisible with no UI branching
|
||||
- Keep the backend-fixed notification ordering (remove client-side priority re-sort); honor the unread badge contract; include the notification id in mark-read requests
|
||||
- Never use `read-all` inside the popup display chain
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `h5-popup-candidate`: Define once-per-entry popup candidate query, rendering, whitelist action mapping, and read-mark dismissal
|
||||
- `h5-risk-exchange-address`: Define the risk-exchange shipping address form and idempotent submission
|
||||
|
||||
### Modified Capabilities
|
||||
- `personal-notification-list`: Align the personal notification list/unread contracts with popup notifications
|
||||
|
||||
## Impact
|
||||
|
||||
- Affected code: new `api/modules/popup.js`, new `components/PopupCandidate.vue`, `pages/index/index.vue`, `pages/package-order/package-order.vue`, `pages/my-wallet/my-wallet.vue`, `pages/notifications/notifications.vue`, `api/modules/notification.js`
|
||||
- Backend contracts: C-end popup/risk-exchange/notification endpoints only; admin endpoints are out of scope
|
||||
@@ -0,0 +1,52 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: H5 SHALL query popup candidates once per page entry
|
||||
The system SHALL call `GET /api/c/v1/popup-candidates` exactly once per page entry with the current page and asset identifier, and SHALL render the returned candidate or stay silent when none exists. The query SHALL NOT be prefetched, polled, or repeated inside one entry.
|
||||
|
||||
#### Scenario: Candidate exists on page entry
|
||||
- **WHEN** the user enters `home`, `package_purchase`, or `asset_wallet_recharge` and the backend returns a candidate
|
||||
- **THEN** the system SHALL show the candidate title/body popup
|
||||
|
||||
#### Scenario: No candidate on page entry
|
||||
- **WHEN** the backend returns an empty candidate
|
||||
- **THEN** the system SHALL show no popup
|
||||
- **AND** the page SHALL continue its normal flow
|
||||
|
||||
#### Scenario: Same notification id within one day
|
||||
- **WHEN** a later page entry returns the same `notification_id` already handled this session
|
||||
- **THEN** the system SHALL NOT re-popup the same notification
|
||||
|
||||
### Requirement: Popup actions SHALL use a frontend whitelist
|
||||
The system SHALL map `action_type` through a frontend whitelist only and SHALL NOT accept URLs or routes from the backend. `package_purchase` routes to the package purchase page, `asset_wallet_recharge` routes to the wallet page, and an empty value shows no action.
|
||||
|
||||
#### Scenario: package_purchase action
|
||||
- **WHEN** the candidate `action_type` is `package_purchase`
|
||||
- **THEN** the popup SHALL navigate to the package purchase page
|
||||
|
||||
#### Scenario: asset_wallet_recharge action
|
||||
- **WHEN** the candidate `action_type` is `asset_wallet_recharge`
|
||||
- **THEN** the popup SHALL navigate to the asset wallet page
|
||||
|
||||
#### Scenario: No action
|
||||
- **WHEN** `action_type` is empty
|
||||
- **THEN** the popup SHALL show no action button
|
||||
|
||||
### Requirement: Popup dismissal SHALL mark the notification read
|
||||
The system SHALL call `PUT /api/c/v1/notifications/{id}/read` with the candidate `notification_id` when the popup is dismissed or when its action is taken. Popup closing and action-taking SHALL NOT use `read-all`.
|
||||
|
||||
#### Scenario: User closes the popup
|
||||
- **WHEN** the user closes the popup
|
||||
- **THEN** the notification SHALL be marked read
|
||||
|
||||
#### Scenario: User takes the popup action
|
||||
- **WHEN** the user taps the action button
|
||||
- **THEN** the notification SHALL be marked read
|
||||
- **AND** the mapped page SHALL open
|
||||
|
||||
### Requirement: Resource-invisible responses SHALL be silent
|
||||
The system SHALL treat HTTP 400 / `code=1180` responses uniformly as resource-invisible and SHALL NOT branch UI, show special prompts, or distinguish asset-missing from not-owned states.
|
||||
|
||||
#### Scenario: Invisible asset on popup query
|
||||
- **WHEN** the popup query returns an invisible-asset error
|
||||
- **THEN** the system SHALL show no popup
|
||||
- **AND** the page SHALL continue its normal flow
|
||||
@@ -0,0 +1,28 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Risk-exchange popup SHALL collect a shipping address
|
||||
The system SHALL present a shipping address form when the candidate `popup_type` is `risk_exchange`, collecting recipient name, recipient phone, and full address, and SHALL submit them to `POST /api/c/v1/risk-exchanges/{asset_id}/address` using the candidate `asset_id` (never derived from the H5 identifier).
|
||||
|
||||
#### Scenario: Valid address submission
|
||||
- **WHEN** the user fills the required fields and submits
|
||||
- **THEN** the risk-exchange request SHALL be submitted with the candidate `asset_id`
|
||||
- **AND** the system SHALL show the returned exchange number and status
|
||||
|
||||
#### Scenario: Invalid or incomplete fields
|
||||
- **WHEN** the user submits empty or invalid fields
|
||||
- **THEN** the system SHALL block submission and prompt for the missing fields
|
||||
|
||||
### Requirement: Address submission SHALL be idempotent
|
||||
The system SHALL NOT provide an edit-address UI. Repeated or concurrent submissions return the first-created exchange order and address without overwriting.
|
||||
|
||||
#### Scenario: Repeated submission
|
||||
- **WHEN** the user submits the address again for the same asset
|
||||
- **THEN** the system SHALL return the first-created exchange order and address
|
||||
|
||||
### Requirement: Risk-exchange failures SHALL keep the user in place
|
||||
The system SHALL keep the address form open on failure and treat invisible-asset failures uniformly without UI branching.
|
||||
|
||||
#### Scenario: Submission failure
|
||||
- **WHEN** the address submission fails
|
||||
- **THEN** the system SHALL keep the form open
|
||||
- **AND** the user SHALL be able to retry
|
||||
@@ -0,0 +1,36 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Personal notification list SHALL preserve backend ordering
|
||||
The system SHALL display personal notifications in the backend-fixed order (created time and notification id descending) and SHALL NOT re-sort items client-side by severity or priority.
|
||||
|
||||
#### Scenario: Backend returns a fixed order
|
||||
- **WHEN** the notification list is loaded
|
||||
- **THEN** the items SHALL be rendered in the returned order
|
||||
|
||||
### Requirement: Notification items SHALL pass through popup fields and types
|
||||
The system SHALL render items with the new `type` values `h5.popup.risk_exchange` and `h5.popup.operation` (category `system`) and SHALL keep `popup_snapshot` available for popup-related actions without treating it as a route.
|
||||
|
||||
#### Scenario: List contains a popup notification
|
||||
- **WHEN** an item has `type` `h5.popup.risk_exchange` or `h5.popup.operation`
|
||||
- **THEN** the item SHALL render as a normal system-category notification
|
||||
|
||||
### Requirement: Unread badge SHALL reflect popup notifications
|
||||
The system SHALL count popup notifications in the unread badge and collapse the badge to `99+` above 99 per the `display_count` contract.
|
||||
|
||||
#### Scenario: Popup notification unread
|
||||
- **WHEN** a popup notification is unread
|
||||
- **THEN** the unread badge SHALL increase accordingly
|
||||
|
||||
### Requirement: Mark-read SHALL include the notification id and be idempotent
|
||||
The system SHALL send the notification `id` in the mark-read request body and SHALL treat success responses idempotently.
|
||||
|
||||
#### Scenario: Mark-read an already-read notification
|
||||
- **WHEN** the notification is already read or owned by another customer
|
||||
- **THEN** the request SHALL still succeed
|
||||
|
||||
### Requirement: read-all SHALL NOT close popups implicitly in the popup chain
|
||||
The system SHALL NOT call `read-all` from any popup display path because it also marks today's popup notifications read.
|
||||
|
||||
#### Scenario: Popup display flow
|
||||
- **WHEN** the popup candidate flow runs
|
||||
- **THEN** the system SHALL NOT call `read-all`
|
||||
36
openspec/changes/add-h5-popup-candidate-flow/tasks.md
Normal file
36
openspec/changes/add-h5-popup-candidate-flow/tasks.md
Normal file
@@ -0,0 +1,36 @@
|
||||
## 1. API Layer
|
||||
|
||||
- [ ] 1.1 Add `popupApi.getCandidate(page, identifier)` for `GET /api/c/v1/popup-candidates`
|
||||
- [ ] 1.2 Add `popupApi.submitRiskExchangeAddress(assetId, params)` for `POST /api/c/v1/risk-exchanges/{asset_id}/address`
|
||||
- [ ] 1.3 Include the notification `id` in the mark-read request body (R3)
|
||||
|
||||
## 2. PopupCandidate Component
|
||||
|
||||
- [ ] 2.1 Query the candidate once per mount and emit `empty` when none exists
|
||||
- [ ] 2.2 Render title/body and hide silently on resource-invisible responses
|
||||
- [ ] 2.3 Map `action_type` through the whitelist and navigate from the mapped page
|
||||
- [ ] 2.4 Mark the notification read on dismiss and on action (R3)
|
||||
- [ ] 2.5 Deduplicate by `notification_id` within a session so same-day repeats do not re-popup
|
||||
- [ ] 2.6 Provide the risk-exchange address form, submission, and result view
|
||||
|
||||
## 3. Page Wiring
|
||||
|
||||
- [ ] 3.1 Wire `home` in `pages/index/index.vue` with priority over the legacy unread-notification popup
|
||||
- [ ] 3.2 Wire `package_purchase` in `pages/package-order/package-order.vue`
|
||||
- [ ] 3.3 Wire `asset_wallet_recharge` in `pages/my-wallet/my-wallet.vue`
|
||||
- [ ] 3.4 Leave `asset_detail` unwired
|
||||
|
||||
## 4. Notification Contract Alignment
|
||||
|
||||
- [ ] 4.1 Remove client-side priority re-sort and rely on the backend ordering
|
||||
- [ ] 4.2 Align the category label map with `approval/expiry/sync/system`
|
||||
- [ ] 4.3 Keep the unread badge consistent with popup counting and `99+` display
|
||||
- [ ] 4.4 Keep `read-all` out of the popup display chain
|
||||
|
||||
## 5. Regression Verification
|
||||
|
||||
- [ ] 5.1 Operation popup with action navigates to the mapped page and marks read
|
||||
- [ ] 5.2 Risk-exchange popup submits the address and shows the exchange number
|
||||
- [ ] 5.3 Repeat entry with the same `notification_id` does not re-popup
|
||||
- [ ] 5.4 Invisible asset shows no popup and no error branch
|
||||
- [ ] 5.5 Notification center renders new `h5.popup.*` types without layout breakage
|
||||
Reference in New Issue
Block a user