Upload beautify
This commit is contained in:
123
docs/feed-rollout-runbook.md
Normal file
123
docs/feed-rollout-runbook.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# Feed Rollout Runbook (clip-cosine-v2, prod set 1)
|
||||
|
||||
## Scope
|
||||
|
||||
- Candidate: `clip-cosine-v2` with weights `w1=0.52, w2=0.23, w3=0.15, w4=0.10`
|
||||
- Baseline: `clip-cosine-v1`
|
||||
- Rollout gates: `10% -> 50% -> 100%`
|
||||
- Temporary policy: `save_rate` is informational only until save-event schema reliability is confirmed in production.
|
||||
|
||||
## Pre-flight checks
|
||||
|
||||
1. Confirm config values:
|
||||
- `DISCOVERY_ROLLOUT_ENABLED=true`
|
||||
- `DISCOVERY_ROLLOUT_BASELINE_ALGO_VERSION=clip-cosine-v1`
|
||||
- `DISCOVERY_ROLLOUT_CANDIDATE_ALGO_VERSION=clip-cosine-v2`
|
||||
- `DISCOVERY_ROLLOUT_ACTIVE_GATE=g10`
|
||||
- `DISCOVERY_FORCE_ALGO_VERSION` is empty
|
||||
2. Confirm candidate weights are active in `config/discovery.php` and env overrides.
|
||||
3. Confirm ingestion health for discovery events:
|
||||
- `event_id` populated for all new events
|
||||
- `favorite` and `download` events present in `user_discovery_events`
|
||||
4. Run daily aggregation:
|
||||
- `php artisan analytics:aggregate-feed --date=YYYY-MM-DD`
|
||||
|
||||
## Gate progression
|
||||
|
||||
### Gate 1: 10%
|
||||
|
||||
- Set: `DISCOVERY_ROLLOUT_ACTIVE_GATE=g10`
|
||||
- Observe for at least 2-3 days with minimum sample volume.
|
||||
- Required checks:
|
||||
- CTR delta vs baseline
|
||||
- Long-dwell-share delta vs baseline
|
||||
- Diversity concentration delta vs baseline
|
||||
- Save-rate trend (informational only)
|
||||
|
||||
Promote to 50% only if no rollback trigger fires and no persistent warning trend is present.
|
||||
|
||||
### Gate 2: 50%
|
||||
|
||||
- Set: `DISCOVERY_ROLLOUT_ACTIVE_GATE=g50`
|
||||
- Observe for 3-5 days with stable daily traffic.
|
||||
- Apply same checks and thresholds.
|
||||
|
||||
Promote to 100% only with at least 2 consecutive healthy days.
|
||||
|
||||
### Gate 3: 100%
|
||||
|
||||
- Set: `DISCOVERY_ROLLOUT_ACTIVE_GATE=g100`
|
||||
- Keep baseline available for rapid rollback via force toggle.
|
||||
|
||||
## Monitoring thresholds (candidate vs baseline)
|
||||
|
||||
- CTR:
|
||||
- Warning: drop >= 3%
|
||||
- Rollback: drop >= 5% (or >= 10% in a single severe window)
|
||||
- Long dwell share (`(dwell_30_120 + dwell_120_plus) / clicks`):
|
||||
- Warning: drop >= 4%
|
||||
- Rollback: drop >= 8% (or >= 12% in a single severe window)
|
||||
- Diversity concentration (e.g. top-author/top-category share, near-duplicate concentration):
|
||||
- Warning: rise >= 10%
|
||||
- Rollback: rise >= 15%
|
||||
|
||||
## Rollback actions
|
||||
|
||||
### Immediate rollback (fastest)
|
||||
|
||||
- Set `DISCOVERY_FORCE_ALGO_VERSION=clip-cosine-v1`
|
||||
- Reload config/cache as needed in your deployment flow.
|
||||
- Verify feed responses show `meta.algo_version=clip-cosine-v1`.
|
||||
|
||||
### Standard rollback
|
||||
|
||||
- Set `DISCOVERY_ROLLOUT_ACTIVE_GATE=g10` (or disable rollout)
|
||||
- Keep candidate enabled only for controlled validation traffic.
|
||||
|
||||
## Save-event schema note and fix
|
||||
|
||||
Observed issue class in mixed environments: save-event writes can fail if discovery event schema differs from code expectations (e.g., `meta`/`metadata` drift, required `event_id`).
|
||||
|
||||
Implemented fix path:
|
||||
|
||||
- Ingestion now always writes `event_id` and inserts schema-aware metadata (`meta` if present, otherwise `metadata` if present).
|
||||
- Keep `DISCOVERY_EVAL_SAVE_RATE_INFORMATIONAL=true` until production confirms stable save-event ingestion.
|
||||
|
||||
Validation query examples:
|
||||
|
||||
- Save events by day:
|
||||
- `SELECT event_date, COUNT(*) FROM user_discovery_events WHERE event_type IN ('favorite','download') GROUP BY event_date ORDER BY event_date DESC;`
|
||||
- Null/empty event id check:
|
||||
- `SELECT COUNT(*) FROM user_discovery_events WHERE event_id IS NULL OR event_id = '';`
|
||||
|
||||
## Daily operator checklist
|
||||
|
||||
1. Run feed aggregation for the previous day.
|
||||
2. Run evaluator and compare commands:
|
||||
- `php artisan analytics:evaluate-feed-weights --from=YYYY-MM-DD --to=YYYY-MM-DD --json`
|
||||
- `php artisan analytics:compare-feed-ab clip-cosine-v1 clip-cosine-v2 --from=YYYY-MM-DD --to=YYYY-MM-DD --json`
|
||||
3. Record deltas for CTR, long_dwell_share, diversity concentration.
|
||||
4. Record save_rate as informational only.
|
||||
5. Decide: hold, promote gate, or rollback.
|
||||
|
||||
## First 24h verification checklist
|
||||
|
||||
1. Confirm rollout activation and gate state:
|
||||
- `DISCOVERY_ROLLOUT_ENABLED=true`
|
||||
- `DISCOVERY_ROLLOUT_ACTIVE_GATE=g10`
|
||||
- `DISCOVERY_FORCE_ALGO_VERSION` empty
|
||||
2. Verify both algos are receiving traffic in analytics:
|
||||
- candidate (`clip-cosine-v2`) should be near 10% share (allow normal variance)
|
||||
- baseline (`clip-cosine-v1`) remains dominant
|
||||
3. Run aggregation/evaluation at least twice in first day (midday + end-of-day):
|
||||
- `php artisan analytics:aggregate-feed --date=YYYY-MM-DD`
|
||||
- `php artisan analytics:evaluate-feed-weights --from=YYYY-MM-DD --to=YYYY-MM-DD --json`
|
||||
- `php artisan analytics:compare-feed-ab clip-cosine-v1 clip-cosine-v2 --from=YYYY-MM-DD --to=YYYY-MM-DD --json`
|
||||
4. Check guardrails:
|
||||
- CTR drop < rollback threshold
|
||||
- long_dwell_share drop < rollback threshold
|
||||
- diversity concentration rise < rollback threshold
|
||||
5. Check save-event ingestion health:
|
||||
- save events (`favorite`,`download`) are arriving in `user_discovery_events`
|
||||
- `event_id` is always populated
|
||||
6. If any rollback trigger is breached, apply emergency rollback preset immediately.
|
||||
101
docs/ui/tag-input.md
Normal file
101
docs/ui/tag-input.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# TagInput UI Component
|
||||
|
||||
## Overview
|
||||
|
||||
`TagInput` is the reusable tag entry component for Skinbase artwork flows.
|
||||
|
||||
It is designed for:
|
||||
|
||||
- Upload page
|
||||
- Artwork edit page
|
||||
- Admin moderation screens
|
||||
|
||||
The component encapsulates all tag UX behavior (chips, search, keyboard flow, AI suggestions, status hints) so pages stay thin.
|
||||
|
||||
## File Location
|
||||
|
||||
- `resources/js/components/tags/TagInput.jsx`
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `value` | `string \| string[]` | required | Controlled selected tags. Can be CSV string or array. |
|
||||
| `onChange` | `(tags: string[]) => void` | required | Called whenever selected tags change. |
|
||||
| `suggestedTags` | `Array<string \| object>` | `[]` | AI-suggested tags shown as clickable pills. |
|
||||
| `disabled` | `boolean` | `false` | Disables input and interactions. |
|
||||
| `maxTags` | `number` | `15` | Maximum number of selected tags. |
|
||||
| `minLength` | `number` | `2` | Minimum normalized tag length. |
|
||||
| `maxLength` | `number` | `32` | Maximum normalized tag length. |
|
||||
| `placeholder` | `string` | `Type tags…` | Input placeholder text. |
|
||||
| `searchEndpoint` | `string` | `/api/tags/search` | Search API endpoint. |
|
||||
| `popularEndpoint` | `string` | `/api/tags/popular` | Popular tags endpoint when input is empty. |
|
||||
|
||||
## Normalization
|
||||
|
||||
Tags are normalized client-side before being added:
|
||||
|
||||
- lowercase
|
||||
- trim
|
||||
- spaces → `-`
|
||||
- remove unsupported characters
|
||||
- collapse repeated separators
|
||||
- max length = 32
|
||||
|
||||
Server-side normalization/validation still applies and remains authoritative.
|
||||
|
||||
## Keyboard & Interaction
|
||||
|
||||
- `Enter` → add tag
|
||||
- `Comma` → add tag
|
||||
- `Tab` → accept highlighted suggestion
|
||||
- `Backspace` (empty input) → remove last tag
|
||||
- `Escape` → close suggestion dropdown
|
||||
- Paste CSV (`a, b, c`) → split and add valid tags
|
||||
|
||||
## Accessibility
|
||||
|
||||
- Suggestion dropdown uses `role="listbox"`
|
||||
- Suggestions use `role="option"`
|
||||
- Active item uses `aria-selected`
|
||||
- Input uses `aria-expanded`, `aria-controls`, `aria-autocomplete`
|
||||
|
||||
## API Usage
|
||||
|
||||
The component performs debounced search (300ms):
|
||||
|
||||
- `GET /api/tags/search?q=<query>`
|
||||
- `GET /api/tags/popular` (empty query)
|
||||
|
||||
Behavior:
|
||||
|
||||
- caches recent query results
|
||||
- aborts outdated requests
|
||||
- max 8 suggestions
|
||||
- excludes already-selected tags
|
||||
- shows non-blocking message when search fails
|
||||
|
||||
## Upload Integration Example
|
||||
|
||||
```jsx
|
||||
<TagInput
|
||||
value={state.metadata.tags}
|
||||
onChange={(nextTags) => {
|
||||
dispatch({ type: 'SET_METADATA', payload: { tags: nextTags.join(', ') } })
|
||||
}}
|
||||
suggestedTags={props.suggested_tags || []}
|
||||
maxTags={15}
|
||||
minLength={2}
|
||||
maxLength={32}
|
||||
/>
|
||||
```
|
||||
|
||||
## Events & Save Strategy
|
||||
|
||||
`TagInput` itself does not persist to backend on keystrokes.
|
||||
|
||||
Persistence is done on save/publish boundary by page logic, e.g.:
|
||||
|
||||
- `PUT /api/artworks/{id}/tags`
|
||||
|
||||
This keeps UI responsive and avoids unnecessary API writes.
|
||||
130
docs/ui/upload-v2-rollout-runbook.md
Normal file
130
docs/ui/upload-v2-rollout-runbook.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# Upload UI v2 Rollout Runbook
|
||||
|
||||
## Status
|
||||
|
||||
- Upload UI v2 is production-ready.
|
||||
- Feature flag posture: `uploads_v2` default ON.
|
||||
- Emergency override remains available through `SKINBASE_UPLOADS_V2=false`.
|
||||
|
||||
## Scope
|
||||
|
||||
- Route: `/upload`
|
||||
- UI: React/Inertia Upload Wizard v2
|
||||
- API endpoints in use:
|
||||
- `POST /api/uploads/init`
|
||||
- `POST /api/uploads/chunk`
|
||||
- `POST /api/uploads/finish`
|
||||
- `GET /api/uploads/{id}/status`
|
||||
- `POST /api/uploads/{id}/publish`
|
||||
- `POST /api/uploads/cancel`
|
||||
|
||||
## Legacy Flow Policy
|
||||
|
||||
- Current state: legacy upload flow remains in code behind feature flag branch.
|
||||
- Removal decision: **scheduled removal** (not immediate deletion).
|
||||
- Target window: remove legacy branch in the next hardening cycle after stable production operation.
|
||||
- Suggested checkpoint gates before removal:
|
||||
1. 7 consecutive days with no Sev-1/Sev-2 upload regressions.
|
||||
2. Upload completion rate at or above pre-v2 baseline.
|
||||
3. No unresolved blockers in publish/cancel/status polling.
|
||||
|
||||
## Rollout Checklist
|
||||
|
||||
### 1) Staging
|
||||
|
||||
- Set `SKINBASE_UPLOADS_V2=true` in staging env.
|
||||
- Build and deploy current commit.
|
||||
- Verify upload happy paths:
|
||||
- image upload (jpg/png/webp)
|
||||
- archive upload with required screenshots
|
||||
- cancel in-progress upload
|
||||
- publish after ready state
|
||||
- Verify failure paths:
|
||||
- invalid file type
|
||||
- over-size files
|
||||
- processing/publish API failure surfaces retry/reset correctly
|
||||
- Verify analytics events emitted in browser:
|
||||
- `upload_start`
|
||||
- `upload_complete`
|
||||
- `upload_publish`
|
||||
- `upload_cancel`
|
||||
- `upload_error`
|
||||
|
||||
### 2) Production Enablement
|
||||
|
||||
- Confirm production env has `SKINBASE_UPLOADS_V2=true` (or unset, default ON).
|
||||
- Deploy release artifact.
|
||||
- Run smoke tests on `/upload` with one image and one archive flow.
|
||||
- Confirm endpoints respond with expected status codes under normal load.
|
||||
|
||||
### 3) Post-Deploy Verification (0-24h)
|
||||
|
||||
- Validate build artifact and route rendering:
|
||||
- `/upload` renders v2 wizard UI
|
||||
- no front-end boot errors in browser console
|
||||
- Validate pipeline behavior:
|
||||
- init/chunk/finish/status/publish/cancel all reachable
|
||||
- status polling transitions to ready/publishable where expected
|
||||
- Validate user outcomes:
|
||||
- completion and publish rates are stable vs prior day baseline
|
||||
- no spike in cancellation due to UI confusion
|
||||
|
||||
## Post-Deploy Monitoring Plan
|
||||
|
||||
### Key Metrics
|
||||
|
||||
- Upload start volume (`upload_start`)
|
||||
- Upload completion volume (`upload_complete`)
|
||||
- Publish success volume (`upload_publish`)
|
||||
- Error volume by stage (`upload_error.stage`)
|
||||
- Cancel volume (`upload_cancel`)
|
||||
- Derived funnel:
|
||||
- start -> complete conversion
|
||||
- complete -> publish conversion
|
||||
- overall start -> publish conversion
|
||||
|
||||
### Operational Signals
|
||||
|
||||
- API error rates for `/api/uploads/*`
|
||||
- p95 latency for `init`, `chunk`, `finish`, `status`, `publish`
|
||||
- 4xx/5xx split by endpoint
|
||||
- Client-side uncaught exceptions on `/upload`
|
||||
|
||||
### Alert Thresholds (initial)
|
||||
|
||||
- Critical rollback candidate:
|
||||
- `upload_error` rate > 2x baseline for 15+ minutes, or
|
||||
- publish failure rate > 5% sustained for 15+ minutes, or
|
||||
- any endpoint 5xx rate > 3% sustained for 10+ minutes.
|
||||
- Warning/observe:
|
||||
- completion funnel drops > 10% vs trailing 7-day average.
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
### Fast Toggle Rollback (preferred)
|
||||
|
||||
1. Set `SKINBASE_UPLOADS_V2=false`.
|
||||
2. Reload config/cache per deploy process.
|
||||
3. Verify `/upload` serves legacy flow.
|
||||
4. Continue API monitoring until error rates normalize.
|
||||
|
||||
### Release Rollback (if needed)
|
||||
|
||||
1. Roll back to prior release artifact.
|
||||
2. Keep `SKINBASE_UPLOADS_V2=false` during stabilization.
|
||||
3. Re-run smoke test for upload + publish.
|
||||
|
||||
### Communication
|
||||
|
||||
- Post incident update in release channel with:
|
||||
- start time
|
||||
- impact scope (upload, publish, cancel)
|
||||
- rollback action taken
|
||||
- follow-up issue link
|
||||
|
||||
## Ownership and Next Actions
|
||||
|
||||
- Owner: Upload frontend + API maintainers.
|
||||
- First review checkpoint: 24h post deploy.
|
||||
- Second checkpoint: 7 days post deploy for legacy removal go/no-go.
|
||||
- If metrics remain healthy, create removal PR for legacy branch in `/upload` page component.
|
||||
Reference in New Issue
Block a user