@nexttylabs/echo 0.3.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.
Files changed (248) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/app/(public)/[organizationSlug]/roadmap/page.tsx +19 -1
  3. package/app/api/admin/backup/route.ts +22 -4
  4. package/app/api/auth/register/handler.ts +1 -2
  5. package/lib/auth/config.ts +0 -7
  6. package/lib/db/migrations/0000_needy_leech.sql +335 -0
  7. package/lib/db/migrations/meta/0000_snapshot.json +2186 -1
  8. package/lib/db/migrations/meta/_journal.json +2 -135
  9. package/lib/db/schema/auth.ts +0 -1
  10. package/lib/db/schema/index.ts +0 -1
  11. package/lib/portal/public-context.tsx +5 -0
  12. package/package.json +20 -1
  13. package/.changeset/README.md +0 -21
  14. package/.changeset/config.json +0 -11
  15. package/.changeset/cozy-ghosts-care.md +0 -5
  16. package/.changeset/sharp-lines-stand.md +0 -5
  17. package/.changeset/sour-doodles-eat.md +0 -5
  18. package/.changeset/tender-moose-shop.md +0 -5
  19. package/.github/pull_request_template.md +0 -13
  20. package/.github/workflows/ci.yml +0 -41
  21. package/.github/workflows/publish.yml +0 -44
  22. package/.github/workflows/release.yml +0 -73
  23. package/AGENTS.md +0 -92
  24. package/Dockerfile +0 -57
  25. package/Makefile +0 -77
  26. package/app/api/internal/domain-lookup/route.ts +0 -67
  27. package/bun.lock +0 -2503
  28. package/components/portal/project-switcher.tsx +0 -20
  29. package/docker-compose.dev.yml +0 -26
  30. package/docker-compose.yml +0 -98
  31. package/docs/architecture.md +0 -259
  32. package/docs/component-inventory.md +0 -261
  33. package/docs/database-migrations.md +0 -76
  34. package/docs/development-guide.md +0 -209
  35. package/docs/e2e-user-flows.csv +0 -31
  36. package/docs/er-diagram-feedback.mmd +0 -138
  37. package/docs/er-diagram.mmd +0 -281
  38. package/docs/i18n-check-report.md +0 -296
  39. package/docs/index.md +0 -214
  40. package/docs/logic-chain.md +0 -94
  41. package/docs/plans/2026-01-02-database-migration-scripts.md +0 -496
  42. package/docs/plans/2026-01-02-user-login-design.md +0 -37
  43. package/docs/plans/2026-01-02-user-login.md +0 -437
  44. package/docs/plans/2026-01-02-user-registration-design.md +0 -47
  45. package/docs/plans/2026-01-02-user-registration.md +0 -628
  46. package/docs/plans/2026-01-03-roles-permissions-design.md +0 -20
  47. package/docs/plans/2026-01-03-roles-permissions.md +0 -266
  48. package/docs/plans/2026-01-05-authentication-middleware.md +0 -207
  49. package/docs/plans/2026-01-05-member-removal.md +0 -186
  50. package/docs/plans/2026-01-05-organization-creation.md +0 -374
  51. package/docs/plans/2026-01-05-rbac-middleware.md +0 -112
  52. package/docs/plans/2026-01-05-role-configuration.md +0 -441
  53. package/docs/plans/2026-01-06-file-upload-support.md +0 -804
  54. package/docs/plans/2026-01-06-permission-check-hook.md +0 -155
  55. package/docs/plans/2026-01-06-resource-ownership-check.md +0 -231
  56. package/docs/plans/2026-01-07-feedback-tracking-link.md +0 -459
  57. package/docs/plans/2026-01-09-logout-redirect-design.md +0 -52
  58. package/docs/plans/2026-01-09-phase2-3-plan.md +0 -654
  59. package/docs/plans/2026-01-09-portal-execution-plan.md +0 -408
  60. package/docs/plans/2026-01-09-project-delete-feature-design.md +0 -163
  61. package/docs/plans/2026-01-09-project-delete-implementation.md +0 -451
  62. package/docs/plans/2026-01-09-project-edit-delete-design.md +0 -52
  63. package/docs/plans/2026-01-09-settings-center-design.md +0 -114
  64. package/docs/plans/2026-01-09-settings-center.md +0 -948
  65. package/docs/plans/2026-01-10-organization-only-design.md +0 -66
  66. package/docs/plans/2026-01-10-organization-only-implementation.md +0 -433
  67. package/docs/plans/2026-01-10-portal-settings-restructure-plan.md +0 -18
  68. package/docs/plans/2026-01-10-project-settings-tabs-design-implementation.md +0 -296
  69. package/docs/plans/2026-01-14-e2e-playwright-feedback.md +0 -173
  70. package/docs/plans/2026-01-15-feedback-management-org-context-design.md +0 -82
  71. package/docs/plans/2026-01-15-feedback-management-org-context-implementation-plan.md +0 -521
  72. package/docs/plans/2026-01-16-admin-feedback-filters-design.md +0 -75
  73. package/docs/plans/2026-01-16-admin-feedback-filters-implementation.md +0 -293
  74. package/docs/plans/2026-01-16-admin-feedback-route-consolidation.md +0 -180
  75. package/docs/plans/2026-01-16-e2e-test-fixes.md +0 -158
  76. package/docs/plans/2026-01-17-admin-feedback-filters.md +0 -214
  77. package/docs/plans/2026-01-17-admin-feedback-improvements.md +0 -453
  78. package/docs/plans/2026-01-18-changesets-design.md +0 -40
  79. package/docs/product_changes.md +0 -37
  80. package/docs/project-overview.md +0 -159
  81. package/docs/project-scan-report.json +0 -104
  82. package/docs/route-role-visibility.md +0 -51
  83. package/docs/source-tree-analysis.md +0 -150
  84. package/docs/testing/delete-project-manual-tests.md +0 -18
  85. package/docs/user-story-tracking.md +0 -191
  86. package/eslint.config.mjs +0 -19
  87. package/lib/db/migrations/.gitkeep +0 -0
  88. package/lib/db/migrations/0000_cynical_gladiator.sql +0 -53
  89. package/lib/db/migrations/0001_wandering_sunfire.sql +0 -27
  90. package/lib/db/migrations/0002_shallow_speedball.sql +0 -1
  91. package/lib/db/migrations/0003_add_org_description.sql +0 -1
  92. package/lib/db/migrations/0003_boring_wild_pack.sql +0 -13
  93. package/lib/db/migrations/0004_windy_tyrannus.sql +0 -27
  94. package/lib/db/migrations/0005_perpetual_doorman.sql +0 -5
  95. package/lib/db/migrations/0006_aberrant_captain_midlands.sql +0 -13
  96. package/lib/db/migrations/0007_clever_captain_cross.sql +0 -14
  97. package/lib/db/migrations/0008_sparkling_pandemic.sql +0 -2
  98. package/lib/db/migrations/0009_happy_black_tom.sql +0 -29
  99. package/lib/db/migrations/0010_kind_junta.sql +0 -8
  100. package/lib/db/migrations/0011_mute_squadron_supreme.sql +0 -25
  101. package/lib/db/migrations/0012_giant_power_man.sql +0 -24
  102. package/lib/db/migrations/0013_damp_titanium_man.sql +0 -17
  103. package/lib/db/migrations/0014_blue_alice.sql +0 -18
  104. package/lib/db/migrations/0015_webhook_tables.sql +0 -41
  105. package/lib/db/migrations/0016_github_integration.sql +0 -30
  106. package/lib/db/migrations/0016_overjoyed_ghost_rider.sql +0 -22
  107. package/lib/db/migrations/0017_slimy_inhumans.sql +0 -6
  108. package/lib/db/migrations/0018_same_spitfire.sql +0 -1
  109. package/lib/db/migrations/0019_jittery_loners.sql +0 -16
  110. package/lib/db/migrations/0019_remove_projects_add_org_settings.sql +0 -14
  111. package/lib/db/migrations/meta/0001_snapshot.json +0 -553
  112. package/lib/db/migrations/meta/0002_snapshot.json +0 -560
  113. package/lib/db/migrations/meta/0003_snapshot.json +0 -650
  114. package/lib/db/migrations/meta/0004_snapshot.json +0 -852
  115. package/lib/db/migrations/meta/0005_snapshot.json +0 -900
  116. package/lib/db/migrations/meta/0006_snapshot.json +0 -1011
  117. package/lib/db/migrations/meta/0007_snapshot.json +0 -1125
  118. package/lib/db/migrations/meta/0008_snapshot.json +0 -1146
  119. package/lib/db/migrations/meta/0009_snapshot.json +0 -1386
  120. package/lib/db/migrations/meta/0010_snapshot.json +0 -1419
  121. package/lib/db/migrations/meta/0011_snapshot.json +0 -1615
  122. package/lib/db/migrations/meta/0012_snapshot.json +0 -1805
  123. package/lib/db/migrations/meta/0013_snapshot.json +0 -1948
  124. package/lib/db/migrations/meta/0014_snapshot.json +0 -2082
  125. package/lib/db/migrations/meta/0015_snapshot.json +0 -2476
  126. package/lib/db/migrations/meta/0016_snapshot.json +0 -2633
  127. package/lib/db/migrations/meta/0017_snapshot.json +0 -2680
  128. package/lib/db/migrations/meta/0018_snapshot.json +0 -2686
  129. package/lib/db/migrations/meta/0019_snapshot.json +0 -2741
  130. package/lib/db/schema/projects.ts +0 -145
  131. package/lib/db/schema/user-profiles.ts +0 -31
  132. package/lib/validations/projects.ts +0 -49
  133. package/next-env.d.ts +0 -6
  134. package/playwright.config.ts +0 -44
  135. package/proxy.test.ts +0 -131
  136. package/proxy.ts +0 -190
  137. package/scripts/backup-db.sh +0 -57
  138. package/scripts/backup-db.ts +0 -24
  139. package/scripts/generate-openapi.ts +0 -22
  140. package/scripts/migration-helper.ts +0 -39
  141. package/scripts/pre-deploy.ts +0 -75
  142. package/scripts/restore-db.sh +0 -60
  143. package/scripts/rollback.ts +0 -72
  144. package/scripts/seed-tags.ts +0 -48
  145. package/tests/api/feedback-bulk.test.ts +0 -47
  146. package/tests/api/feedback-by-id.test.ts +0 -67
  147. package/tests/api/feedback-comments-route-import.test.ts +0 -26
  148. package/tests/api/feedback-create.test.ts +0 -71
  149. package/tests/api/feedback-delete.test.ts +0 -160
  150. package/tests/api/feedback-filter.test.ts +0 -250
  151. package/tests/api/feedback-list.test.ts +0 -234
  152. package/tests/api/feedback-route-assignee-condition.test.ts +0 -32
  153. package/tests/api/feedback-similar.test.ts +0 -46
  154. package/tests/api/feedback-sort.test.ts +0 -261
  155. package/tests/api/feedback-status-enum.test.ts +0 -49
  156. package/tests/api/feedback-status-filter.test.ts +0 -117
  157. package/tests/api/feedback-submit-on-behalf.test.ts +0 -269
  158. package/tests/api/feedback.test.ts +0 -175
  159. package/tests/api/identify-jwt.test.ts +0 -25
  160. package/tests/api/invitation-accept.test.ts +0 -213
  161. package/tests/api/organization-invitations.test.ts +0 -186
  162. package/tests/api/organization-members-list.test.ts +0 -79
  163. package/tests/api/organization-members.test.ts +0 -340
  164. package/tests/api/organizations.test.ts +0 -149
  165. package/tests/api/register.test.ts +0 -112
  166. package/tests/api/upload.test.ts +0 -103
  167. package/tests/api/vote.test.ts +0 -82
  168. package/tests/app/admin-feedback-detail-page.test.tsx +0 -25
  169. package/tests/app/admin-feedback-list-page.test.tsx +0 -25
  170. package/tests/app/admin-feedback-new-page.test.tsx +0 -25
  171. package/tests/app/health-route-helpers.test.ts +0 -27
  172. package/tests/app/login-page.test.ts +0 -26
  173. package/tests/app/portal-page.test.ts +0 -29
  174. package/tests/app/project-portal-overview.test.tsx +0 -25
  175. package/tests/app/widget-page-import.test.ts +0 -25
  176. package/tests/components/create-post-dialog-defaults.test.ts +0 -43
  177. package/tests/components/feedback/duplicate-suggestions-inline.test.tsx +0 -27
  178. package/tests/components/feedback/embedded-feedback-form.test.tsx +0 -96
  179. package/tests/components/feedback/feedback-detail.test.tsx +0 -25
  180. package/tests/components/feedback/feedback-stats.test.tsx +0 -49
  181. package/tests/components/feedback-bulk-actions.test.tsx +0 -39
  182. package/tests/components/feedback-i18n-keys.test.ts +0 -70
  183. package/tests/components/feedback-list-controls-compile.test.ts +0 -25
  184. package/tests/components/feedback-list-controls.test.tsx +0 -204
  185. package/tests/components/feedback-list-item.test.tsx +0 -67
  186. package/tests/components/landing/hero.test.tsx +0 -46
  187. package/tests/components/layout/language-switcher.test.tsx +0 -25
  188. package/tests/components/layout/sidebar.test.tsx +0 -157
  189. package/tests/components/login-form.test.ts +0 -25
  190. package/tests/components/organization-form.test.ts +0 -32
  191. package/tests/components/organization-switcher.test.ts +0 -25
  192. package/tests/components/pagination.test.tsx +0 -43
  193. package/tests/components/portal-overview.test.tsx +0 -25
  194. package/tests/components/profile-form.test.tsx +0 -139
  195. package/tests/components/role-selector.test.ts +0 -31
  196. package/tests/components/status-chart.test.tsx +0 -90
  197. package/tests/e2e/auth.e2e.ts +0 -323
  198. package/tests/e2e/feedback-actions.e2e.ts +0 -471
  199. package/tests/e2e/feedback-attachment.e2e.ts +0 -168
  200. package/tests/e2e/feedback-customer.e2e.ts +0 -226
  201. package/tests/e2e/feedback-management.e2e.ts +0 -565
  202. package/tests/e2e/feedback-submit.e2e.ts +0 -133
  203. package/tests/e2e/feedback-view.e2e.ts +0 -297
  204. package/tests/e2e/fixtures/test-data.ts +0 -235
  205. package/tests/e2e/health-check.e2e.ts +0 -230
  206. package/tests/e2e/helpers/test-utils-helpers.test.ts +0 -43
  207. package/tests/e2e/helpers/test-utils.ts +0 -298
  208. package/tests/e2e/integration-placeholders.e2e.ts +0 -199
  209. package/tests/e2e/organization.e2e.ts +0 -292
  210. package/tests/e2e/permissions.e2e.ts +0 -424
  211. package/tests/e2e/project-widget.e2e.ts +0 -63
  212. package/tests/feedback/filters.test.ts +0 -29
  213. package/tests/hooks/use-permissions.test.ts +0 -52
  214. package/tests/lib/ai/classifier.test.ts +0 -104
  215. package/tests/lib/ai/duplicate-detector.test.ts +0 -234
  216. package/tests/lib/attachments-schema.test.ts +0 -30
  217. package/tests/lib/auth/session.test.ts +0 -49
  218. package/tests/lib/auth-client.test.ts +0 -37
  219. package/tests/lib/auth-config.test.ts +0 -26
  220. package/tests/lib/feedback-prefill.test.ts +0 -52
  221. package/tests/lib/feedback-processor.test.ts +0 -41
  222. package/tests/lib/feedback-schema.test.ts +0 -33
  223. package/tests/lib/file-validator.test.ts +0 -48
  224. package/tests/lib/get-feedback-by-id.test.ts +0 -37
  225. package/tests/lib/invitations.test.ts +0 -35
  226. package/tests/lib/login-schema.test.ts +0 -36
  227. package/tests/lib/org-context.test.ts +0 -95
  228. package/tests/lib/organization-access.test.ts +0 -44
  229. package/tests/lib/organization-member-role-schema.test.ts +0 -41
  230. package/tests/lib/permissions.test.ts +0 -88
  231. package/tests/lib/portal-analytics.test.ts +0 -25
  232. package/tests/lib/portal-contributors.test.ts +0 -25
  233. package/tests/lib/portal-copy.test.ts +0 -27
  234. package/tests/lib/portal-i18n.test.ts +0 -30
  235. package/tests/lib/portal-leaderboard-settings.test.ts +0 -25
  236. package/tests/lib/portal-modules.test.ts +0 -25
  237. package/tests/lib/portal-seo.test.ts +0 -25
  238. package/tests/lib/portal-sharing.test.ts +0 -25
  239. package/tests/lib/portal-sorting.test.ts +0 -25
  240. package/tests/lib/portal-theme.test.ts +0 -25
  241. package/tests/lib/rate-limit.test.ts +0 -142
  242. package/tests/lib/resolve-locale.test.ts +0 -34
  243. package/tests/lib/services/backup.test.ts +0 -145
  244. package/tests/lib/user-organizations.test.ts +0 -42
  245. package/tests/lib/user-role-schema.test.ts +0 -33
  246. package/tests/lib/user-schema.test.ts +0 -25
  247. package/tests/setup.ts +0 -74
  248. 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` 上产生额外提交