@allbluecn/web-app 0.5.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 (170) hide show
  1. package/package.json +239 -30
  2. package/src/components/iteration/auto-schedule-dialog.tsx +150 -0
  3. package/src/components/iteration/burndown-chart.tsx +63 -0
  4. package/src/components/iteration/constants.ts +28 -0
  5. package/src/components/iteration/create-iteration-dialog.tsx +140 -0
  6. package/src/components/iteration/iteration-card.tsx +60 -0
  7. package/src/components/iteration/iteration-editor.tsx +116 -0
  8. package/src/components/iteration/iteration-progress-bar.tsx +44 -0
  9. package/src/components/iteration/iteration-status-badge.tsx +33 -0
  10. package/src/components/knowledge/dialog/knowledge-form-dialog.tsx +21 -30
  11. package/src/components/knowledge/knowledge-toolbar.tsx +124 -1
  12. package/src/components/layout/sidebar-03/__tests__/nav-workspace.test.tsx +2 -2
  13. package/src/components/layout/sidebar-03/nav-workspace.tsx +2 -2
  14. package/src/components/project/create-project-dialog.tsx +44 -57
  15. package/src/components/shared/__tests__/lucide-curated-icons.test.ts +83 -0
  16. package/src/components/shared/__tests__/lucide-icon-data.test.ts +131 -0
  17. package/src/components/shared/__tests__/lucide-icon-picker.test.tsx +197 -0
  18. package/src/components/shared/lucide-icon-picker/curated-icons.ts +478 -0
  19. package/src/components/shared/lucide-icon-picker/icon-data.ts +109 -0
  20. package/src/components/shared/lucide-icon-picker/index.tsx +82 -0
  21. package/src/components/shared/lucide-icon-picker/lucide-icon-by-name.tsx +78 -0
  22. package/src/components/shared/lucide-icon-picker/panel.tsx +163 -0
  23. package/src/components/story/__tests__/constants.test.ts +255 -0
  24. package/src/components/story/ai/story-ai-panel.tsx +23 -15
  25. package/src/components/story/ai/story-ai-preview-list.tsx +19 -0
  26. package/src/components/story/constants.ts +43 -1
  27. package/src/components/story/create-story-dialog.tsx +42 -16
  28. package/src/components/story/detail/__tests__/associated-attachments-panel.test.tsx +114 -0
  29. package/src/components/story/detail/__tests__/categories.test.tsx +62 -0
  30. package/src/components/story/detail/__tests__/dependencies-button.test.tsx +49 -0
  31. package/src/components/story/detail/activity-timeline.tsx +26 -4
  32. package/src/components/story/detail/story-detail-body.tsx +90 -38
  33. package/src/components/story/detail/story-detail-toolbar/associated-attachments-panel.tsx +857 -0
  34. package/src/components/story/detail/story-detail-toolbar/categories.tsx +23 -18
  35. package/src/components/story/detail/story-detail-toolbar/children.tsx +9 -4
  36. package/src/components/story/detail/story-detail-toolbar/dependency-panel.tsx +95 -27
  37. package/src/components/story/detail/story-detail-toolbar.tsx +159 -8
  38. package/src/components/story/detail/story-properties.tsx +21 -4
  39. package/src/components/story/display/__tests__/group-fields.test.ts +107 -0
  40. package/src/components/story/display/group-resolver.tsx +2 -2
  41. package/src/components/story/inline-editors/__tests__/dependency-type-select.test.tsx +155 -0
  42. package/src/components/story/inline-editors/__tests__/labels-editor.test.tsx +155 -0
  43. package/src/components/story/inline-editors/__tests__/priority-editor.test.tsx +173 -0
  44. package/src/components/story/inline-editors/__tests__/state-editor.test.tsx +174 -0
  45. package/src/components/story/inline-editors/dependency-type-select.tsx +180 -0
  46. package/src/components/story/inline-editors/index.ts +2 -1
  47. package/src/components/story/inline-editors/labels-editor.tsx +159 -3
  48. package/src/components/story/inline-editors/story-select-dialog.tsx +41 -63
  49. package/src/components/story/peek-panel.tsx +121 -21
  50. package/src/components/story/relations/__tests__/attachment-manage-dialog.test.tsx +171 -0
  51. package/src/components/story/relations/__tests__/knowledge-select-dialog.test.tsx +174 -0
  52. package/src/components/story/relations/__tests__/link-manage-dialog.test.tsx +93 -0
  53. package/src/components/story/relations/attachment-manage-dialog.tsx +490 -0
  54. package/src/components/story/relations/file-preview.tsx +173 -0
  55. package/src/components/story/relations/format.ts +120 -0
  56. package/src/components/story/relations/general-groups.ts +32 -0
  57. package/src/components/story/relations/knowledge-select-dialog.tsx +403 -0
  58. package/src/components/story/relations/link-manage-dialog.tsx +192 -0
  59. package/src/components/story/relations/story-ref-row.tsx +17 -27
  60. package/src/components/story/story-panel-header.tsx +25 -19
  61. package/src/components/story/story-toolbar.tsx +3 -3
  62. package/src/components/story/transfer-story-popover.tsx +2 -2
  63. package/src/components/story/types.ts +14 -0
  64. package/src/components/story/views/create-view-dialog.tsx +3 -3
  65. package/src/components/story/views/story-list/__tests__/group-header.test.tsx +219 -0
  66. package/src/components/story/views/story-list/group.tsx +1 -1
  67. package/src/components/story/views/story-list/inline-story-creator.tsx +2 -2
  68. package/src/components/story/views/story-list/row.tsx +1 -1
  69. package/src/components/workspace/create-workspace-dialog.tsx +41 -49
  70. package/src/lib/__tests__/project-display-id.test.ts +8 -5
  71. package/src/lib/changelog/sdk-versions.ts +20 -20
  72. package/src/lib/iteration/__tests__/status.test.ts +41 -0
  73. package/src/lib/iteration/status.ts +28 -0
  74. package/src/lib/project-display-id.ts +14 -5
  75. package/src/lib/project-module-meta.ts +77 -0
  76. package/src/lib/story/__tests__/video-embed.test.ts +77 -0
  77. package/src/lib/story/video-embed.ts +59 -0
  78. package/src/lib/utils.d.ts +1 -1
  79. package/src/plugins/.gen-manifest.json +13 -0
  80. package/src/plugins/modules.gen.ts +20 -0
  81. package/src/plugins/plugins.gen.ts +20 -0
  82. package/src/plugins/ui.gen.ts +8 -0
  83. package/src/providers/index.tsx +6 -0
  84. package/src/routeTree.gen.ts +96 -0
  85. package/src/routes/__root.tsx +0 -8
  86. package/src/routes/_nav/inspiration/route.tsx +8 -0
  87. package/src/routes/_nav/knowledge/index.lazy.tsx +2 -2
  88. package/src/routes/_nav/prd/$id/route.tsx +9 -0
  89. package/src/routes/_nav/prd/index.tsx +9 -0
  90. package/src/routes/_nav/prd/route.tsx +8 -0
  91. package/src/routes/_nav/projects/$projectId/$storyId/route.lazy.tsx +52 -25
  92. package/src/routes/_nav/projects/$projectId/$storyId/route.tsx +6 -3
  93. package/src/routes/_nav/projects/$projectId/index.lazy.tsx +32 -15
  94. package/src/routes/_nav/projects/$projectId/iterations/$iterationId/route.lazy.tsx +289 -0
  95. package/src/routes/_nav/projects/$projectId/iterations/$iterationId/route.tsx +27 -0
  96. package/src/routes/_nav/projects/$projectId/iterations/index.lazy.tsx +111 -0
  97. package/src/routes/_nav/projects/$projectId/iterations/index.tsx +24 -0
  98. package/src/routes/_nav/projects/$projectId/route.tsx +4 -2
  99. package/src/routes/_nav/projects/$projectId/settings/route.lazy.tsx +78 -7
  100. package/src/routes/_nav/projects/$projectId/settings/route.tsx +6 -2
  101. package/src/routes/_nav/stories/$storyId/route.lazy.tsx +5 -1
  102. package/src/routes/_nav/stories/$storyId/route.tsx +4 -0
  103. package/src/routes/_nav/tags/$id/route.tsx +8 -0
  104. package/src/routes/_nav/tags/index.tsx +8 -0
  105. package/src/routes/_nav/tags/route.tsx +8 -0
  106. package/src/routes/_nav/tags/settings/route.tsx +9 -0
  107. package/src/routes/_nav/workspaces/$workspaceId/route.lazy.tsx +3 -3
  108. package/src/routes/_nav/workspaces/$workspaceId/settings/route.lazy.tsx +4 -4
  109. package/src/routes/_nav/workspaces/index.lazy.tsx +3 -3
  110. package/src/routes/api/attachments/$id/content.ts +60 -0
  111. package/src/routes/api/attachments/upload.ts +148 -0
  112. package/src/routes/share/prd/$prdId/route.tsx +10 -0
  113. package/src/server/serverFns/__tests__/project-kb.test.ts +147 -0
  114. package/src/server/serverFns/iteration/__tests__/auto-schedule-core.test.ts +62 -0
  115. package/src/server/serverFns/iteration/__tests__/burndown-core.test.ts +90 -0
  116. package/src/server/serverFns/iteration/__tests__/iteration-crud.test.ts +211 -0
  117. package/src/server/serverFns/iteration/__tests__/iteration-stories.test.ts +155 -0
  118. package/src/server/serverFns/iteration/__tests__/iteration-transfer.test.ts +136 -0
  119. package/src/server/serverFns/iteration/auto-schedule-core.ts +54 -0
  120. package/src/server/serverFns/iteration/auto-schedule-internal.ts +88 -0
  121. package/src/server/serverFns/iteration/burndown-core.ts +60 -0
  122. package/src/server/serverFns/iteration/index.ts +22 -0
  123. package/src/server/serverFns/iteration/iteration-auto-schedule.ts +66 -0
  124. package/src/server/serverFns/iteration/iteration-burndown.ts +70 -0
  125. package/src/server/serverFns/iteration/iteration-crud.ts +208 -0
  126. package/src/server/serverFns/iteration/iteration-stories.ts +111 -0
  127. package/src/server/serverFns/iteration/iteration-transfer.ts +42 -0
  128. package/src/server/serverFns/iteration/schemas.ts +58 -0
  129. package/src/server/serverFns/iteration/transfer-internal.ts +62 -0
  130. package/src/server/serverFns/knowledge.ts +50 -4
  131. package/src/server/serverFns/project/module-overview.ts +109 -0
  132. package/src/server/serverFns/project.ts +45 -2
  133. package/src/server/serverFns/story/__tests__/kb-scope.test.ts +56 -0
  134. package/src/server/serverFns/story/__tests__/label-suggest-fn.test.ts +100 -0
  135. package/src/server/serverFns/story/__tests__/labels.test.ts +67 -0
  136. package/src/server/serverFns/story/__tests__/story-assoc.test.ts +222 -0
  137. package/src/server/serverFns/story/__tests__/story-dependencies.test.ts +132 -2
  138. package/src/server/serverFns/story/index.ts +9 -4
  139. package/src/server/serverFns/story/kb-general.ts +39 -0
  140. package/src/server/serverFns/story/kb-scope.ts +52 -0
  141. package/src/server/serverFns/story/link-preview.ts +115 -0
  142. package/src/server/serverFns/story/story-assoc.ts +372 -51
  143. package/src/server/serverFns/story/story-attachments.ts +32 -45
  144. package/src/{components/project/project-icon.tsx → server/serverFns/story/story-crud/label-palette.ts} +16 -19
  145. package/src/server/serverFns/story/story-crud/label-suggest-core.ts +88 -0
  146. package/src/server/serverFns/story/story-crud/label-suggest.ts +38 -0
  147. package/src/{components/knowledge/knowledge-icon.tsx → server/serverFns/story/story-crud/label-upsert.ts} +13 -19
  148. package/src/server/serverFns/story/story-crud/labels.ts +3 -5
  149. package/src/server/serverFns/story/story-crud.ts +53 -6
  150. package/src/server/serverFns/story/story-dependencies.ts +158 -5
  151. package/src/server/serverFns/story/story-links.ts +43 -7
  152. package/src/server/serverFns/story/story-transfer.ts +5 -4
  153. package/src/server/serverFns/story/story-tree.ts +28 -1
  154. package/src/server/serverFns/story/types.ts +38 -0
  155. package/src/styles/globals.css +25 -0
  156. package/src/types/project.ts +5 -5
  157. package/vite.shared.ts +2 -0
  158. package/src/components/project/project-animated-icon.tsx +0 -73
  159. package/src/components/shared/icon-packs/index.ts +0 -1
  160. package/src/components/shared/icon-packs/knowledge.ts +0 -1
  161. package/src/components/shared/icon-packs/prd.ts +0 -1
  162. package/src/components/shared/icon-packs/project.ts +0 -1
  163. package/src/components/shared/icon-picker.tsx +0 -1
  164. package/src/components/story/detail/story-detail-toolbar/attachments.tsx +0 -73
  165. package/src/components/story/detail/story-detail-toolbar/knowledge.tsx +0 -73
  166. package/src/components/story/detail/story-detail-toolbar/links.tsx +0 -73
  167. package/src/components/story/relations/attachments-panel.tsx +0 -179
  168. package/src/components/story/relations/knowledge-panel.tsx +0 -284
  169. package/src/components/story/relations/links-panel.tsx +0 -240
  170. package/src/lib/project-mock.ts +0 -156
