@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,948 +0,0 @@
1
- # Settings Center Implementation Plan
2
-
3
- > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4
-
5
- **Goal:** Consolidate all settings into a unified Settings Center accessible from user dropdown menu.
6
-
7
- **Architecture:**
8
- - Remove settings links from main navigation, add user dropdown menu with Settings/Logout
9
- - Create `/settings` layout with left sidebar navigation and right content area
10
- - Reuse existing notification preferences component, create new profile/appearance pages
11
-
12
- **Tech Stack:** Next.js App Router, Radix UI DropdownMenu, Tailwind CSS
13
-
14
- ---
15
-
16
- ## Task 1: Simplify Sidebar Navigation
17
-
18
- **Files:**
19
- - Modify: `components/layout/sidebar.tsx`
20
-
21
- **Step 1: Remove settings-related nav items from navigation**
22
-
23
- Replace lines 55-63 with:
24
-
25
- ```tsx
26
- const navItems = [
27
- { href: "/dashboard", label: "仪表盘", icon: LayoutDashboard },
28
- { href: "/feedback", label: "反馈管理", icon: MessageSquare },
29
- ];
30
- ```
31
-
32
- Remove `adminNavItems` array entirely and its rendering (lines 61-63 and 110-124).
33
-
34
- **Step 2: Run build to verify no errors**
35
-
36
- Run: `bun run build 2>&1 | tail -10`
37
- Expected: Build succeeds
38
-
39
- **Step 3: Commit**
40
-
41
- ```bash
42
- git add components/layout/sidebar.tsx
43
- git commit -m "refactor: remove settings links from main navigation"
44
- ```
45
-
46
- ---
47
-
48
- ## Task 2: Add User Dropdown Menu to Sidebar
49
-
50
- **Files:**
51
- - Modify: `components/layout/sidebar.tsx`
52
-
53
- **Step 1: Add dropdown menu imports**
54
-
55
- Add to imports:
56
-
57
- ```tsx
58
- import {
59
- DropdownMenu,
60
- DropdownMenuContent,
61
- DropdownMenuItem,
62
- DropdownMenuSeparator,
63
- DropdownMenuTrigger,
64
- } from "@/components/ui/dropdown-menu";
65
- import { ChevronUp } from "lucide-react";
66
- ```
67
-
68
- **Step 2: Replace user profile section (lines 165-191)**
69
-
70
- Replace the entire `{/* User Profile */}` section with:
71
-
72
- ```tsx
73
- {/* User Profile Dropdown */}
74
- <div className="border-t p-4">
75
- <DropdownMenu>
76
- <DropdownMenuTrigger asChild>
77
- <button className="flex w-full items-center gap-3 rounded-lg px-2 py-2 hover:bg-muted transition-colors">
78
- <Avatar className="h-9 w-9">
79
- <AvatarImage src={user.image || undefined} alt={user.name} />
80
- <AvatarFallback>{user.name.slice(0, 2).toUpperCase()}</AvatarFallback>
81
- </Avatar>
82
- <div className="flex-1 min-w-0 text-left">
83
- <p className="text-sm font-medium truncate">{user.name}</p>
84
- <p className="text-xs text-muted-foreground truncate">{user.email}</p>
85
- </div>
86
- <ChevronUp className="h-4 w-4 text-muted-foreground" />
87
- </button>
88
- </DropdownMenuTrigger>
89
- <DropdownMenuContent align="end" className="w-56">
90
- <DropdownMenuItem asChild>
91
- <Link href="/settings" onClick={handleLinkClick}>
92
- <Settings className="mr-2 h-4 w-4" />
93
- 设置
94
- </Link>
95
- </DropdownMenuItem>
96
- <DropdownMenuSeparator />
97
- <DropdownMenuItem onClick={handleLogout}>
98
- <LogOut className="mr-2 h-4 w-4" />
99
- 退出登录
100
- </DropdownMenuItem>
101
- </DropdownMenuContent>
102
- </DropdownMenu>
103
- </div>
104
- ```
105
-
106
- **Step 3: Remove unused imports**
107
-
108
- Remove `Badge` and `Building2` from imports (no longer used).
109
-
110
- **Step 4: Remove unused code**
111
-
112
- Remove `roleLabels` object and `isAdmin` variable if no longer used.
113
-
114
- **Step 5: Run build to verify**
115
-
116
- Run: `bun run build 2>&1 | tail -10`
117
- Expected: Build succeeds
118
-
119
- **Step 6: Commit**
120
-
121
- ```bash
122
- git add components/layout/sidebar.tsx
123
- git commit -m "feat: add user dropdown menu with settings and logout"
124
- ```
125
-
126
- ---
127
-
128
- ## Task 3: Create Settings Layout with Sidebar
129
-
130
- **Files:**
131
- - Create: `app/(dashboard)/settings/layout.tsx`
132
- - Create: `components/settings/settings-sidebar.tsx`
133
- - Create: `components/settings/index.ts`
134
-
135
- **Step 1: Create settings sidebar component**
136
-
137
- Create `components/settings/settings-sidebar.tsx`:
138
-
139
- ```tsx
140
- "use client";
141
-
142
- import Link from "next/link";
143
- import { usePathname } from "next/navigation";
144
- import { cn } from "@/lib/utils";
145
- import { User, Bell, Palette, Building2, Key } from "lucide-react";
146
- import type { UserRole } from "@/lib/auth/permissions";
147
-
148
- interface SettingsSidebarProps {
149
- userRole: UserRole;
150
- }
151
-
152
- export function SettingsSidebar({ userRole }: SettingsSidebarProps) {
153
- const pathname = usePathname();
154
- const isAdmin = userRole === "admin";
155
- const isAdminOrPM = isAdmin || userRole === "product_manager";
156
-
157
- const menuItems = [
158
- { href: "/settings/profile", label: "个人资料", icon: User },
159
- { href: "/settings/notifications", label: "通知设置", icon: Bell },
160
- { href: "/settings/appearance", label: "外观设置", icon: Palette },
161
- ];
162
-
163
- const adminItems = [
164
- { href: "/settings/organization", label: "组织管理", icon: Building2, show: isAdmin },
165
- { href: "/settings/api-keys", label: "API 密钥", icon: Key, show: isAdminOrPM },
166
- ];
167
-
168
- return (
169
- <nav className="w-56 shrink-0 border-r bg-muted/30 p-4">
170
- <h2 className="mb-4 px-2 text-lg font-semibold">设置</h2>
171
- <div className="space-y-1">
172
- {menuItems.map((item) => (
173
- <Link
174
- key={item.href}
175
- href={item.href}
176
- className={cn(
177
- "flex items-center gap-3 rounded-lg px-3 py-2 text-sm transition-colors hover:bg-muted",
178
- pathname === item.href && "bg-muted font-medium"
179
- )}
180
- >
181
- <item.icon className="h-4 w-4" />
182
- {item.label}
183
- </Link>
184
- ))}
185
- {adminItems.filter(item => item.show).map((item) => (
186
- <Link
187
- key={item.href}
188
- href={item.href}
189
- className={cn(
190
- "flex items-center gap-3 rounded-lg px-3 py-2 text-sm transition-colors hover:bg-muted",
191
- pathname === item.href && "bg-muted font-medium"
192
- )}
193
- >
194
- <item.icon className="h-4 w-4" />
195
- {item.label}
196
- </Link>
197
- ))}
198
- </div>
199
- </nav>
200
- );
201
- }
202
- ```
203
-
204
- **Step 2: Create settings index export**
205
-
206
- Create `components/settings/index.ts`:
207
-
208
- ```ts
209
- export { SettingsSidebar } from "./settings-sidebar";
210
- ```
211
-
212
- **Step 3: Create settings layout**
213
-
214
- Create `app/(dashboard)/settings/layout.tsx`:
215
-
216
- ```tsx
217
- import { headers } from "next/headers";
218
- import { redirect } from "next/navigation";
219
- import { auth } from "@/lib/auth/config";
220
- import { SettingsSidebar } from "@/components/settings";
221
- import type { UserRole } from "@/lib/auth/permissions";
222
-
223
- export default async function SettingsLayout({
224
- children,
225
- }: {
226
- children: React.ReactNode;
227
- }) {
228
- const session = await auth.api.getSession({ headers: await headers() });
229
-
230
- if (!session?.user) {
231
- redirect("/login");
232
- }
233
-
234
- const userRole = (session.user as { role?: string }).role as UserRole || "customer";
235
-
236
- return (
237
- <div className="flex min-h-[calc(100vh-3.5rem)]">
238
- <SettingsSidebar userRole={userRole} />
239
- <main className="flex-1 p-6">{children}</main>
240
- </div>
241
- );
242
- }
243
- ```
244
-
245
- **Step 4: Run build to verify**
246
-
247
- Run: `bun run build 2>&1 | tail -10`
248
- Expected: Build succeeds
249
-
250
- **Step 5: Commit**
251
-
252
- ```bash
253
- git add components/settings/settings-sidebar.tsx components/settings/index.ts app/\(dashboard\)/settings/layout.tsx
254
- git commit -m "feat: add settings layout with sidebar navigation"
255
- ```
256
-
257
- ---
258
-
259
- ## Task 4: Create Settings Root Page (Redirect)
260
-
261
- **Files:**
262
- - Create: `app/(dashboard)/settings/page.tsx`
263
-
264
- **Step 1: Create redirect page**
265
-
266
- Create `app/(dashboard)/settings/page.tsx`:
267
-
268
- ```tsx
269
- import { redirect } from "next/navigation";
270
-
271
- export default function SettingsPage() {
272
- redirect("/settings/profile");
273
- }
274
- ```
275
-
276
- **Step 2: Run build to verify**
277
-
278
- Run: `bun run build 2>&1 | tail -10`
279
- Expected: Build succeeds
280
-
281
- **Step 3: Commit**
282
-
283
- ```bash
284
- git add app/\(dashboard\)/settings/page.tsx
285
- git commit -m "feat: add settings root page with redirect to profile"
286
- ```
287
-
288
- ---
289
-
290
- ## Task 5: Create Profile Settings Page
291
-
292
- **Files:**
293
- - Create: `app/(dashboard)/settings/profile/page.tsx`
294
- - Create: `components/settings/profile-form.tsx`
295
-
296
- **Step 1: Create profile form component**
297
-
298
- Create `components/settings/profile-form.tsx`:
299
-
300
- ```tsx
301
- "use client";
302
-
303
- import { useState } from "react";
304
- import { Button } from "@/components/ui/button";
305
- import { Input } from "@/components/ui/input";
306
- import { Label } from "@/components/ui/label";
307
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
308
- import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
309
-
310
- interface ProfileFormProps {
311
- user: {
312
- id: string;
313
- name: string;
314
- email: string;
315
- image?: string | null;
316
- };
317
- }
318
-
319
- export function ProfileForm({ user }: ProfileFormProps) {
320
- const [name, setName] = useState(user.name);
321
- const [isLoading, setIsLoading] = useState(false);
322
-
323
- const handleSubmit = async (e: React.FormEvent) => {
324
- e.preventDefault();
325
- setIsLoading(true);
326
- // TODO: Implement profile update API
327
- setTimeout(() => setIsLoading(false), 1000);
328
- };
329
-
330
- return (
331
- <div className="space-y-6">
332
- <Card>
333
- <CardHeader>
334
- <CardTitle>个人资料</CardTitle>
335
- <CardDescription>管理您的账户信息</CardDescription>
336
- </CardHeader>
337
- <CardContent>
338
- <form onSubmit={handleSubmit} className="space-y-6">
339
- <div className="flex items-center gap-4">
340
- <Avatar className="h-20 w-20">
341
- <AvatarImage src={user.image || undefined} alt={user.name} />
342
- <AvatarFallback className="text-lg">
343
- {user.name.slice(0, 2).toUpperCase()}
344
- </AvatarFallback>
345
- </Avatar>
346
- <Button type="button" variant="outline" disabled>
347
- 更换头像
348
- </Button>
349
- </div>
350
-
351
- <div className="space-y-2">
352
- <Label htmlFor="name">姓名</Label>
353
- <Input
354
- id="name"
355
- value={name}
356
- onChange={(e) => setName(e.target.value)}
357
- placeholder="您的姓名"
358
- />
359
- </div>
360
-
361
- <div className="space-y-2">
362
- <Label htmlFor="email">邮箱</Label>
363
- <Input
364
- id="email"
365
- value={user.email}
366
- disabled
367
- className="bg-muted"
368
- />
369
- <p className="text-xs text-muted-foreground">邮箱地址不可修改</p>
370
- </div>
371
-
372
- <Button type="submit" disabled={isLoading}>
373
- {isLoading ? "保存中..." : "保存更改"}
374
- </Button>
375
- </form>
376
- </CardContent>
377
- </Card>
378
-
379
- <Card>
380
- <CardHeader>
381
- <CardTitle>修改密码</CardTitle>
382
- <CardDescription>更新您的登录密码</CardDescription>
383
- </CardHeader>
384
- <CardContent>
385
- <Button variant="outline" disabled>
386
- 修改密码
387
- </Button>
388
- <p className="mt-2 text-xs text-muted-foreground">密码修改功能即将推出</p>
389
- </CardContent>
390
- </Card>
391
- </div>
392
- );
393
- }
394
- ```
395
-
396
- **Step 2: Create profile page**
397
-
398
- Create `app/(dashboard)/settings/profile/page.tsx`:
399
-
400
- ```tsx
401
- import { headers } from "next/headers";
402
- import { redirect } from "next/navigation";
403
- import { auth } from "@/lib/auth/config";
404
- import { ProfileForm } from "@/components/settings/profile-form";
405
-
406
- export const metadata = {
407
- title: "个人资料 - Echo",
408
- };
409
-
410
- export default async function ProfileSettingsPage() {
411
- const session = await auth.api.getSession({ headers: await headers() });
412
-
413
- if (!session?.user) {
414
- redirect("/login");
415
- }
416
-
417
- return (
418
- <div className="max-w-2xl">
419
- <div className="mb-6">
420
- <h1 className="text-2xl font-semibold">个人资料</h1>
421
- <p className="text-muted-foreground">管理您的账户信息和偏好设置</p>
422
- </div>
423
- <ProfileForm user={session.user} />
424
- </div>
425
- );
426
- }
427
- ```
428
-
429
- **Step 3: Update settings index export**
430
-
431
- Update `components/settings/index.ts`:
432
-
433
- ```ts
434
- export { SettingsSidebar } from "./settings-sidebar";
435
- export { ProfileForm } from "./profile-form";
436
- ```
437
-
438
- **Step 4: Run build to verify**
439
-
440
- Run: `bun run build 2>&1 | tail -10`
441
- Expected: Build succeeds
442
-
443
- **Step 5: Commit**
444
-
445
- ```bash
446
- git add components/settings/profile-form.tsx components/settings/index.ts app/\(dashboard\)/settings/profile/page.tsx
447
- git commit -m "feat: add profile settings page"
448
- ```
449
-
450
- ---
451
-
452
- ## Task 6: Update Notifications Page to Use New Layout
453
-
454
- **Files:**
455
- - Modify: `app/(dashboard)/settings/notifications/page.tsx`
456
-
457
- **Step 1: Update notifications page**
458
-
459
- Replace `app/(dashboard)/settings/notifications/page.tsx`:
460
-
461
- ```tsx
462
- import { headers } from "next/headers";
463
- import { redirect } from "next/navigation";
464
- import { auth } from "@/lib/auth/config";
465
- import { NotificationPreferences } from "@/components/settings/notification-preferences";
466
-
467
- export const metadata = {
468
- title: "通知设置 - Echo",
469
- };
470
-
471
- export default async function NotificationSettingsPage() {
472
- const session = await auth.api.getSession({ headers: await headers() });
473
-
474
- if (!session?.user) {
475
- redirect("/login");
476
- }
477
-
478
- return (
479
- <div className="max-w-2xl">
480
- <div className="mb-6">
481
- <h1 className="text-2xl font-semibold">通知设置</h1>
482
- <p className="text-muted-foreground">选择您希望接收的通知类型</p>
483
- </div>
484
- <NotificationPreferences />
485
- </div>
486
- );
487
- }
488
- ```
489
-
490
- **Step 2: Run build to verify**
491
-
492
- Run: `bun run build 2>&1 | tail -10`
493
- Expected: Build succeeds
494
-
495
- **Step 3: Commit**
496
-
497
- ```bash
498
- git add app/\(dashboard\)/settings/notifications/page.tsx
499
- git commit -m "refactor: update notifications page to use settings layout"
500
- ```
501
-
502
- ---
503
-
504
- ## Task 7: Create Appearance Settings Page
505
-
506
- **Files:**
507
- - Create: `app/(dashboard)/settings/appearance/page.tsx`
508
- - Create: `components/settings/appearance-form.tsx`
509
-
510
- **Step 1: Create appearance form component**
511
-
512
- Create `components/settings/appearance-form.tsx`:
513
-
514
- ```tsx
515
- "use client";
516
-
517
- import { useState } from "react";
518
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
519
- import { Label } from "@/components/ui/label";
520
- import { cn } from "@/lib/utils";
521
- import { Monitor, Moon, Sun } from "lucide-react";
522
-
523
- type Theme = "light" | "dark" | "system";
524
-
525
- export function AppearanceForm() {
526
- const [theme, setTheme] = useState<Theme>("system");
527
-
528
- const themes: { value: Theme; label: string; icon: React.ElementType }[] = [
529
- { value: "light", label: "浅色", icon: Sun },
530
- { value: "dark", label: "深色", icon: Moon },
531
- { value: "system", label: "跟随系统", icon: Monitor },
532
- ];
533
-
534
- return (
535
- <Card>
536
- <CardHeader>
537
- <CardTitle>外观设置</CardTitle>
538
- <CardDescription>自定义应用的显示风格</CardDescription>
539
- </CardHeader>
540
- <CardContent>
541
- <div className="space-y-4">
542
- <Label>主题</Label>
543
- <div className="grid grid-cols-3 gap-4">
544
- {themes.map((t) => (
545
- <button
546
- key={t.value}
547
- onClick={() => setTheme(t.value)}
548
- className={cn(
549
- "flex flex-col items-center gap-2 rounded-lg border-2 p-4 transition-colors hover:bg-muted",
550
- theme === t.value ? "border-primary" : "border-transparent"
551
- )}
552
- >
553
- <t.icon className="h-6 w-6" />
554
- <span className="text-sm font-medium">{t.label}</span>
555
- </button>
556
- ))}
557
- </div>
558
- <p className="text-xs text-muted-foreground">
559
- 主题切换功能即将推出
560
- </p>
561
- </div>
562
- </CardContent>
563
- </Card>
564
- );
565
- }
566
- ```
567
-
568
- **Step 2: Create appearance page**
569
-
570
- Create `app/(dashboard)/settings/appearance/page.tsx`:
571
-
572
- ```tsx
573
- import { AppearanceForm } from "@/components/settings/appearance-form";
574
-
575
- export const metadata = {
576
- title: "外观设置 - Echo",
577
- };
578
-
579
- export default function AppearanceSettingsPage() {
580
- return (
581
- <div className="max-w-2xl">
582
- <div className="mb-6">
583
- <h1 className="text-2xl font-semibold">外观设置</h1>
584
- <p className="text-muted-foreground">自定义应用的显示风格</p>
585
- </div>
586
- <AppearanceForm />
587
- </div>
588
- );
589
- }
590
- ```
591
-
592
- **Step 3: Update settings index export**
593
-
594
- Update `components/settings/index.ts`:
595
-
596
- ```ts
597
- export { SettingsSidebar } from "./settings-sidebar";
598
- export { ProfileForm } from "./profile-form";
599
- export { AppearanceForm } from "./appearance-form";
600
- ```
601
-
602
- **Step 4: Run build to verify**
603
-
604
- Run: `bun run build 2>&1 | tail -10`
605
- Expected: Build succeeds
606
-
607
- **Step 5: Commit**
608
-
609
- ```bash
610
- git add components/settings/appearance-form.tsx components/settings/index.ts app/\(dashboard\)/settings/appearance/page.tsx
611
- git commit -m "feat: add appearance settings page"
612
- ```
613
-
614
- ---
615
-
616
- ## Task 8: Create Organization Settings Page
617
-
618
- **Files:**
619
- - Create: `app/(dashboard)/settings/organization/page.tsx`
620
-
621
- **Step 1: Create organization settings page**
622
-
623
- Create `app/(dashboard)/settings/organization/page.tsx`:
624
-
625
- ```tsx
626
- import { headers } from "next/headers";
627
- import { redirect } from "next/navigation";
628
- import { auth } from "@/lib/auth/config";
629
- import { db } from "@/lib/db";
630
- import { getUserOrganization } from "@/lib/auth/organization";
631
- import { OrganizationForm } from "@/components/settings/organization-form";
632
- import { OrganizationMembersList } from "@/components/settings/organization-members-list";
633
- import { InviteMemberForm } from "@/components/settings/invite-member-form";
634
- import type { UserRole } from "@/lib/auth/permissions";
635
-
636
- export const metadata = {
637
- title: "组织管理 - Echo",
638
- };
639
-
640
- export default async function OrganizationSettingsPage() {
641
- const session = await auth.api.getSession({ headers: await headers() });
642
-
643
- if (!session?.user) {
644
- redirect("/login");
645
- }
646
-
647
- const userRole = (session.user as { role?: string }).role as UserRole || "customer";
648
-
649
- if (userRole !== "admin") {
650
- redirect("/settings/profile");
651
- }
652
-
653
- let organization = null;
654
- if (db) {
655
- organization = await getUserOrganization(db, session.user.id);
656
- }
657
-
658
- if (!organization) {
659
- redirect("/settings/organizations/new");
660
- }
661
-
662
- return (
663
- <div className="max-w-4xl space-y-6">
664
- <div>
665
- <h1 className="text-2xl font-semibold">组织管理</h1>
666
- <p className="text-muted-foreground">管理您的组织信息和成员</p>
667
- </div>
668
-
669
- <OrganizationForm
670
- organizationId={organization.id}
671
- initialName={organization.name}
672
- initialSlug={organization.slug}
673
- />
674
-
675
- <InviteMemberForm organizationId={organization.id} />
676
-
677
- <OrganizationMembersList organizationId={organization.id} />
678
- </div>
679
- );
680
- }
681
- ```
682
-
683
- **Step 2: Run build to verify**
684
-
685
- Run: `bun run build 2>&1 | tail -10`
686
- Expected: Build succeeds
687
-
688
- **Step 3: Commit**
689
-
690
- ```bash
691
- git add app/\(dashboard\)/settings/organization/page.tsx
692
- git commit -m "feat: add organization settings page"
693
- ```
694
-
695
- ---
696
-
697
- ## Task 9: Create API Keys Settings Page
698
-
699
- **Files:**
700
- - Create: `app/(dashboard)/settings/api-keys/page.tsx`
701
- - Create: `components/settings/api-keys-list.tsx`
702
-
703
- **Step 1: Create API keys list component**
704
-
705
- Create `components/settings/api-keys-list.tsx`:
706
-
707
- ```tsx
708
- "use client";
709
-
710
- import { useState, useEffect } from "react";
711
- import { Button } from "@/components/ui/button";
712
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
713
- import { Input } from "@/components/ui/input";
714
- import { Label } from "@/components/ui/label";
715
- import { Key, Copy, Trash2, Plus } from "lucide-react";
716
-
717
- interface ApiKey {
718
- keyId: string;
719
- name: string;
720
- prefix: string;
721
- displayKey: string;
722
- createdAt: string;
723
- lastUsed: string | null;
724
- }
725
-
726
- export function ApiKeysList() {
727
- const [keys, setKeys] = useState<ApiKey[]>([]);
728
- const [isLoading, setIsLoading] = useState(true);
729
- const [newKeyName, setNewKeyName] = useState("");
730
- const [isCreating, setIsCreating] = useState(false);
731
-
732
- useEffect(() => {
733
- fetchKeys();
734
- }, []);
735
-
736
- const fetchKeys = async () => {
737
- try {
738
- const res = await fetch("/api/api-keys");
739
- if (res.ok) {
740
- const data = await res.json();
741
- setKeys(data.data || []);
742
- }
743
- } catch (error) {
744
- console.error("Failed to fetch API keys:", error);
745
- } finally {
746
- setIsLoading(false);
747
- }
748
- };
749
-
750
- const handleCreate = async (e: React.FormEvent) => {
751
- e.preventDefault();
752
- if (!newKeyName.trim()) return;
753
-
754
- setIsCreating(true);
755
- try {
756
- const res = await fetch("/api/api-keys", {
757
- method: "POST",
758
- headers: { "Content-Type": "application/json" },
759
- body: JSON.stringify({ name: newKeyName }),
760
- });
761
-
762
- if (res.ok) {
763
- const data = await res.json();
764
- alert(`新密钥已创建: ${data.data.key}\n\n请立即保存此密钥,它将不会再次显示。`);
765
- setNewKeyName("");
766
- fetchKeys();
767
- }
768
- } catch (error) {
769
- console.error("Failed to create API key:", error);
770
- } finally {
771
- setIsCreating(false);
772
- }
773
- };
774
-
775
- const handleDelete = async (keyId: string) => {
776
- if (!confirm("确定要删除此 API 密钥吗?此操作不可撤销。")) return;
777
-
778
- try {
779
- const res = await fetch(`/api/api-keys/${keyId}`, { method: "DELETE" });
780
- if (res.ok) {
781
- fetchKeys();
782
- }
783
- } catch (error) {
784
- console.error("Failed to delete API key:", error);
785
- }
786
- };
787
-
788
- if (isLoading) {
789
- return <div className="text-muted-foreground">加载中...</div>;
790
- }
791
-
792
- return (
793
- <div className="space-y-6">
794
- <Card>
795
- <CardHeader>
796
- <CardTitle>创建新密钥</CardTitle>
797
- <CardDescription>创建新的 API 密钥用于外部集成</CardDescription>
798
- </CardHeader>
799
- <CardContent>
800
- <form onSubmit={handleCreate} className="flex gap-4">
801
- <div className="flex-1">
802
- <Label htmlFor="keyName" className="sr-only">密钥名称</Label>
803
- <Input
804
- id="keyName"
805
- value={newKeyName}
806
- onChange={(e) => setNewKeyName(e.target.value)}
807
- placeholder="输入密钥名称"
808
- />
809
- </div>
810
- <Button type="submit" disabled={isCreating || !newKeyName.trim()}>
811
- <Plus className="mr-2 h-4 w-4" />
812
- 创建密钥
813
- </Button>
814
- </form>
815
- </CardContent>
816
- </Card>
817
-
818
- <Card>
819
- <CardHeader>
820
- <CardTitle>已有密钥</CardTitle>
821
- <CardDescription>管理您的 API 密钥</CardDescription>
822
- </CardHeader>
823
- <CardContent>
824
- {keys.length === 0 ? (
825
- <p className="text-muted-foreground py-4">暂无 API 密钥</p>
826
- ) : (
827
- <div className="space-y-4">
828
- {keys.map((key) => (
829
- <div key={key.keyId} className="flex items-center justify-between p-4 border rounded-lg">
830
- <div className="flex items-center gap-3">
831
- <Key className="h-5 w-5 text-muted-foreground" />
832
- <div>
833
- <p className="font-medium">{key.name}</p>
834
- <p className="text-sm text-muted-foreground font-mono">{key.displayKey}</p>
835
- </div>
836
- </div>
837
- <div className="flex items-center gap-2">
838
- <Button variant="ghost" size="icon" onClick={() => navigator.clipboard.writeText(key.prefix)}>
839
- <Copy className="h-4 w-4" />
840
- </Button>
841
- <Button variant="ghost" size="icon" onClick={() => handleDelete(key.keyId)}>
842
- <Trash2 className="h-4 w-4 text-destructive" />
843
- </Button>
844
- </div>
845
- </div>
846
- ))}
847
- </div>
848
- )}
849
- </CardContent>
850
- </Card>
851
- </div>
852
- );
853
- }
854
- ```
855
-
856
- **Step 2: Create API keys page**
857
-
858
- Create `app/(dashboard)/settings/api-keys/page.tsx`:
859
-
860
- ```tsx
861
- import { headers } from "next/headers";
862
- import { redirect } from "next/navigation";
863
- import { auth } from "@/lib/auth/config";
864
- import { ApiKeysList } from "@/components/settings/api-keys-list";
865
- import type { UserRole } from "@/lib/auth/permissions";
866
-
867
- export const metadata = {
868
- title: "API 密钥 - Echo",
869
- };
870
-
871
- export default async function ApiKeysSettingsPage() {
872
- const session = await auth.api.getSession({ headers: await headers() });
873
-
874
- if (!session?.user) {
875
- redirect("/login");
876
- }
877
-
878
- const userRole = (session.user as { role?: string }).role as UserRole || "customer";
879
-
880
- if (userRole !== "admin" && userRole !== "product_manager") {
881
- redirect("/settings/profile");
882
- }
883
-
884
- return (
885
- <div className="max-w-2xl">
886
- <div className="mb-6">
887
- <h1 className="text-2xl font-semibold">API 密钥</h1>
888
- <p className="text-muted-foreground">管理您的 API 访问密钥</p>
889
- </div>
890
- <ApiKeysList />
891
- </div>
892
- );
893
- }
894
- ```
895
-
896
- **Step 3: Update settings index export**
897
-
898
- Update `components/settings/index.ts`:
899
-
900
- ```ts
901
- export { SettingsSidebar } from "./settings-sidebar";
902
- export { ProfileForm } from "./profile-form";
903
- export { AppearanceForm } from "./appearance-form";
904
- export { ApiKeysList } from "./api-keys-list";
905
- ```
906
-
907
- **Step 4: Run build to verify**
908
-
909
- Run: `bun run build 2>&1 | tail -10`
910
- Expected: Build succeeds
911
-
912
- **Step 5: Commit**
913
-
914
- ```bash
915
- git add components/settings/api-keys-list.tsx components/settings/index.ts app/\(dashboard\)/settings/api-keys/page.tsx
916
- git commit -m "feat: add API keys settings page"
917
- ```
918
-
919
- ---
920
-
921
- ## Task 10: Final Verification and Cleanup
922
-
923
- **Step 1: Run full build**
924
-
925
- Run: `bun run build`
926
- Expected: Build succeeds with no errors
927
-
928
- **Step 2: Run linting**
929
-
930
- Run: `bun run lint`
931
- Expected: No errors (warnings acceptable)
932
-
933
- **Step 3: Test manually**
934
-
935
- 1. Start dev server: `bun dev`
936
- 2. Login as admin user
937
- 3. Verify user dropdown appears in sidebar
938
- 4. Click "设置" → redirects to /settings/profile
939
- 5. Verify left sidebar shows all menu items
940
- 6. Navigate through each settings page
941
- 7. Verify non-admin users cannot access organization/api-keys pages
942
-
943
- **Step 4: Final commit if any fixes needed**
944
-
945
- ```bash
946
- git add -A
947
- git commit -m "chore: cleanup and final adjustments"
948
- ```