@nexttylabs/echo 0.4.0 → 0.5.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.
- package/CHANGELOG.md +13 -0
- package/app/(public)/[organizationSlug]/roadmap/page.tsx +19 -1
- package/app/api/admin/backup/route.ts +22 -4
- package/app/api/auth/register/handler.ts +1 -2
- package/lib/auth/config.ts +0 -7
- package/lib/db/migrations/0000_needy_leech.sql +335 -0
- package/lib/db/migrations/meta/0000_snapshot.json +2186 -1
- package/lib/db/migrations/meta/_journal.json +2 -135
- package/lib/db/schema/auth.ts +0 -1
- package/lib/db/schema/index.ts +0 -1
- package/lib/portal/public-context.tsx +5 -0
- package/package.json +20 -1
- package/.changeset/README.md +0 -21
- package/.changeset/config.json +0 -11
- package/.changeset/cozy-ghosts-care.md +0 -5
- package/.changeset/sharp-lines-stand.md +0 -5
- package/.changeset/sour-doodles-eat.md +0 -5
- package/.changeset/tender-moose-shop.md +0 -5
- package/.github/pull_request_template.md +0 -13
- package/.github/workflows/ci.yml +0 -41
- package/.github/workflows/publish.yml +0 -44
- package/.github/workflows/release.yml +0 -73
- package/AGENTS.md +0 -92
- package/Dockerfile +0 -57
- package/Makefile +0 -77
- package/bun.lock +0 -2503
- package/components/portal/project-switcher.tsx +0 -20
- package/docker-compose.dev.yml +0 -26
- package/docker-compose.yml +0 -98
- package/docs/architecture.md +0 -259
- package/docs/component-inventory.md +0 -261
- package/docs/database-migrations.md +0 -76
- package/docs/development-guide.md +0 -209
- package/docs/e2e-user-flows.csv +0 -31
- package/docs/er-diagram-feedback.mmd +0 -138
- package/docs/er-diagram.mmd +0 -281
- package/docs/i18n-check-report.md +0 -296
- package/docs/index.md +0 -214
- package/docs/logic-chain.md +0 -94
- package/docs/plans/2026-01-02-database-migration-scripts.md +0 -496
- package/docs/plans/2026-01-02-user-login-design.md +0 -37
- package/docs/plans/2026-01-02-user-login.md +0 -437
- package/docs/plans/2026-01-02-user-registration-design.md +0 -47
- package/docs/plans/2026-01-02-user-registration.md +0 -628
- package/docs/plans/2026-01-03-roles-permissions-design.md +0 -20
- package/docs/plans/2026-01-03-roles-permissions.md +0 -266
- package/docs/plans/2026-01-05-authentication-middleware.md +0 -207
- package/docs/plans/2026-01-05-member-removal.md +0 -186
- package/docs/plans/2026-01-05-organization-creation.md +0 -374
- package/docs/plans/2026-01-05-rbac-middleware.md +0 -112
- package/docs/plans/2026-01-05-role-configuration.md +0 -441
- package/docs/plans/2026-01-06-file-upload-support.md +0 -804
- package/docs/plans/2026-01-06-permission-check-hook.md +0 -155
- package/docs/plans/2026-01-06-resource-ownership-check.md +0 -231
- package/docs/plans/2026-01-07-feedback-tracking-link.md +0 -459
- package/docs/plans/2026-01-09-logout-redirect-design.md +0 -52
- package/docs/plans/2026-01-09-phase2-3-plan.md +0 -654
- package/docs/plans/2026-01-09-portal-execution-plan.md +0 -408
- package/docs/plans/2026-01-09-project-delete-feature-design.md +0 -163
- package/docs/plans/2026-01-09-project-delete-implementation.md +0 -451
- package/docs/plans/2026-01-09-project-edit-delete-design.md +0 -52
- package/docs/plans/2026-01-09-settings-center-design.md +0 -114
- package/docs/plans/2026-01-09-settings-center.md +0 -948
- package/docs/plans/2026-01-10-organization-only-design.md +0 -66
- package/docs/plans/2026-01-10-organization-only-implementation.md +0 -433
- package/docs/plans/2026-01-10-portal-settings-restructure-plan.md +0 -18
- package/docs/plans/2026-01-10-project-settings-tabs-design-implementation.md +0 -296
- package/docs/plans/2026-01-14-e2e-playwright-feedback.md +0 -173
- package/docs/plans/2026-01-15-feedback-management-org-context-design.md +0 -82
- package/docs/plans/2026-01-15-feedback-management-org-context-implementation-plan.md +0 -521
- package/docs/plans/2026-01-16-admin-feedback-filters-design.md +0 -75
- package/docs/plans/2026-01-16-admin-feedback-filters-implementation.md +0 -293
- package/docs/plans/2026-01-16-admin-feedback-route-consolidation.md +0 -180
- package/docs/plans/2026-01-16-e2e-test-fixes.md +0 -158
- package/docs/plans/2026-01-17-admin-feedback-filters.md +0 -214
- package/docs/plans/2026-01-17-admin-feedback-improvements.md +0 -453
- package/docs/plans/2026-01-18-changesets-design.md +0 -40
- package/docs/product_changes.md +0 -37
- package/docs/project-overview.md +0 -159
- package/docs/project-scan-report.json +0 -104
- package/docs/route-role-visibility.md +0 -51
- package/docs/source-tree-analysis.md +0 -150
- package/docs/testing/delete-project-manual-tests.md +0 -18
- package/docs/user-story-tracking.md +0 -191
- package/eslint.config.mjs +0 -19
- package/lib/db/migrations/.gitkeep +0 -0
- package/lib/db/migrations/0000_cynical_gladiator.sql +0 -53
- package/lib/db/migrations/0001_wandering_sunfire.sql +0 -27
- package/lib/db/migrations/0002_shallow_speedball.sql +0 -1
- package/lib/db/migrations/0003_add_org_description.sql +0 -1
- package/lib/db/migrations/0003_boring_wild_pack.sql +0 -13
- package/lib/db/migrations/0004_windy_tyrannus.sql +0 -27
- package/lib/db/migrations/0005_perpetual_doorman.sql +0 -5
- package/lib/db/migrations/0006_aberrant_captain_midlands.sql +0 -13
- package/lib/db/migrations/0007_clever_captain_cross.sql +0 -14
- package/lib/db/migrations/0008_sparkling_pandemic.sql +0 -2
- package/lib/db/migrations/0009_happy_black_tom.sql +0 -29
- package/lib/db/migrations/0010_kind_junta.sql +0 -8
- package/lib/db/migrations/0011_mute_squadron_supreme.sql +0 -25
- package/lib/db/migrations/0012_giant_power_man.sql +0 -24
- package/lib/db/migrations/0013_damp_titanium_man.sql +0 -17
- package/lib/db/migrations/0014_blue_alice.sql +0 -18
- package/lib/db/migrations/0015_webhook_tables.sql +0 -41
- package/lib/db/migrations/0016_github_integration.sql +0 -30
- package/lib/db/migrations/0016_overjoyed_ghost_rider.sql +0 -22
- package/lib/db/migrations/0017_slimy_inhumans.sql +0 -6
- package/lib/db/migrations/0018_same_spitfire.sql +0 -1
- package/lib/db/migrations/0019_jittery_loners.sql +0 -16
- package/lib/db/migrations/0019_remove_projects_add_org_settings.sql +0 -14
- package/lib/db/migrations/meta/0001_snapshot.json +0 -553
- package/lib/db/migrations/meta/0002_snapshot.json +0 -560
- package/lib/db/migrations/meta/0003_snapshot.json +0 -650
- package/lib/db/migrations/meta/0004_snapshot.json +0 -852
- package/lib/db/migrations/meta/0005_snapshot.json +0 -900
- package/lib/db/migrations/meta/0006_snapshot.json +0 -1011
- package/lib/db/migrations/meta/0007_snapshot.json +0 -1125
- package/lib/db/migrations/meta/0008_snapshot.json +0 -1146
- package/lib/db/migrations/meta/0009_snapshot.json +0 -1386
- package/lib/db/migrations/meta/0010_snapshot.json +0 -1419
- package/lib/db/migrations/meta/0011_snapshot.json +0 -1615
- package/lib/db/migrations/meta/0012_snapshot.json +0 -1805
- package/lib/db/migrations/meta/0013_snapshot.json +0 -1948
- package/lib/db/migrations/meta/0014_snapshot.json +0 -2082
- package/lib/db/migrations/meta/0015_snapshot.json +0 -2476
- package/lib/db/migrations/meta/0016_snapshot.json +0 -2633
- package/lib/db/migrations/meta/0017_snapshot.json +0 -2680
- package/lib/db/migrations/meta/0018_snapshot.json +0 -2686
- package/lib/db/migrations/meta/0019_snapshot.json +0 -2741
- package/lib/db/schema/projects.ts +0 -145
- package/lib/db/schema/user-profiles.ts +0 -31
- package/lib/validations/projects.ts +0 -49
- package/next-env.d.ts +0 -6
- package/playwright.config.ts +0 -44
- package/proxy.test.ts +0 -131
- package/proxy.ts +0 -116
- package/scripts/backup-db.sh +0 -57
- package/scripts/backup-db.ts +0 -24
- package/scripts/generate-openapi.ts +0 -22
- package/scripts/migration-helper.ts +0 -39
- package/scripts/pre-deploy.ts +0 -75
- package/scripts/restore-db.sh +0 -60
- package/scripts/rollback.ts +0 -72
- package/scripts/seed-tags.ts +0 -48
- package/tests/api/feedback-bulk.test.ts +0 -47
- package/tests/api/feedback-by-id.test.ts +0 -67
- package/tests/api/feedback-comments-route-import.test.ts +0 -26
- package/tests/api/feedback-create.test.ts +0 -71
- package/tests/api/feedback-delete.test.ts +0 -160
- package/tests/api/feedback-filter.test.ts +0 -250
- package/tests/api/feedback-list.test.ts +0 -234
- package/tests/api/feedback-route-assignee-condition.test.ts +0 -32
- package/tests/api/feedback-similar.test.ts +0 -46
- package/tests/api/feedback-sort.test.ts +0 -261
- package/tests/api/feedback-status-enum.test.ts +0 -49
- package/tests/api/feedback-status-filter.test.ts +0 -117
- package/tests/api/feedback-submit-on-behalf.test.ts +0 -269
- package/tests/api/feedback.test.ts +0 -175
- package/tests/api/identify-jwt.test.ts +0 -25
- package/tests/api/invitation-accept.test.ts +0 -213
- package/tests/api/organization-invitations.test.ts +0 -186
- package/tests/api/organization-members-list.test.ts +0 -79
- package/tests/api/organization-members.test.ts +0 -340
- package/tests/api/organizations.test.ts +0 -149
- package/tests/api/register.test.ts +0 -112
- package/tests/api/upload.test.ts +0 -103
- package/tests/api/vote.test.ts +0 -82
- package/tests/app/admin-feedback-detail-page.test.tsx +0 -25
- package/tests/app/admin-feedback-list-page.test.tsx +0 -25
- package/tests/app/admin-feedback-new-page.test.tsx +0 -25
- package/tests/app/health-route-helpers.test.ts +0 -27
- package/tests/app/login-page.test.ts +0 -26
- package/tests/app/portal-page.test.ts +0 -29
- package/tests/app/project-portal-overview.test.tsx +0 -25
- package/tests/app/widget-page-import.test.ts +0 -25
- package/tests/components/create-post-dialog-defaults.test.ts +0 -43
- package/tests/components/feedback/duplicate-suggestions-inline.test.tsx +0 -27
- package/tests/components/feedback/embedded-feedback-form.test.tsx +0 -96
- package/tests/components/feedback/feedback-detail.test.tsx +0 -25
- package/tests/components/feedback/feedback-stats.test.tsx +0 -49
- package/tests/components/feedback-bulk-actions.test.tsx +0 -39
- package/tests/components/feedback-i18n-keys.test.ts +0 -70
- package/tests/components/feedback-list-controls-compile.test.ts +0 -25
- package/tests/components/feedback-list-controls.test.tsx +0 -204
- package/tests/components/feedback-list-item.test.tsx +0 -67
- package/tests/components/landing/hero.test.tsx +0 -46
- package/tests/components/layout/language-switcher.test.tsx +0 -25
- package/tests/components/layout/sidebar.test.tsx +0 -157
- package/tests/components/login-form.test.ts +0 -25
- package/tests/components/organization-form.test.ts +0 -32
- package/tests/components/organization-switcher.test.ts +0 -25
- package/tests/components/pagination.test.tsx +0 -43
- package/tests/components/portal-overview.test.tsx +0 -25
- package/tests/components/profile-form.test.tsx +0 -139
- package/tests/components/role-selector.test.ts +0 -31
- package/tests/components/status-chart.test.tsx +0 -90
- package/tests/e2e/auth.e2e.ts +0 -323
- package/tests/e2e/feedback-actions.e2e.ts +0 -471
- package/tests/e2e/feedback-attachment.e2e.ts +0 -168
- package/tests/e2e/feedback-customer.e2e.ts +0 -226
- package/tests/e2e/feedback-management.e2e.ts +0 -565
- package/tests/e2e/feedback-submit.e2e.ts +0 -133
- package/tests/e2e/feedback-view.e2e.ts +0 -297
- package/tests/e2e/fixtures/test-data.ts +0 -235
- package/tests/e2e/health-check.e2e.ts +0 -230
- package/tests/e2e/helpers/test-utils-helpers.test.ts +0 -43
- package/tests/e2e/helpers/test-utils.ts +0 -298
- package/tests/e2e/integration-placeholders.e2e.ts +0 -199
- package/tests/e2e/organization.e2e.ts +0 -292
- package/tests/e2e/permissions.e2e.ts +0 -424
- package/tests/e2e/project-widget.e2e.ts +0 -63
- package/tests/feedback/filters.test.ts +0 -29
- package/tests/hooks/use-permissions.test.ts +0 -52
- package/tests/lib/ai/classifier.test.ts +0 -104
- package/tests/lib/ai/duplicate-detector.test.ts +0 -234
- package/tests/lib/attachments-schema.test.ts +0 -30
- package/tests/lib/auth/session.test.ts +0 -49
- package/tests/lib/auth-client.test.ts +0 -37
- package/tests/lib/auth-config.test.ts +0 -26
- package/tests/lib/feedback-prefill.test.ts +0 -52
- package/tests/lib/feedback-processor.test.ts +0 -41
- package/tests/lib/feedback-schema.test.ts +0 -33
- package/tests/lib/file-validator.test.ts +0 -48
- package/tests/lib/get-feedback-by-id.test.ts +0 -37
- package/tests/lib/invitations.test.ts +0 -35
- package/tests/lib/login-schema.test.ts +0 -36
- package/tests/lib/org-context.test.ts +0 -95
- package/tests/lib/organization-access.test.ts +0 -44
- package/tests/lib/organization-member-role-schema.test.ts +0 -41
- package/tests/lib/permissions.test.ts +0 -88
- package/tests/lib/portal-analytics.test.ts +0 -25
- package/tests/lib/portal-contributors.test.ts +0 -25
- package/tests/lib/portal-copy.test.ts +0 -27
- package/tests/lib/portal-i18n.test.ts +0 -30
- package/tests/lib/portal-leaderboard-settings.test.ts +0 -25
- package/tests/lib/portal-modules.test.ts +0 -25
- package/tests/lib/portal-seo.test.ts +0 -25
- package/tests/lib/portal-sharing.test.ts +0 -25
- package/tests/lib/portal-sorting.test.ts +0 -25
- package/tests/lib/portal-theme.test.ts +0 -25
- package/tests/lib/rate-limit.test.ts +0 -142
- package/tests/lib/resolve-locale.test.ts +0 -34
- package/tests/lib/services/backup.test.ts +0 -145
- package/tests/lib/user-organizations.test.ts +0 -42
- package/tests/lib/user-role-schema.test.ts +0 -33
- package/tests/lib/user-schema.test.ts +0 -25
- package/tests/setup.ts +0 -74
- package/vercel.json +0 -4
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
# Admin Feedback Filters Implementation Plan
|
|
2
|
-
|
|
3
|
-
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
-
|
|
5
|
-
**Goal:** Improve `/admin/feedback` filtering clarity with a visible summary bar, AND-logic explanation, and simplified sort options (time, votes), while preserving URL-driven filters.
|
|
6
|
-
|
|
7
|
-
**Architecture:** Keep `FeedbackList` client-driven and URL-based. `FeedbackListControls` owns filter UI state, URL updates, and summary rendering. Sorting uses existing `sortBy/sortOrder` parameters. All UI strings remain in `messages/*.json` via `next-intl`.
|
|
8
|
-
|
|
9
|
-
**Tech Stack:** Next.js App Router, React 19, TypeScript, Tailwind CSS, shadcn/ui, Bun test.
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
### Task 0: Restore lint baseline (per user approval, no tests)
|
|
14
|
-
|
|
15
|
-
**Files:**
|
|
16
|
-
- Modify: `components/feedback/feedback-detail-view.tsx:116`
|
|
17
|
-
- Modify: `components/shared/pagination.tsx:55`
|
|
18
|
-
|
|
19
|
-
**Step 1: Fix JSX parse error in detail view**
|
|
20
|
-
|
|
21
|
-
```tsx
|
|
22
|
-
<div className="flex items-start gap-4">
|
|
23
|
-
<Button
|
|
24
|
-
variant="ghost"
|
|
25
|
-
size="icon"
|
|
26
|
-
onClick={handleBack}
|
|
27
|
-
aria-label={t("detail.backToList")}
|
|
28
|
-
className="shrink-0 mt-1"
|
|
29
|
-
>
|
|
30
|
-
<ArrowLeft className="w-5 h-5" />
|
|
31
|
-
</Button>
|
|
32
|
-
<div className="flex-1">
|
|
33
|
-
...
|
|
34
|
-
</div>
|
|
35
|
-
</div>
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
**Step 2: Fix prefer-const and unused index**
|
|
39
|
-
|
|
40
|
-
```tsx
|
|
41
|
-
const startPage = Math.max(2, currentPage - Math.floor((maxVisible - 2) / 2));
|
|
42
|
-
const endPage = Math.min(totalPages - 1, startPage + maxVisible - 3);
|
|
43
|
-
...
|
|
44
|
-
{pageNumbers.map((page) => (
|
|
45
|
-
...
|
|
46
|
-
))}
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
**Step 3: Run lint to confirm baseline repaired**
|
|
50
|
-
|
|
51
|
-
Run: `bun run lint`
|
|
52
|
-
Expected: no parsing errors; warnings acceptable only if unrelated.
|
|
53
|
-
|
|
54
|
-
---
|
|
55
|
-
|
|
56
|
-
### Task 1: Add tests for filter summary and debounce behavior
|
|
57
|
-
|
|
58
|
-
**Files:**
|
|
59
|
-
- Modify: `tests/components/feedback-list-controls.test.tsx`
|
|
60
|
-
|
|
61
|
-
**Step 1: Write failing tests**
|
|
62
|
-
|
|
63
|
-
```tsx
|
|
64
|
-
it("shows selected filters summary and clear-all", async () => {
|
|
65
|
-
const searchParams = new URLSearchParams(
|
|
66
|
-
"status=new,planned&type=bug&priority=high"
|
|
67
|
-
);
|
|
68
|
-
// mock useSearchParams to return searchParams
|
|
69
|
-
// render and assert chips + logic hint + clear button
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("debounces search input before pushing URL", async () => {
|
|
73
|
-
// render, change input, assert push not called immediately,
|
|
74
|
-
// await 350ms, assert push called once
|
|
75
|
-
});
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
**Step 2: Run test to verify failure**
|
|
79
|
-
|
|
80
|
-
Run: `bun test tests/components/feedback-list-controls.test.tsx`
|
|
81
|
-
Expected: FAIL due to missing summary bar + debounce behavior.
|
|
82
|
-
|
|
83
|
-
---
|
|
84
|
-
|
|
85
|
-
### Task 2: Implement summary bar and debounce in controls
|
|
86
|
-
|
|
87
|
-
**Files:**
|
|
88
|
-
- Modify: `components/feedback/feedback-list-controls.tsx`
|
|
89
|
-
|
|
90
|
-
**Step 1: Implement minimal code changes**
|
|
91
|
-
|
|
92
|
-
```tsx
|
|
93
|
-
const [searchInput, setSearchInput] = useState(query);
|
|
94
|
-
useEffect(() => setSearchInput(query), [query]);
|
|
95
|
-
useEffect(() => {
|
|
96
|
-
const timeout = setTimeout(() => {
|
|
97
|
-
if (searchInput !== query) {
|
|
98
|
-
updateParams({ query: searchInput || null });
|
|
99
|
-
}
|
|
100
|
-
}, 300);
|
|
101
|
-
return () => clearTimeout(timeout);
|
|
102
|
-
}, [searchInput, query]);
|
|
103
|
-
|
|
104
|
-
const filterGroups = [
|
|
105
|
-
{ key: "status", label: t("filters.statusLabel"), values: status, formatter: ... },
|
|
106
|
-
{ key: "type", label: t("filters.typeLabel"), values: type, formatter: ... },
|
|
107
|
-
{ key: "priority", label: t("filters.priorityLabel"), values: priority, formatter: ... },
|
|
108
|
-
{ key: "hasVotes", label: t("list.hasVotes"), values: hasVotes, formatter: ... },
|
|
109
|
-
{ key: "hasReplies", label: t("list.hasReplies"), values: hasReplies, formatter: ... },
|
|
110
|
-
].filter(group => group.values.length > 0);
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
**Step 2: Update summary UI**
|
|
114
|
-
|
|
115
|
-
```tsx
|
|
116
|
-
<div className="flex flex-wrap items-center gap-2 overflow-x-auto">
|
|
117
|
-
{filterGroups.length === 0 ? (
|
|
118
|
-
<span className="text-xs text-muted-foreground">{t("list.filtersEmpty")}</span>
|
|
119
|
-
) : (
|
|
120
|
-
filterGroups.map((group, groupIndex) => (
|
|
121
|
-
<div key={group.key} className="flex items-center gap-2">
|
|
122
|
-
<span className="text-xs text-muted-foreground">{group.label} {group.values.length}</span>
|
|
123
|
-
{group.values.map(value => (
|
|
124
|
-
<Button ... onClick={() => toggleValue(group.key, value)}>...</Button>
|
|
125
|
-
))}
|
|
126
|
-
{groupIndex < filterGroups.length - 1 && (
|
|
127
|
-
<span className="text-xs text-muted-foreground">AND</span>
|
|
128
|
-
)}
|
|
129
|
-
</div>
|
|
130
|
-
))
|
|
131
|
-
)}
|
|
132
|
-
<span className="text-xs text-muted-foreground">{t("filters.logicHint")}</span>
|
|
133
|
-
<Button variant="ghost" size="sm" onClick={clearAll}>{t("filters.clearAll")}</Button>
|
|
134
|
-
</div>
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
**Step 3: Simplify sort options**
|
|
138
|
-
|
|
139
|
-
```tsx
|
|
140
|
-
const SORT_OPTIONS = [
|
|
141
|
-
{ value: "createdAt:desc", label: t("list.sortNewest") },
|
|
142
|
-
{ value: "createdAt:asc", label: t("list.sortOldest") },
|
|
143
|
-
{ value: "voteCount:desc", label: t("list.sortMostVotes") },
|
|
144
|
-
{ value: "voteCount:asc", label: t("list.sortFewestVotes") },
|
|
145
|
-
] as const;
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
**Step 4: Run test to verify pass**
|
|
149
|
-
|
|
150
|
-
Run: `bun test tests/components/feedback-list-controls.test.tsx`
|
|
151
|
-
Expected: PASS
|
|
152
|
-
|
|
153
|
-
---
|
|
154
|
-
|
|
155
|
-
### Task 3: Update translations
|
|
156
|
-
|
|
157
|
-
**Files:**
|
|
158
|
-
- Modify: `messages/en.json`
|
|
159
|
-
- Modify: `messages/zh-CN.json`
|
|
160
|
-
- Modify: `messages/jp.json`
|
|
161
|
-
|
|
162
|
-
**Step 1: Add keys**
|
|
163
|
-
|
|
164
|
-
```json
|
|
165
|
-
"filters": {
|
|
166
|
-
...,
|
|
167
|
-
"logicHint": "Within a group: match any • Across groups: match all"
|
|
168
|
-
},
|
|
169
|
-
"list": {
|
|
170
|
-
...,
|
|
171
|
-
"sortNewest": "Newest",
|
|
172
|
-
"sortOldest": "Oldest",
|
|
173
|
-
"sortMostVotes": "Most votes",
|
|
174
|
-
"sortFewestVotes": "Fewest votes"
|
|
175
|
-
}
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
**Step 2: Verify app compiles**
|
|
179
|
-
|
|
180
|
-
Run: `bun run lint`
|
|
181
|
-
Expected: no errors.
|
|
182
|
-
|
|
183
|
-
---
|
|
184
|
-
|
|
185
|
-
### Task 4: Update controls tests for new labels
|
|
186
|
-
|
|
187
|
-
**Files:**
|
|
188
|
-
- Modify: `tests/components/feedback-list-controls.test.tsx`
|
|
189
|
-
|
|
190
|
-
**Step 1: Adjust translation mock**
|
|
191
|
-
|
|
192
|
-
```tsx
|
|
193
|
-
if (key === "filters.logicHint") return "Within a group: match any • Across groups: match all";
|
|
194
|
-
if (key === "list.sortNewest") return "Newest";
|
|
195
|
-
...
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
**Step 2: Run test suite**
|
|
199
|
-
|
|
200
|
-
Run: `bun test tests/components/feedback-list-controls.test.tsx`
|
|
201
|
-
Expected: PASS
|
|
202
|
-
|
|
203
|
-
---
|
|
204
|
-
|
|
205
|
-
### Task 5: Final verification
|
|
206
|
-
|
|
207
|
-
**Files:**
|
|
208
|
-
- None
|
|
209
|
-
|
|
210
|
-
**Step 1: Run lint**
|
|
211
|
-
|
|
212
|
-
Run: `bun run lint`
|
|
213
|
-
Expected: PASS (0 errors).
|
|
214
|
-
|
|
@@ -1,453 +0,0 @@
|
|
|
1
|
-
# Admin Feedback Improvements Implementation Plan
|
|
2
|
-
|
|
3
|
-
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
-
|
|
5
|
-
**Goal:** Improve the admin feedback list/detail UX with i18n consistency, safer row interactions, bulk actions, and pagination controls.
|
|
6
|
-
|
|
7
|
-
**Architecture:** Keep `/admin/feedback` client-driven. `FeedbackList` owns selection and refresh; `FeedbackListControls` manages filters/sort/page size; a new bulk API route performs multi-row updates with permission checks and status history inserts. All UI strings and relative dates are localized via `next-intl`.
|
|
8
|
-
|
|
9
|
-
**Tech Stack:** Next.js App Router, React 19, TypeScript, next-intl, Tailwind CSS, Bun, Drizzle ORM.
|
|
10
|
-
|
|
11
|
-
### Task 1: i18n foundations for feedback admin UI
|
|
12
|
-
|
|
13
|
-
**Files:**
|
|
14
|
-
- Modify: `messages/en.json`
|
|
15
|
-
- Modify: `messages/zh-CN.json`
|
|
16
|
-
- Modify: `messages/jp.json`
|
|
17
|
-
|
|
18
|
-
**Step 1: Write the failing test**
|
|
19
|
-
|
|
20
|
-
Add a minimal test that references new translation keys so missing keys fail.
|
|
21
|
-
|
|
22
|
-
```ts
|
|
23
|
-
// tests/components/feedback-i18n-keys.test.ts
|
|
24
|
-
import { describe, expect, it } from "bun:test";
|
|
25
|
-
import en from "@/messages/en.json";
|
|
26
|
-
|
|
27
|
-
const requiredKeys = [
|
|
28
|
-
"feedback.list.searchPlaceholder",
|
|
29
|
-
"feedback.list.searchButton",
|
|
30
|
-
"feedback.list.sortLabel",
|
|
31
|
-
"feedback.list.pageSizeLabel",
|
|
32
|
-
"feedback.list.summary",
|
|
33
|
-
"feedback.list.empty",
|
|
34
|
-
"feedback.list.error",
|
|
35
|
-
"feedback.pagination.jumpTo",
|
|
36
|
-
"feedback.pagination.go",
|
|
37
|
-
"feedback.bulk.selectedCount",
|
|
38
|
-
"feedback.bulk.deleteConfirmTitle",
|
|
39
|
-
"feedback.bulk.deleteConfirmDesc",
|
|
40
|
-
"feedback.vote.vote",
|
|
41
|
-
"feedback.vote.voted",
|
|
42
|
-
"feedback.relative.daysAgo",
|
|
43
|
-
];
|
|
44
|
-
|
|
45
|
-
const get = (obj: Record<string, unknown>, path: string) =>
|
|
46
|
-
path.split(".").reduce((acc, key) => (acc as any)?.[key], obj);
|
|
47
|
-
|
|
48
|
-
describe("feedback i18n keys", () => {
|
|
49
|
-
it("contains required keys", () => {
|
|
50
|
-
requiredKeys.forEach((key) => {
|
|
51
|
-
expect(get(en as any, key)).toBeTruthy();
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
**Step 2: Run test to verify it fails**
|
|
58
|
-
|
|
59
|
-
Run: `bun test tests/components/feedback-i18n-keys.test.ts`
|
|
60
|
-
Expected: FAIL with missing translation keys.
|
|
61
|
-
|
|
62
|
-
**Step 3: Write minimal implementation**
|
|
63
|
-
|
|
64
|
-
Add the required keys to each locale file. Example (English):
|
|
65
|
-
|
|
66
|
-
```json
|
|
67
|
-
"feedback": {
|
|
68
|
-
"list": {
|
|
69
|
-
"searchPlaceholder": "Search title or description…",
|
|
70
|
-
"searchButton": "Search",
|
|
71
|
-
"sortLabel": "Sort",
|
|
72
|
-
"pageSizeLabel": "Per page",
|
|
73
|
-
"summary": "Total {total} / {pageSize} per page",
|
|
74
|
-
"empty": "No feedback yet",
|
|
75
|
-
"error": "Error: {message}"
|
|
76
|
-
},
|
|
77
|
-
"pagination": {
|
|
78
|
-
"previous": "Previous",
|
|
79
|
-
"next": "Next",
|
|
80
|
-
"jumpTo": "Go to",
|
|
81
|
-
"go": "Go"
|
|
82
|
-
},
|
|
83
|
-
"bulk": {
|
|
84
|
-
"selectedCount": "{count} selected",
|
|
85
|
-
"updateStatus": "Update status",
|
|
86
|
-
"updatePriority": "Update priority",
|
|
87
|
-
"delete": "Delete",
|
|
88
|
-
"deleteConfirmTitle": "Delete selected feedback?",
|
|
89
|
-
"deleteConfirmDesc": "Deleted feedback will no longer appear in the list."
|
|
90
|
-
},
|
|
91
|
-
"vote": {
|
|
92
|
-
"vote": "Vote",
|
|
93
|
-
"voted": "Voted",
|
|
94
|
-
"count": "{count} votes"
|
|
95
|
-
},
|
|
96
|
-
"relative": {
|
|
97
|
-
"today": "Today",
|
|
98
|
-
"yesterday": "Yesterday",
|
|
99
|
-
"daysAgo": "{count} days ago"
|
|
100
|
-
},
|
|
101
|
-
"detail": {
|
|
102
|
-
"createdAt": "Created",
|
|
103
|
-
"updatedAt": "Updated",
|
|
104
|
-
"feedbackId": "Feedback ID",
|
|
105
|
-
"votes": "Votes",
|
|
106
|
-
"description": "Description",
|
|
107
|
-
"attachments": "Attachments",
|
|
108
|
-
"votesTitle": "Votes",
|
|
109
|
-
"statusHistory": "Status History"
|
|
110
|
-
},
|
|
111
|
-
"actions": {
|
|
112
|
-
"label": "Feedback actions"
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
Mirror the same keys in `messages/zh-CN.json` and `messages/jp.json` with localized strings.
|
|
118
|
-
|
|
119
|
-
**Step 4: Run test to verify it passes**
|
|
120
|
-
|
|
121
|
-
Run: `bun test tests/components/feedback-i18n-keys.test.ts`
|
|
122
|
-
Expected: PASS.
|
|
123
|
-
|
|
124
|
-
**Step 5: Commit**
|
|
125
|
-
|
|
126
|
-
```bash
|
|
127
|
-
git add messages/en.json messages/zh-CN.json messages/jp.json tests/components/feedback-i18n-keys.test.ts
|
|
128
|
-
git commit -m "test: assert feedback admin i18n keys"
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
### Task 2: Update list controls (remove assignee, add page-size, i18n labels)
|
|
132
|
-
|
|
133
|
-
**Files:**
|
|
134
|
-
- Modify: `components/feedback/feedback-list-controls.tsx`
|
|
135
|
-
- Modify: `components/feedback/feedback-list.tsx`
|
|
136
|
-
- Modify: `tests/components/feedback-list-controls.test.tsx`
|
|
137
|
-
|
|
138
|
-
**Step 1: Write the failing test**
|
|
139
|
-
|
|
140
|
-
Update the list controls test to expect i18n-driven labels and page size select.
|
|
141
|
-
|
|
142
|
-
```ts
|
|
143
|
-
// tests/components/feedback-list-controls.test.tsx
|
|
144
|
-
mock.module("next-intl", () => ({
|
|
145
|
-
useTranslations: () => (key: string, vars?: Record<string, unknown>) =>
|
|
146
|
-
key === "feedback.list.searchPlaceholder"
|
|
147
|
-
? "Search title or description…"
|
|
148
|
-
: key === "feedback.list.searchButton"
|
|
149
|
-
? "Search"
|
|
150
|
-
: key,
|
|
151
|
-
}));
|
|
152
|
-
|
|
153
|
-
// ...in test
|
|
154
|
-
expect(getByLabelText("Search title or description…")).toBeTruthy();
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
**Step 2: Run test to verify it fails**
|
|
158
|
-
|
|
159
|
-
Run: `bun test tests/components/feedback-list-controls.test.tsx`
|
|
160
|
-
Expected: FAIL because translations and page size are not wired.
|
|
161
|
-
|
|
162
|
-
**Step 3: Write minimal implementation**
|
|
163
|
-
|
|
164
|
-
- Remove assignee state, members fetch, and dropdown.
|
|
165
|
-
- Add `useTranslations("feedback")` and replace hardcoded strings.
|
|
166
|
-
- Add a page-size `<Select>` that updates `pageSize` param.
|
|
167
|
-
- Update `FeedbackList` to read `pageSize` from query and pass into fetch.
|
|
168
|
-
|
|
169
|
-
**Step 4: Run test to verify it passes**
|
|
170
|
-
|
|
171
|
-
Run: `bun test tests/components/feedback-list-controls.test.tsx`
|
|
172
|
-
Expected: PASS.
|
|
173
|
-
|
|
174
|
-
**Step 5: Commit**
|
|
175
|
-
|
|
176
|
-
```bash
|
|
177
|
-
git add components/feedback/feedback-list-controls.tsx components/feedback/feedback-list.tsx tests/components/feedback-list-controls.test.tsx
|
|
178
|
-
git commit -m "feat: localize feedback list controls and drop assignee filter"
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
### Task 3: Safer list items + selection + vote display
|
|
182
|
-
|
|
183
|
-
**Files:**
|
|
184
|
-
- Modify: `components/feedback/feedback-list-item.tsx`
|
|
185
|
-
- Modify: `components/feedback/vote-button.tsx`
|
|
186
|
-
- Add: `tests/components/feedback-list-item.test.tsx`
|
|
187
|
-
|
|
188
|
-
**Step 1: Write the failing test**
|
|
189
|
-
|
|
190
|
-
Create a test that ensures clicking the delete button does not trigger row navigation and that selection checkbox renders.
|
|
191
|
-
|
|
192
|
-
```tsx
|
|
193
|
-
// tests/components/feedback-list-item.test.tsx
|
|
194
|
-
import { describe, expect, it, mock } from "bun:test";
|
|
195
|
-
import { render, fireEvent } from "@testing-library/react";
|
|
196
|
-
import { FeedbackListItem } from "@/components/feedback/feedback-list-item";
|
|
197
|
-
import "../setup";
|
|
198
|
-
|
|
199
|
-
const push = mock();
|
|
200
|
-
mock.module("next/navigation", () => ({ useRouter: () => ({ push }) }));
|
|
201
|
-
mock.module("next-intl", () => ({
|
|
202
|
-
useTranslations: () => (key: string) => key,
|
|
203
|
-
useLocale: () => "en",
|
|
204
|
-
}));
|
|
205
|
-
|
|
206
|
-
it("does not navigate when delete is clicked", () => {
|
|
207
|
-
const { getByLabelText } = render(
|
|
208
|
-
<FeedbackListItem
|
|
209
|
-
feedback={{
|
|
210
|
-
feedbackId: 1,
|
|
211
|
-
title: "Title",
|
|
212
|
-
description: "Desc",
|
|
213
|
-
type: "bug",
|
|
214
|
-
priority: "low",
|
|
215
|
-
status: "new",
|
|
216
|
-
createdAt: new Date().toISOString(),
|
|
217
|
-
voteCount: 2,
|
|
218
|
-
}}
|
|
219
|
-
canDelete
|
|
220
|
-
isSelected={false}
|
|
221
|
-
onSelect={() => {}}
|
|
222
|
-
/>
|
|
223
|
-
);
|
|
224
|
-
|
|
225
|
-
fireEvent.click(getByLabelText("feedback.list.delete"));
|
|
226
|
-
expect(push).not.toHaveBeenCalled();
|
|
227
|
-
});
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
**Step 2: Run test to verify it fails**
|
|
231
|
-
|
|
232
|
-
Run: `bun test tests/components/feedback-list-item.test.tsx`
|
|
233
|
-
Expected: FAIL (no selection props, missing aria label, row click uses Link).
|
|
234
|
-
|
|
235
|
-
**Step 3: Write minimal implementation**
|
|
236
|
-
|
|
237
|
-
- Replace outer `<Link>` with a `<Card>` that calls `router.push` on click.
|
|
238
|
-
- Add checkbox and `isSelected/onSelect` props; stop propagation on checkbox, vote, and delete.
|
|
239
|
-
- Replace `VoteButton` in the list with a non-clickable badge showing `{count} votes`.
|
|
240
|
-
- Localize status/type/priority labels and relative dates using `useTranslations` and `useLocale`.
|
|
241
|
-
- Localize vote button text in `components/feedback/vote-button.tsx` using `useTranslations("feedback")`.
|
|
242
|
-
|
|
243
|
-
**Step 4: Run test to verify it passes**
|
|
244
|
-
|
|
245
|
-
Run: `bun test tests/components/feedback-list-item.test.tsx`
|
|
246
|
-
Expected: PASS.
|
|
247
|
-
|
|
248
|
-
**Step 5: Commit**
|
|
249
|
-
|
|
250
|
-
```bash
|
|
251
|
-
git add components/feedback/feedback-list-item.tsx components/feedback/vote-button.tsx tests/components/feedback-list-item.test.tsx
|
|
252
|
-
git commit -m "feat: safer feedback list items with selection and i18n"
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
### Task 4: Bulk action UI + list state refresh
|
|
256
|
-
|
|
257
|
-
**Files:**
|
|
258
|
-
- Modify: `components/feedback/feedback-list.tsx`
|
|
259
|
-
- Add: `components/feedback/feedback-bulk-actions.tsx`
|
|
260
|
-
|
|
261
|
-
**Step 1: Write the failing test**
|
|
262
|
-
|
|
263
|
-
Add a component test that shows the bulk action bar after selecting rows.
|
|
264
|
-
|
|
265
|
-
```tsx
|
|
266
|
-
// tests/components/feedback-bulk-actions.test.tsx
|
|
267
|
-
import { describe, expect, it, mock } from "bun:test";
|
|
268
|
-
import { render, fireEvent } from "@testing-library/react";
|
|
269
|
-
import { FeedbackBulkActions } from "@/components/feedback/feedback-bulk-actions";
|
|
270
|
-
import "../setup";
|
|
271
|
-
|
|
272
|
-
mock.module("next-intl", () => ({
|
|
273
|
-
useTranslations: () => (key: string, vars?: Record<string, unknown>) =>
|
|
274
|
-
key === "feedback.bulk.selectedCount" ? `${vars?.count} selected` : key,
|
|
275
|
-
}));
|
|
276
|
-
|
|
277
|
-
it("renders selection count", () => {
|
|
278
|
-
const { getByText } = render(
|
|
279
|
-
<FeedbackBulkActions
|
|
280
|
-
selectedIds={[1, 2]}
|
|
281
|
-
onClear={() => {}}
|
|
282
|
-
onCompleted={() => {}}
|
|
283
|
-
/>
|
|
284
|
-
);
|
|
285
|
-
expect(getByText("2 selected")).toBeTruthy();
|
|
286
|
-
});
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
**Step 2: Run test to verify it fails**
|
|
290
|
-
|
|
291
|
-
Run: `bun test tests/components/feedback-bulk-actions.test.tsx`
|
|
292
|
-
Expected: FAIL (component missing).
|
|
293
|
-
|
|
294
|
-
**Step 3: Write minimal implementation**
|
|
295
|
-
|
|
296
|
-
- Create `FeedbackBulkActions` to render count, status/priority selects, and delete button.
|
|
297
|
-
- Call `POST /api/feedback/bulk` for bulk actions.
|
|
298
|
-
- In `FeedbackList`, track `selectedIds`, pass selection props to items, and show the bulk bar when `selectedIds.length > 0`.
|
|
299
|
-
- Add a header checkbox to select all on the current page; clear selection on refresh.
|
|
300
|
-
- Use `fetchFeedback` as a `useCallback` so bulk actions can refresh the list after success.
|
|
301
|
-
|
|
302
|
-
**Step 4: Run test to verify it passes**
|
|
303
|
-
|
|
304
|
-
Run: `bun test tests/components/feedback-bulk-actions.test.tsx`
|
|
305
|
-
Expected: PASS.
|
|
306
|
-
|
|
307
|
-
**Step 5: Commit**
|
|
308
|
-
|
|
309
|
-
```bash
|
|
310
|
-
git add components/feedback/feedback-list.tsx components/feedback/feedback-bulk-actions.tsx tests/components/feedback-bulk-actions.test.tsx
|
|
311
|
-
git commit -m "feat: bulk actions UI for feedback list"
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
### Task 5: Bulk API route (status/priority/delete)
|
|
315
|
-
|
|
316
|
-
**Files:**
|
|
317
|
-
- Add: `app/api/feedback/bulk/route.ts`
|
|
318
|
-
- Modify: `tests/api/feedback-filter.test.ts`
|
|
319
|
-
- Add: `tests/api/feedback-bulk.test.ts`
|
|
320
|
-
|
|
321
|
-
**Step 1: Write the failing test**
|
|
322
|
-
|
|
323
|
-
Add tests for validation and permissions on bulk actions.
|
|
324
|
-
|
|
325
|
-
```ts
|
|
326
|
-
// tests/api/feedback-bulk.test.ts
|
|
327
|
-
import { describe, expect, it, mock } from "bun:test";
|
|
328
|
-
import { NextRequest } from "next/server";
|
|
329
|
-
|
|
330
|
-
mock.module("@/lib/auth/config", () => ({
|
|
331
|
-
auth: { api: { getSession: mock(() => Promise.resolve({ user: { id: "u1" } })) } },
|
|
332
|
-
}));
|
|
333
|
-
|
|
334
|
-
mock.module("@/lib/auth/org-context", () => ({
|
|
335
|
-
getOrgContext: mock(() => Promise.resolve({ organizationId: "org_1", memberRole: "developer" })),
|
|
336
|
-
}));
|
|
337
|
-
|
|
338
|
-
mock.module("@/lib/db", () => ({ db: null }));
|
|
339
|
-
|
|
340
|
-
it("returns 500 when db is missing", async () => {
|
|
341
|
-
const { POST } = await import("@/app/api/feedback/bulk/route");
|
|
342
|
-
const req = new NextRequest("http://localhost/api/feedback/bulk", {
|
|
343
|
-
method: "POST",
|
|
344
|
-
body: JSON.stringify({ action: "delete", ids: [1] }),
|
|
345
|
-
});
|
|
346
|
-
const res = await POST(req);
|
|
347
|
-
expect(res.status).toBe(500);
|
|
348
|
-
});
|
|
349
|
-
```
|
|
350
|
-
|
|
351
|
-
**Step 2: Run test to verify it fails**
|
|
352
|
-
|
|
353
|
-
Run: `bun test tests/api/feedback-bulk.test.ts`
|
|
354
|
-
Expected: FAIL (route not found).
|
|
355
|
-
|
|
356
|
-
**Step 3: Write minimal implementation**
|
|
357
|
-
|
|
358
|
-
- Create `app/api/feedback/bulk/route.ts` with a discriminated union schema.
|
|
359
|
-
- Enforce permissions using `canUpdateFeedbackStatus`, `canEditFeedback`, `canDeleteFeedback`.
|
|
360
|
-
- Load existing rows for org + ids + not deleted.
|
|
361
|
-
- For status updates, insert status history rows with old/new status.
|
|
362
|
-
- For delete, set `deletedAt` and `updatedAt`.
|
|
363
|
-
- Return `{ updatedCount }`.
|
|
364
|
-
|
|
365
|
-
**Step 4: Update filter test (assignee removal)**
|
|
366
|
-
|
|
367
|
-
Update `tests/api/feedback-filter.test.ts` to remove `assignee` from the query and rename the test to "should accept hasVotes filters".
|
|
368
|
-
|
|
369
|
-
**Step 5: Run tests to verify they pass**
|
|
370
|
-
|
|
371
|
-
Run: `bun test tests/api/feedback-bulk.test.ts tests/api/feedback-filter.test.ts`
|
|
372
|
-
Expected: PASS.
|
|
373
|
-
|
|
374
|
-
**Step 6: Commit**
|
|
375
|
-
|
|
376
|
-
```bash
|
|
377
|
-
git add app/api/feedback/bulk/route.ts tests/api/feedback-bulk.test.ts tests/api/feedback-filter.test.ts
|
|
378
|
-
git commit -m "feat: add bulk feedback actions API"
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
### Task 6: Detail view i18n + pagination enhancements
|
|
382
|
-
|
|
383
|
-
**Files:**
|
|
384
|
-
- Modify: `components/feedback/feedback-detail-view.tsx`
|
|
385
|
-
- Modify: `components/shared/pagination.tsx`
|
|
386
|
-
|
|
387
|
-
**Step 1: Write the failing test**
|
|
388
|
-
|
|
389
|
-
Add a test for pagination controls showing jump input labels.
|
|
390
|
-
|
|
391
|
-
```ts
|
|
392
|
-
// tests/components/pagination.test.tsx
|
|
393
|
-
import { describe, expect, it } from "bun:test";
|
|
394
|
-
import { render } from "@testing-library/react";
|
|
395
|
-
import { Pagination } from "@/components/shared/pagination";
|
|
396
|
-
import "../setup";
|
|
397
|
-
|
|
398
|
-
mock.module("next-intl", () => ({
|
|
399
|
-
useTranslations: () => (key: string) => key,
|
|
400
|
-
}));
|
|
401
|
-
|
|
402
|
-
it("renders jump controls", () => {
|
|
403
|
-
const { getByText } = render(
|
|
404
|
-
<Pagination currentPage={1} totalPages={3} onPageChange={() => {}} />
|
|
405
|
-
);
|
|
406
|
-
expect(getByText("feedback.pagination.jumpTo")).toBeTruthy();
|
|
407
|
-
});
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
**Step 2: Run test to verify it fails**
|
|
411
|
-
|
|
412
|
-
Run: `bun test tests/components/pagination.test.tsx`
|
|
413
|
-
Expected: FAIL (pagination has no jump controls).
|
|
414
|
-
|
|
415
|
-
**Step 3: Write minimal implementation**
|
|
416
|
-
|
|
417
|
-
- Localize detail view labels and date formatting via `useTranslations` + `useLocale`.
|
|
418
|
-
- Update `Pagination` to include a small jump-to input + button, and localize labels.
|
|
419
|
-
|
|
420
|
-
**Step 4: Run test to verify it passes**
|
|
421
|
-
|
|
422
|
-
Run: `bun test tests/components/pagination.test.tsx`
|
|
423
|
-
Expected: PASS.
|
|
424
|
-
|
|
425
|
-
**Step 5: Commit**
|
|
426
|
-
|
|
427
|
-
```bash
|
|
428
|
-
git add components/feedback/feedback-detail-view.tsx components/shared/pagination.tsx tests/components/pagination.test.tsx
|
|
429
|
-
git commit -m "feat: localize feedback detail view and enhance pagination"
|
|
430
|
-
```
|
|
431
|
-
|
|
432
|
-
### Task 7: Verification
|
|
433
|
-
|
|
434
|
-
**Files:**
|
|
435
|
-
- None
|
|
436
|
-
|
|
437
|
-
**Step 1: Run test suite**
|
|
438
|
-
|
|
439
|
-
Run: `bun test`
|
|
440
|
-
Expected: PASS (note existing domain lookup warnings are acceptable).
|
|
441
|
-
|
|
442
|
-
**Step 2: Manual smoke check**
|
|
443
|
-
|
|
444
|
-
Run: `bun dev` and verify `/admin/feedback` list selection, bulk actions, pagination, and detail view labels in all locales.
|
|
445
|
-
|
|
446
|
-
**Step 3: Commit (if needed)**
|
|
447
|
-
|
|
448
|
-
If any fixes were required after smoke testing:
|
|
449
|
-
|
|
450
|
-
```bash
|
|
451
|
-
git add .
|
|
452
|
-
git commit -m "fix: address admin feedback UX regressions"
|
|
453
|
-
```
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# Changesets 自动版本与 Changelog 设计
|
|
2
|
-
|
|
3
|
-
## 目标
|
|
4
|
-
|
|
5
|
-
- 使用 Changesets 管理版本与 `CHANGELOG.md`
|
|
6
|
-
- 合并到 `main` 后自动生成版本、打 tag,并触发 npm 发布
|
|
7
|
-
- 保持发布流程简单、可追溯
|
|
8
|
-
|
|
9
|
-
## 方案概述
|
|
10
|
-
|
|
11
|
-
- 安装 `@changesets/cli` 并初始化 `.changeset` 配置
|
|
12
|
-
- 开发者在 PR 中添加 changeset 文件描述版本级别与变更内容
|
|
13
|
-
- `Release` workflow 在 `main` 上运行:
|
|
14
|
-
- 执行 `changeset version` 生成 `CHANGELOG.md` 与版本更新
|
|
15
|
-
- 提交版本变更并打 tag `vX.Y.Z`
|
|
16
|
-
- 推送到 `main` 与 tag
|
|
17
|
-
- `Publish` workflow 继续监听 tag(`v*`)并发布到 npm
|
|
18
|
-
|
|
19
|
-
## 目录与配置
|
|
20
|
-
|
|
21
|
-
- `.changeset/config.json`:Changesets 配置
|
|
22
|
-
- `.changeset/README.md`:使用说明
|
|
23
|
-
- `package.json`:新增 changeset 脚本与 publishConfig
|
|
24
|
-
- `.github/workflows/release.yml`:自动 version + tag
|
|
25
|
-
- `.github/workflows/publish.yml`:tag 后发布
|
|
26
|
-
|
|
27
|
-
## 失败与回滚
|
|
28
|
-
|
|
29
|
-
- 若 `changeset version` 无变化,Release workflow 直接退出
|
|
30
|
-
- 如需回滚发布,按 npm 与 git tag 常规流程处理
|
|
31
|
-
|
|
32
|
-
## 测试与验证
|
|
33
|
-
|
|
34
|
-
- 合并含 changeset 的 PR 后观察 `Release` workflow 生成版本与 tag
|
|
35
|
-
- 确认 `Publish` workflow 由 tag 触发并发布到 npm
|
|
36
|
-
|
|
37
|
-
## 取舍
|
|
38
|
-
|
|
39
|
-
- 自动化程度高,减少手动版本管理
|
|
40
|
-
- Release workflow 会在 `main` 上产生额外提交
|