@@ -0,0 +1,197 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+ //
5
+ // This program is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU Affero General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU Affero General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU Affero General Public License
16
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ import { beforeEach, describe, expect, it, vi } from "vitest";
19
+ import { render, screen, waitFor } from "@testing-library/react";
20
+ import userEvent from "@testing-library/user-event";
21
+ import { Star } from "lucide-react";
22
+ import { LucideIconByName } from "@/components/shared/lucide-icon-picker/lucide-icon-by-name";
23
+ import { IconPickerPanel } from "@/components/shared/lucide-icon-picker/panel";
24
+ import { LucideIconPicker } from "@/components/shared/lucide-icon-picker";
25
+ import { CURATED_ICONS } from "@/components/shared/lucide-icon-picker/curated-icons";
26
+ import { pushRecentIcon, getRecentIcons } from "@/components/shared/lucide-icon-picker/icon-data";
27
+
28
+ describe("LucideIconByName", () => {
29
+ it("有效 name 渲染对应图标(jsdom 下 data-icon 属性不可靠,以 width 断言)", () => {
30
+ const { container } = render(<LucideIconByName name="star" size={20} />);
31
+ const svg = container.querySelector("svg");
32
+ expect(svg).toBeTruthy();
33
+ expect(svg?.getAttribute("width")).toBe("20");
34
+ expect(svg?.innerHTML).toContain("<path");
35
+ });
36
+
37
+ it("精选外/未知 name 渲染 fallback(默认 Circle 占位)", () => {
38
+ const { container } = render(<LucideIconByName name="not-exist-icon" />);
39
+ const svg = container.querySelector("svg");
40
+ expect(svg).toBeTruthy();
41
+ expect(svg?.innerHTML).toContain("<circle");
42
+ });
43
+
44
+ it("undefined name 渲染 fallback", () => {
45
+ const { container } = render(<LucideIconByName />);
46
+ expect(container.querySelector("svg")).toBeTruthy();
47
+ });
48
+
49
+ it("自定义 fallback 优先于默认占位", () => {
50
+ render(
51
+ <LucideIconByName name="not-exist" fallback={<Star data-testid="fb" />} />
52
+ );
53
+ expect(screen.getByTestId("fb")).toBeTruthy();
54
+ });
55
+
56
+ it("className 透传", () => {
57
+ const { container } = render(
58
+ <LucideIconByName name="star" className="text-primary" />
59
+ );
60
+ expect(container.querySelector("svg")?.getAttribute("class")).toContain(
61
+ "text-primary"
62
+ );
63
+ });
64
+ });
65
+
66
+ describe("IconPickerPanel", () => {
67
+ beforeEach(() => {
68
+ window.localStorage.clear();
69
+ });
70
+
71
+ it("渲染搜索框 + 全部图标网格;无最近记录时不显示最近使用小节", async () => {
72
+ render(<IconPickerPanel onPick={vi.fn()} />);
73
+ expect(
74
+ screen.getByRole("textbox", { name: /搜索/ })
75
+ ).toBeTruthy();
76
+ // tab 按钮已移除
77
+ expect(screen.queryByRole("button", { name: /最近使用/ })).toBeNull();
78
+ expect(screen.queryByRole("button", { name: /全部图标/ })).toBeNull();
79
+ // 无最近记录:不显示"最近使用"小节标题,网格 = 精选总数
80
+ expect(screen.queryByText(/最近使用/)).toBeNull();
81
+ expect(screen.getByText(/全部图标/)).toBeTruthy();
82
+ const cells = await screen.findAllByRole("button", { name: /^icon-cell-/ });
83
+ expect(cells.length).toBe(CURATED_ICONS.length);
84
+ });
85
+
86
+ it("英文搜索过滤 + Enter 选中第一个结果", async () => {
87
+ const onPick = vi.fn();
88
+ render(<IconPickerPanel onPick={onPick} />);
89
+ const input = screen.getByRole("textbox", { name: /搜索/ });
90
+ await userEvent.type(input, "kanban{Enter}");
91
+ expect(onPick).toHaveBeenCalledWith("folder-kanban");
92
+ });
93
+
94
+ it("中文搜索过滤并点击选中", async () => {
95
+ const onPick = vi.fn();
96
+ render(<IconPickerPanel onPick={onPick} />);
97
+ await userEvent.type(
98
+ screen.getByRole("textbox", { name: /搜索/ }),
99
+ "星标"
100
+ );
101
+ const cells = screen.getAllByRole("button", { name: /^icon-cell-/ });
102
+ expect(cells.length).toBeGreaterThanOrEqual(1);
103
+ await userEvent.click(cells[0]);
104
+ expect(onPick).toHaveBeenCalledWith("star");
105
+ });
106
+
107
+ it("无结果显示空态", async () => {
108
+ render(<IconPickerPanel onPick={vi.fn()} />);
109
+ await userEvent.type(
110
+ screen.getByRole("textbox", { name: /搜索/ }),
111
+ "zzzz不存在"
112
+ );
113
+ expect(screen.getByText(/未找到匹配的图标/)).toBeTruthy();
114
+ expect(screen.queryAllByRole("button", { name: /^icon-cell-/ })).toEqual([]);
115
+ });
116
+
117
+ it("有最近记录时同页显示最近使用小节(上下排列,总数 = 精选数 + 最近数)", async () => {
118
+ pushRecentIcon("star");
119
+ pushRecentIcon("clock");
120
+ render(<IconPickerPanel onPick={vi.fn()} />);
121
+ expect(screen.getByText(/最近使用/)).toBeTruthy();
122
+ expect(screen.getByText(/全部图标/)).toBeTruthy();
123
+ const cells = await screen.findAllByRole("button", { name: /^icon-cell-/ });
124
+ expect(cells.length).toBe(CURATED_ICONS.length + 2);
125
+ });
126
+
127
+ it("value 对应 cell 有选中态标识", async () => {
128
+ render(<IconPickerPanel value="star" onPick={vi.fn()} />);
129
+ const cell = screen.getByRole("button", { name: "icon-cell-star" });
130
+ expect(cell.getAttribute("data-selected")).toBe("true");
131
+ });
132
+ });
133
+
134
+ describe("LucideIconPicker(Popover 包装)", () => {
135
+ beforeEach(() => {
136
+ window.localStorage.clear();
137
+ vi.stubGlobal(
138
+ "ResizeObserver",
139
+ class {
140
+ observe() {}
141
+ unobserve() {}
142
+ disconnect() {}
143
+ }
144
+ );
145
+ });
146
+ afterEach(() => {
147
+ vi.unstubAllGlobals();
148
+ });
149
+
150
+ it("不传 children 时渲染内置微笑触发按钮(未选择占位)", async () => {
151
+ render(<LucideIconPicker value={undefined} onChange={vi.fn()} />);
152
+ const trigger = screen.getByRole("button", { name: /选择图标/ });
153
+ expect(trigger.querySelector("svg")?.getAttribute("class")).toContain(
154
+ "lucide-smile"
155
+ );
156
+ });
157
+
158
+ it("内置触发按钮在选择后回显所选图标", async () => {
159
+ render(<LucideIconPicker value="clock" onChange={vi.fn()} />);
160
+ const trigger = screen.getByRole("button", { name: /选择图标/ });
161
+ expect(trigger.querySelector("svg")?.getAttribute("class")).toContain(
162
+ "lucide-clock"
163
+ );
164
+ });
165
+
166
+ it("渲染 trigger,点击后打开面板(lazy 加载完成)", async () => {
167
+ render(
168
+ <LucideIconPicker value="star" onChange={vi.fn()}>
169
+ <button type="button">选图标</button>
170
+ </LucideIconPicker>
171
+ );
172
+ await userEvent.click(screen.getByRole("button", { name: "选图标" }));
173
+ expect(
174
+ await screen.findByRole("button", { name: "icon-cell-star" })
175
+ ).toBeTruthy();
176
+ });
177
+
178
+ it("点击图标触发 onChange 并关闭弹窗、写入最近使用", async () => {
179
+ const onChange = vi.fn();
180
+ render(
181
+ <LucideIconPicker value={undefined} onChange={onChange}>
182
+ <button type="button">选图标</button>
183
+ </LucideIconPicker>
184
+ );
185
+ await userEvent.click(screen.getByRole("button", { name: "选图标" }));
186
+ await userEvent.click(
187
+ await screen.findByRole("button", { name: "icon-cell-clock" })
188
+ );
189
+ expect(onChange).toHaveBeenCalledWith("clock");
190
+ await waitFor(() =>
191
+ expect(
192
+ screen.queryByRole("button", { name: "icon-cell-clock" })
193
+ ).toBeNull()
194
+ );
195
+ expect(getRecentIcons()).toEqual(["clock"]);
196
+ });
197
+ });