完成五人团队正式内用最小收口(07-18-small-team-internal-readiness)
这个提交包含在:
@@ -938,6 +938,69 @@ validation prevent repeated re-entry and reduce support/debugging cost.
|
||||
- Focused Playwright coverage for draft save, submit validation, unsaved-change
|
||||
protection and server-validation failure paths when changing form behavior.
|
||||
|
||||
## Scenario: Data-Router Unsaved-Change Protection
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
- Trigger: a React form can lose unsaved work through in-app navigation,
|
||||
browser back/forward, reload, closing its panel or switching records.
|
||||
|
||||
### 2. Signatures
|
||||
|
||||
- Router: `createBrowserRouter(...)` rendered through `RouterProvider`.
|
||||
- Navigation blocker: React Router `useBlocker(isDirty)`.
|
||||
- Reload/close guard: `beforeunload` while `isDirty === true`.
|
||||
- Shared UI contract: one unsaved-change dialog with continue-editing and
|
||||
discard actions.
|
||||
|
||||
### 3. Contracts
|
||||
|
||||
- Block navigation before the route or selected record changes; do not restore
|
||||
old UI state after navigation has already occurred.
|
||||
- `blocker.reset()` keeps the current route and form values. `blocker.proceed()`
|
||||
is called only after explicit discard confirmation.
|
||||
- Record switching and panel closing use the same pending-action contract as
|
||||
route navigation. Successful save or explicit discard clears dirty state.
|
||||
- Register `beforeunload` only while dirty and remove it during cleanup.
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
|
||||
- Clean form navigation -> continue without a dialog.
|
||||
- Dirty in-app navigation or browser back -> block and show one dialog.
|
||||
- Continue editing -> URL, record selection and values remain unchanged.
|
||||
- Discard -> execute exactly one pending navigation/selection and clear dirty.
|
||||
- Reload/close while dirty -> browser-level unload warning remains active.
|
||||
|
||||
### 5. Good/Base/Bad Cases
|
||||
|
||||
- Good: reimbursement, procurement, contract and asset forms reuse one guard
|
||||
while keeping their own save and dirty-field logic.
|
||||
- Base: a short non-editable filter form does not register the guard.
|
||||
- Bad: listen to history after navigation, then push the previous URL back; the
|
||||
user can see state flicker or lose form data before confirmation.
|
||||
|
||||
### 6. Tests Required
|
||||
|
||||
- Playwright covers link navigation, browser back, reload and record switch.
|
||||
- Assert cancel preserves URL and values; discard changes target once; save
|
||||
removes the warning.
|
||||
- Frontend typecheck and build after router or shared guard changes.
|
||||
|
||||
### 7. Wrong vs Correct
|
||||
|
||||
#### Wrong
|
||||
|
||||
```ts
|
||||
window.addEventListener("popstate", () => navigate(previousUrl));
|
||||
```
|
||||
|
||||
#### Correct
|
||||
|
||||
```ts
|
||||
const blocker = useBlocker(isDirty);
|
||||
// reset to keep editing; proceed only after explicit discard.
|
||||
```
|
||||
|
||||
## Convention: Detail Evidence Layout
|
||||
|
||||
**What**: Detail pages use one evidence-first structure so users can understand
|
||||
|
||||
在新工单中引用
屏蔽一个用户