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