@shepai/cli 1.86.1 → 1.87.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 (137) hide show
  1. package/dist/src/presentation/web/app/actions/get-feature-plan.d.ts +10 -0
  2. package/dist/src/presentation/web/app/actions/get-feature-plan.d.ts.map +1 -1
  3. package/dist/src/presentation/web/app/actions/get-feature-plan.js +8 -0
  4. package/dist/src/presentation/web/components/common/feature-drawer-tabs/plan-tab.d.ts.map +1 -1
  5. package/dist/src/presentation/web/components/common/feature-drawer-tabs/plan-tab.js +3 -20
  6. package/dist/src/presentation/web/components/common/feature-drawer-tabs/plan-tab.stories.d.ts.map +1 -1
  7. package/dist/src/presentation/web/components/common/feature-drawer-tabs/plan-tab.stories.js +74 -0
  8. package/dist/src/presentation/web/components/common/task-progress-view/index.d.ts +3 -0
  9. package/dist/src/presentation/web/components/common/task-progress-view/index.d.ts.map +1 -0
  10. package/dist/src/presentation/web/components/common/task-progress-view/index.js +1 -0
  11. package/dist/src/presentation/web/components/common/task-progress-view/task-progress-view.d.ts +6 -0
  12. package/dist/src/presentation/web/components/common/task-progress-view/task-progress-view.d.ts.map +1 -0
  13. package/dist/src/presentation/web/components/common/task-progress-view/task-progress-view.js +84 -0
  14. package/dist/src/presentation/web/components/common/task-progress-view/task-progress-view.stories.d.ts +16 -0
  15. package/dist/src/presentation/web/components/common/task-progress-view/task-progress-view.stories.d.ts.map +1 -0
  16. package/dist/src/presentation/web/components/common/task-progress-view/task-progress-view.stories.js +194 -0
  17. package/dist/tsconfig.build.tsbuildinfo +1 -1
  18. package/package.json +1 -1
  19. package/web/.next/BUILD_ID +1 -1
  20. package/web/.next/build-manifest.json +2 -2
  21. package/web/.next/fallback-build-manifest.json +2 -2
  22. package/web/.next/prerender-manifest.json +3 -3
  23. package/web/.next/required-server-files.js +1 -1
  24. package/web/.next/required-server-files.json +1 -1
  25. package/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +18 -18
  26. package/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
  27. package/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
  28. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +21 -21
  29. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
  30. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
  31. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +14 -14
  32. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
  33. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  34. package/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +18 -18
  35. package/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
  36. package/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
  37. package/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +21 -21
  38. package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
  39. package/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
  40. package/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +14 -14
  41. package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
  42. package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  43. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +14 -14
  44. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
  45. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  46. package/web/.next/server/app/_global-error.html +2 -2
  47. package/web/.next/server/app/_global-error.rsc +1 -1
  48. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  49. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  50. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  51. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  52. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  53. package/web/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  54. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  55. package/web/.next/server/app/api/attachments/preview/route.js.nft.json +1 -1
  56. package/web/.next/server/app/settings/page/server-reference-manifest.json +5 -5
  57. package/web/.next/server/app/settings/page.js.nft.json +1 -1
  58. package/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  59. package/web/.next/server/app/skills/page/server-reference-manifest.json +6 -6
  60. package/web/.next/server/app/skills/page.js.nft.json +1 -1
  61. package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  62. package/web/.next/server/app/tools/page/server-reference-manifest.json +6 -6
  63. package/web/.next/server/app/tools/page.js.nft.json +1 -1
  64. package/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
  65. package/web/.next/server/app/version/page/server-reference-manifest.json +1 -1
  66. package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  67. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js +1 -1
  68. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js.map +1 -1
  69. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_da7eef33.js +1 -1
  70. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_da7eef33.js.map +1 -1
  71. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_0a9251aa.js +1 -1
  72. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_0a9251aa.js.map +1 -1
  73. package/web/.next/server/chunks/ssr/[root-of-the-server]__11033878._.js +1 -1
  74. package/web/.next/server/chunks/ssr/[root-of-the-server]__11033878._.js.map +1 -1
  75. package/web/.next/server/chunks/ssr/[root-of-the-server]__17143953._.js +1 -1
  76. package/web/.next/server/chunks/ssr/[root-of-the-server]__17143953._.js.map +1 -1
  77. package/web/.next/server/chunks/ssr/[root-of-the-server]__357d99f9._.js +1 -1
  78. package/web/.next/server/chunks/ssr/[root-of-the-server]__4be9a9bb._.js +1 -1
  79. package/web/.next/server/chunks/ssr/[root-of-the-server]__5d87af2e._.js +1 -1
  80. package/web/.next/server/chunks/ssr/[root-of-the-server]__9de266fa._.js +1 -1
  81. package/web/.next/server/chunks/ssr/[root-of-the-server]__9de266fa._.js.map +1 -1
  82. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js +1 -1
  83. package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js.map +1 -1
  84. package/web/.next/server/chunks/ssr/[root-of-the-server]__b3a4cef4._.js +1 -1
  85. package/web/.next/server/chunks/ssr/[root-of-the-server]__b3a4cef4._.js.map +1 -1
  86. package/web/.next/server/chunks/ssr/[root-of-the-server]__f0d8d054._.js +2 -2
  87. package/web/.next/server/chunks/ssr/[root-of-the-server]__fdc75809._.js +1 -1
  88. package/web/.next/server/chunks/ssr/[root-of-the-server]__fdc75809._.js.map +1 -1
  89. package/web/.next/server/chunks/ssr/_135edb9c._.js +1 -1
  90. package/web/.next/server/chunks/ssr/_135edb9c._.js.map +1 -1
  91. package/web/.next/server/chunks/ssr/_752e01fd._.js +1 -1
  92. package/web/.next/server/chunks/ssr/_752e01fd._.js.map +1 -1
  93. package/web/.next/server/chunks/ssr/_8dacd1e7._.js +1 -1
  94. package/web/.next/server/chunks/ssr/_8dacd1e7._.js.map +1 -1
  95. package/web/.next/server/chunks/ssr/{_61480424._.js → _b29b7044._.js} +2 -2
  96. package/web/.next/server/chunks/ssr/{_61480424._.js.map → _b29b7044._.js.map} +1 -1
  97. package/web/.next/server/chunks/ssr/_cc77a143._.js +1 -1
  98. package/web/.next/server/chunks/ssr/_cc77a143._.js.map +1 -1
  99. package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js +1 -1
  100. package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js.map +1 -1
  101. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_021a05f2.js +1 -1
  102. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_021a05f2.js.map +1 -1
  103. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1fa8042e.js +1 -1
  104. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1fa8042e.js.map +1 -1
  105. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_24684200.js +1 -1
  106. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_24684200.js.map +1 -1
  107. package/web/.next/server/chunks/ssr/src_presentation_web_c4348b8a._.js +2 -2
  108. package/web/.next/server/chunks/ssr/src_presentation_web_c4348b8a._.js.map +1 -1
  109. package/web/.next/server/chunks/ssr/src_presentation_web_components_bf50c527._.js +1 -1
  110. package/web/.next/server/chunks/ssr/src_presentation_web_components_bf50c527._.js.map +1 -1
  111. package/web/.next/server/chunks/ssr/src_presentation_web_components_de814675._.js +1 -1
  112. package/web/.next/server/chunks/ssr/src_presentation_web_components_de814675._.js.map +1 -1
  113. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js +1 -1
  114. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js.map +1 -1
  115. package/web/.next/server/chunks/ssr/src_presentation_web_e4510d10._.js +2 -2
  116. package/web/.next/server/chunks/ssr/src_presentation_web_e4510d10._.js.map +1 -1
  117. package/web/.next/server/pages/500.html +2 -2
  118. package/web/.next/server/server-reference-manifest.js +1 -1
  119. package/web/.next/server/server-reference-manifest.json +28 -28
  120. package/web/.next/static/chunks/{866566643c6aaecf.js → 07c448f82a47be9b.js} +1 -1
  121. package/web/.next/static/chunks/13b44f31bc42c75f.js +1 -0
  122. package/web/.next/static/chunks/{f46a0078f2ee9c44.js → 291ca11b0444a406.js} +1 -1
  123. package/web/.next/static/chunks/{dd6cd59c8f439583.js → 370ff313c8ecbf40.js} +1 -1
  124. package/web/.next/static/chunks/3e8082bdb91101cd.js +1 -0
  125. package/web/.next/static/chunks/{dd6c69087923da49.js → 4e793015b0080efb.js} +2 -2
  126. package/web/.next/static/chunks/{9d59d178a1e154b2.js → 553ebb57e23539a0.js} +1 -1
  127. package/web/.next/static/chunks/{0b3852464455cb56.js → 6297253706a181d9.js} +1 -1
  128. package/web/.next/static/chunks/91d63f8e429f5900.css +2 -0
  129. package/web/.next/static/chunks/a4c011f21bb06fe3.js +1 -0
  130. package/web/.next/static/chunks/{c7a4303d3cf08477.js → a7b53fdf8b1142dd.js} +1 -1
  131. package/web/.next/static/chunks/177579b39fd0f63a.js +0 -1
  132. package/web/.next/static/chunks/50f89c3112f6dd23.css +0 -2
  133. package/web/.next/static/chunks/8d82ff1402dcf377.js +0 -1
  134. package/web/.next/static/chunks/e1edce9ef55b5824.js +0 -1
  135. /package/web/.next/static/{T8HBuz_VR2eZQbNRNSohp → 8io5aUA9fyFtLguGngwMV}/_buildManifest.js +0 -0
  136. /package/web/.next/static/{T8HBuz_VR2eZQbNRNSohp → 8io5aUA9fyFtLguGngwMV}/_clientMiddlewareManifest.json +0 -0
  137. /package/web/.next/static/{T8HBuz_VR2eZQbNRNSohp → 8io5aUA9fyFtLguGngwMV}/_ssgManifest.js +0 -0
@@ -1,7 +1,17 @@
1
+ export interface AcceptanceCriterionData {
2
+ description: string;
3
+ verified: boolean;
4
+ }
5
+ export interface ActionItemData {
6
+ name: string;
7
+ description: string;
8
+ acceptanceCriteria: AcceptanceCriterionData[];
9
+ }
1
10
  export interface PlanTaskData {
2
11
  title: string;
3
12
  description: string;
4
13
  state: string;
14
+ actionItems: ActionItemData[];
5
15
  }
6
16
  export interface PlanData {
7
17
  state: string;
@@ -1 +1 @@
1
- {"version":3,"file":"get-feature-plan.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/web/app/actions/get-feature-plan.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,KAAK,aAAa,GAAG;IAAE,IAAI,EAAE,QAAQ,GAAG,SAAS,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAExE,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAgC9E"}
1
+ {"version":3,"file":"get-feature-plan.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/web/app/actions/get-feature-plan.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,uBAAuB,EAAE,CAAC;CAC/C;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,cAAc,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,KAAK,aAAa,GAAG;IAAE,IAAI,EAAE,QAAQ,GAAG,SAAS,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAExE,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAwC9E"}
@@ -20,6 +20,14 @@ export async function getFeaturePlan(featureId) {
20
20
  title: t.title ?? '',
21
21
  description: t.description ?? '',
22
22
  state: t.state,
23
+ actionItems: (t.actionItems ?? []).map((ai) => ({
24
+ name: ai.name,
25
+ description: ai.description,
26
+ acceptanceCriteria: (ai.acceptanceCriteria ?? []).map((ac) => ({
27
+ description: ac.description,
28
+ verified: ac.verified,
29
+ })),
30
+ })),
23
31
  })),
24
32
  };
25
33
  return { plan };
@@ -1 +1 @@
1
- {"version":3,"file":"plan-tab.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-drawer-tabs/plan-tab.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAgB,MAAM,gCAAgC,CAAC;AAE7E,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAoBD,wBAAgB,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,YAAY,2CAgD7D"}
1
+ {"version":3,"file":"plan-tab.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-drawer-tabs/plan-tab.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAE/D,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAQD,wBAAgB,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,YAAY,2CAgD7D"}
@@ -1,20 +1,14 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Loader2, Circle, Check, Eye } from 'lucide-react';
3
+ import { Loader2 } from 'lucide-react';
4
4
  import { AlertCircle, ListTodo } from 'lucide-react';
5
5
  import { Badge } from '../../ui/badge.js';
6
+ import { TaskProgressView } from '../../common/task-progress-view/index.js';
6
7
  const planStateBadgeStyles = {
7
8
  Requirements: 'border-transparent bg-blue-50 text-blue-700 hover:bg-blue-50',
8
9
  ClarificationRequired: 'border-transparent bg-amber-50 text-amber-700 hover:bg-amber-50',
9
10
  Ready: 'border-transparent bg-green-50 text-green-700 hover:bg-green-50',
10
11
  };
11
- const taskStateConfig = {
12
- Todo: { icon: Circle, colorClass: 'text-muted-foreground' },
13
- 'Work in Progress': { icon: Loader2, colorClass: 'text-blue-600', spinning: true },
14
- Done: { icon: Check, colorClass: 'text-emerald-600' },
15
- Review: { icon: Eye, colorClass: 'text-amber-600' },
16
- };
17
- const defaultTaskConfig = { icon: Circle, colorClass: 'text-muted-foreground' };
18
12
  export function PlanTab({ plan, loading, error }) {
19
13
  if (loading) {
20
14
  return (_jsx("div", { "data-testid": "plan-tab-loading", className: "flex items-center justify-center p-8", children: _jsx(Loader2, { className: "text-muted-foreground h-6 w-6 animate-spin" }) }));
@@ -25,16 +19,5 @@ export function PlanTab({ plan, loading, error }) {
25
19
  if (!plan) {
26
20
  return (_jsxs("div", { className: "flex flex-col items-center justify-center gap-2 p-8", children: [_jsx(ListTodo, { className: "text-muted-foreground h-8 w-8" }), _jsx("p", { className: "text-muted-foreground text-sm", children: "No plan created yet" })] }));
27
21
  }
28
- return (_jsxs("div", { className: "flex flex-col gap-4 p-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-muted-foreground text-xs font-medium", children: "Plan State" }), _jsx(Badge, { className: planStateBadgeStyles[plan.state] ?? 'border-transparent bg-gray-50 text-gray-700', children: plan.state })] }), plan.overview ? (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("span", { className: "text-muted-foreground text-xs font-medium", children: "Overview" }), _jsx("p", { className: "text-sm leading-relaxed", children: plan.overview })] })) : null, _jsx(TaskList, { tasks: plan.tasks })] }));
29
- }
30
- function TaskList({ tasks }) {
31
- if (tasks.length === 0) {
32
- return (_jsx("div", { className: "flex flex-col items-center justify-center gap-2 py-4", children: _jsx("p", { className: "text-muted-foreground text-sm", children: "No tasks defined yet" }) }));
33
- }
34
- return (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("span", { className: "text-muted-foreground text-xs font-medium", children: "Tasks" }), _jsx("div", { "data-testid": "plan-task-list", className: "flex flex-col gap-2", children: tasks.map((task, index) => (_jsx(TaskItem, { task: task, index: index }, task.title || `task-${index}`))) })] }));
35
- }
36
- function TaskItem({ task, index }) {
37
- const config = taskStateConfig[task.state] ?? defaultTaskConfig;
38
- const Icon = config.icon;
39
- return (_jsxs("div", { "data-testid": `plan-task-${index}`, className: `flex items-start gap-2 rounded-md border px-3 py-2 ${config.colorClass}`, children: [_jsx(Icon, { className: `mt-0.5 h-4 w-4 shrink-0 ${config.spinning ? 'animate-spin' : ''}` }), _jsxs("div", { className: "flex min-w-0 flex-col gap-0.5", children: [_jsx("span", { className: "text-sm font-medium", children: task.title }), task.description ? (_jsx("span", { className: "text-muted-foreground text-xs", children: task.description })) : null] })] }));
22
+ return (_jsxs("div", { className: "flex flex-col gap-4 p-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-muted-foreground text-xs font-medium", children: "Plan State" }), _jsx(Badge, { className: planStateBadgeStyles[plan.state] ?? 'border-transparent bg-gray-50 text-gray-700', children: plan.state })] }), plan.overview ? (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("span", { className: "text-muted-foreground text-xs font-medium", children: "Overview" }), _jsx("p", { className: "text-sm leading-relaxed", children: plan.overview })] })) : null, _jsx(TaskProgressView, { tasks: plan.tasks })] }));
40
23
  }
@@ -1 +1 @@
1
- {"version":3,"file":"plan-tab.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-drawer-tabs/plan-tab.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAGrC,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,OAAO,CAc9B,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,OAAO,CAAC,CAAC;AA0HtC,6EAA6E;AAC7E,eAAO,MAAM,OAAO,EAAE,KAMrB,CAAC;AAEF,2BAA2B;AAC3B,eAAO,MAAM,YAAY,EAAE,KAM1B,CAAC;AAEF,wCAAwC;AACxC,eAAO,MAAM,SAAS,EAAE,KAMvB,CAAC;AAEF,wEAAwE;AACxE,eAAO,MAAM,qBAAqB,EAAE,KAMnC,CAAC;AAEF,mDAAmD;AACnD,eAAO,MAAM,OAAO,EAAE,KAMrB,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,OAAO,EAAE,KAMrB,CAAC;AAEF,yCAAyC;AACzC,eAAO,MAAM,KAAK,EAAE,KAMnB,CAAC;AAEF,0CAA0C;AAC1C,eAAO,MAAM,KAAK,EAAE,KAMnB,CAAC"}
1
+ {"version":3,"file":"plan-tab.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-drawer-tabs/plan-tab.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAGrC,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,OAAO,CAc9B,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,OAAO,CAAC,CAAC;AAoMtC,6EAA6E;AAC7E,eAAO,MAAM,OAAO,EAAE,KAMrB,CAAC;AAEF,2BAA2B;AAC3B,eAAO,MAAM,YAAY,EAAE,KAM1B,CAAC;AAEF,wCAAwC;AACxC,eAAO,MAAM,SAAS,EAAE,KAMvB,CAAC;AAEF,wEAAwE;AACxE,eAAO,MAAM,qBAAqB,EAAE,KAMnC,CAAC;AAEF,mDAAmD;AACnD,eAAO,MAAM,OAAO,EAAE,KAMrB,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,OAAO,EAAE,KAMrB,CAAC;AAEF,yCAAyC;AACzC,eAAO,MAAM,KAAK,EAAE,KAMnB,CAAC;AAEF,0CAA0C;AAC1C,eAAO,MAAM,KAAK,EAAE,KAMnB,CAAC"}
@@ -23,31 +23,96 @@ const mixedTasksPlan = {
23
23
  title: 'Set up OAuth provider configuration',
24
24
  description: 'Configure Google and GitHub OAuth apps with client IDs and secrets',
25
25
  state: 'Done',
26
+ actionItems: [
27
+ {
28
+ name: 'Create OAuth config module',
29
+ description: 'Define provider configuration schema and load from env',
30
+ acceptanceCriteria: [
31
+ { description: 'Config loads from environment variables', verified: true },
32
+ { description: 'Validation rejects missing client ID or secret', verified: true },
33
+ ],
34
+ },
35
+ {
36
+ name: 'Register OAuth apps',
37
+ description: 'Register apps with Google and GitHub developer consoles',
38
+ acceptanceCriteria: [
39
+ { description: 'Google OAuth app created with correct scopes', verified: true },
40
+ { description: 'GitHub OAuth app created with correct callback URL', verified: true },
41
+ ],
42
+ },
43
+ ],
26
44
  },
27
45
  {
28
46
  title: 'Implement callback handler',
29
47
  description: 'Handle OAuth callback with authorization code exchange',
30
48
  state: 'Done',
49
+ actionItems: [
50
+ {
51
+ name: 'Build callback endpoint',
52
+ description: 'GET /auth/callback with code exchange logic',
53
+ acceptanceCriteria: [
54
+ { description: 'Exchanges authorization code for tokens', verified: true },
55
+ ],
56
+ },
57
+ ],
31
58
  },
32
59
  {
33
60
  title: 'Add token refresh logic',
34
61
  description: 'Implement silent token refresh using refresh tokens stored in httpOnly cookies',
35
62
  state: 'Work in Progress',
63
+ actionItems: [
64
+ {
65
+ name: 'Implement refresh endpoint',
66
+ description: 'POST /auth/refresh to issue new access token',
67
+ acceptanceCriteria: [
68
+ { description: 'Returns new access token when refresh token is valid', verified: true },
69
+ { description: 'Returns 401 when refresh token is expired', verified: false },
70
+ ],
71
+ },
72
+ {
73
+ name: 'Add httpOnly cookie handling',
74
+ description: 'Store refresh tokens in secure httpOnly cookies',
75
+ acceptanceCriteria: [
76
+ { description: 'Cookies are set with Secure and HttpOnly flags', verified: false },
77
+ ],
78
+ },
79
+ ],
36
80
  },
37
81
  {
38
82
  title: 'Create user profile sync',
39
83
  description: 'Sync user profile data from identity providers on login',
40
84
  state: 'Todo',
85
+ actionItems: [
86
+ {
87
+ name: 'Fetch profile from provider',
88
+ description: 'Use access token to retrieve user profile from Google/GitHub',
89
+ acceptanceCriteria: [
90
+ { description: 'Retrieves name, email, and avatar from Google', verified: false },
91
+ { description: 'Retrieves name, email, and avatar from GitHub', verified: false },
92
+ ],
93
+ },
94
+ ],
41
95
  },
42
96
  {
43
97
  title: 'Write integration tests',
44
98
  description: 'Test the full OAuth flow with mocked providers',
45
99
  state: 'Todo',
100
+ actionItems: [],
46
101
  },
47
102
  {
48
103
  title: 'Update API documentation',
49
104
  description: 'Document new authentication endpoints and flows',
50
105
  state: 'Review',
106
+ actionItems: [
107
+ {
108
+ name: 'Document auth endpoints',
109
+ description: 'Add OpenAPI specs for all auth routes',
110
+ acceptanceCriteria: [
111
+ { description: 'All endpoints have OpenAPI documentation', verified: true },
112
+ { description: 'Examples included for each endpoint', verified: true },
113
+ ],
114
+ },
115
+ ],
51
116
  },
52
117
  ],
53
118
  };
@@ -59,21 +124,25 @@ const allDonePlan = {
59
124
  title: 'Design sliding window algorithm',
60
125
  description: 'Implement Redis-backed sliding window counter',
61
126
  state: 'Done',
127
+ actionItems: [],
62
128
  },
63
129
  {
64
130
  title: 'Add rate limit middleware',
65
131
  description: 'Express middleware that checks rate limits per IP',
66
132
  state: 'Done',
133
+ actionItems: [],
67
134
  },
68
135
  {
69
136
  title: 'Configure limits per endpoint',
70
137
  description: 'Set different rate limits for public vs authenticated endpoints',
71
138
  state: 'Done',
139
+ actionItems: [],
72
140
  },
73
141
  {
74
142
  title: 'Add rate limit headers',
75
143
  description: 'Include X-RateLimit-* headers in responses',
76
144
  state: 'Done',
145
+ actionItems: [],
77
146
  },
78
147
  ],
79
148
  };
@@ -85,16 +154,19 @@ const draftPlan = {
85
154
  title: 'Define notification types',
86
155
  description: 'Enumerate all notification events and their payloads',
87
156
  state: 'Todo',
157
+ actionItems: [],
88
158
  },
89
159
  {
90
160
  title: 'Set up WebSocket server',
91
161
  description: 'Configure Socket.io with Redis adapter for horizontal scaling',
92
162
  state: 'Todo',
163
+ actionItems: [],
93
164
  },
94
165
  {
95
166
  title: 'Implement notification store',
96
167
  description: 'Persist notifications in database with read/unread tracking',
97
168
  state: 'Todo',
169
+ actionItems: [],
98
170
  },
99
171
  ],
100
172
  };
@@ -106,11 +178,13 @@ const clarificationPlan = {
106
178
  title: 'Set up email provider',
107
179
  description: 'Configure SendGrid API integration',
108
180
  state: 'Done',
181
+ actionItems: [],
109
182
  },
110
183
  {
111
184
  title: 'Create template engine',
112
185
  description: 'Implement Handlebars-based email template rendering',
113
186
  state: 'Todo',
187
+ actionItems: [],
114
188
  },
115
189
  ],
116
190
  };
@@ -0,0 +1,3 @@
1
+ export { TaskProgressView } from './task-progress-view.js';
2
+ export type { TaskProgressViewProps } from './task-progress-view.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/task-progress-view/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,YAAY,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1 @@
1
+ export { TaskProgressView } from './task-progress-view.js';
@@ -0,0 +1,6 @@
1
+ import type { PlanTaskData } from '../../../app/actions/get-feature-plan.js';
2
+ export interface TaskProgressViewProps {
3
+ tasks: PlanTaskData[];
4
+ }
5
+ export declare function TaskProgressView({ tasks }: TaskProgressViewProps): import("react/jsx-runtime").JSX.Element;
6
+ //# sourceMappingURL=task-progress-view.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-progress-view.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/task-progress-view/task-progress-view.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,gCAAgC,CAAC;AAEnF,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AA8CD,wBAAgB,gBAAgB,CAAC,EAAE,KAAK,EAAE,EAAE,qBAAqB,2CAmBhE"}
@@ -0,0 +1,84 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState, useCallback, useMemo } from 'react';
4
+ import { Loader2, Circle, Check, Eye, ChevronRight, CheckCircle2, CircleDashed, } from 'lucide-react';
5
+ import { cn } from '../../../lib/utils.js';
6
+ const taskStateConfig = {
7
+ Todo: {
8
+ icon: Circle,
9
+ colorClass: 'text-muted-foreground',
10
+ borderClass: 'border-border',
11
+ label: 'Todo',
12
+ },
13
+ 'Work in Progress': {
14
+ icon: Loader2,
15
+ colorClass: 'text-blue-600',
16
+ borderClass: 'border-blue-200',
17
+ spinning: true,
18
+ label: 'In Progress',
19
+ },
20
+ Done: {
21
+ icon: Check,
22
+ colorClass: 'text-emerald-600',
23
+ borderClass: 'border-emerald-200',
24
+ label: 'Done',
25
+ },
26
+ Review: {
27
+ icon: Eye,
28
+ colorClass: 'text-amber-600',
29
+ borderClass: 'border-amber-200',
30
+ label: 'Review',
31
+ },
32
+ };
33
+ const defaultTaskConfig = {
34
+ icon: Circle,
35
+ colorClass: 'text-muted-foreground',
36
+ borderClass: 'border-border',
37
+ label: 'Unknown',
38
+ };
39
+ export function TaskProgressView({ tasks }) {
40
+ if (tasks.length === 0) {
41
+ return (_jsx("div", { className: "flex flex-col items-center justify-center gap-2 py-4", children: _jsx("p", { className: "text-muted-foreground text-sm", children: "No tasks defined yet" }) }));
42
+ }
43
+ return (_jsxs("div", { "data-testid": "task-progress-view", className: "flex flex-col gap-3", children: [_jsx(ProgressSummary, { tasks: tasks }), _jsx("div", { "data-testid": "task-progress-list", className: "flex flex-col gap-2", children: tasks.map((task, index) => (_jsx(TaskCard, { task: task, index: index }, task.title || `task-${index}`))) })] }));
44
+ }
45
+ // ── Progress Summary ─────────────────────────────────────────────────
46
+ function ProgressSummary({ tasks }) {
47
+ const counts = useMemo(() => {
48
+ const done = tasks.filter((t) => t.state === 'Done').length;
49
+ const wip = tasks.filter((t) => t.state === 'Work in Progress').length;
50
+ const review = tasks.filter((t) => t.state === 'Review').length;
51
+ const todo = tasks.filter((t) => t.state !== 'Done' && t.state !== 'Work in Progress' && t.state !== 'Review').length;
52
+ const total = tasks.length;
53
+ const percent = total > 0 ? Math.round((done / total) * 100) : 0;
54
+ return { done, wip, review, todo, total, percent };
55
+ }, [tasks]);
56
+ return (_jsxs("div", { "data-testid": "task-progress-summary", className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-muted-foreground text-xs font-medium", children: "Task Progress" }), _jsxs("span", { className: "text-muted-foreground text-xs", children: [counts.done, " of ", counts.total, " done"] })] }), _jsx("div", { className: "bg-muted h-2 w-full overflow-hidden rounded-full", children: _jsx("div", { "data-testid": "task-progress-bar", className: "h-full rounded-full bg-emerald-500 transition-all", style: { width: `${counts.percent}%` } }) }), _jsxs("div", { className: "flex flex-wrap gap-3", children: [counts.done > 0 ? (_jsx(StatChip, { icon: Check, label: "Done", count: counts.done, className: "text-emerald-600" })) : null, counts.wip > 0 ? (_jsx(StatChip, { icon: Loader2, label: "In Progress", count: counts.wip, className: "text-blue-600" })) : null, counts.review > 0 ? (_jsx(StatChip, { icon: Eye, label: "Review", count: counts.review, className: "text-amber-600" })) : null, counts.todo > 0 ? (_jsx(StatChip, { icon: Circle, label: "Todo", count: counts.todo, className: "text-muted-foreground" })) : null] })] }));
57
+ }
58
+ function StatChip({ icon: Icon, label, count, className, }) {
59
+ return (_jsxs("span", { className: cn('flex items-center gap-1 text-xs', className), children: [_jsx(Icon, { className: "h-3 w-3" }), count, " ", label] }));
60
+ }
61
+ // ── Task Card ────────────────────────────────────────────────────────
62
+ function TaskCard({ task, index }) {
63
+ const [expanded, setExpanded] = useState(false);
64
+ const config = taskStateConfig[task.state] ?? defaultTaskConfig;
65
+ const Icon = config.icon;
66
+ const hasDetails = task.actionItems.length > 0;
67
+ const handleToggle = useCallback(() => {
68
+ if (hasDetails) {
69
+ setExpanded((prev) => !prev);
70
+ }
71
+ }, [hasDetails]);
72
+ return (_jsxs("div", { "data-testid": `task-card-${index}`, className: cn('rounded-lg border', config.borderClass), children: [_jsxs("button", { type: "button", onClick: handleToggle, disabled: !hasDetails, className: cn('flex w-full items-start gap-2 px-3 py-2.5 text-left', hasDetails && 'hover:bg-muted/50 cursor-pointer transition-colors'), children: [_jsx(Icon, { className: cn('mt-0.5 h-4 w-4 shrink-0', config.colorClass, config.spinning && 'animate-spin') }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [_jsx("span", { className: cn('text-sm font-medium', config.colorClass), children: task.title }), task.description ? (_jsx("span", { className: "text-muted-foreground text-xs", children: task.description })) : null] }), hasDetails ? (_jsx(ChevronRight, { className: cn('text-muted-foreground mt-0.5 h-4 w-4 shrink-0 transition-transform', expanded && 'rotate-90') })) : null] }), expanded && hasDetails ? (_jsx("div", { className: "border-t px-3 py-2.5", children: _jsx("div", { className: "flex flex-col gap-2", children: task.actionItems.map((item, aiIndex) => (_jsx(ActionItemRow, { item: item }, item.name || `ai-${aiIndex}`))) }) })) : null] }));
73
+ }
74
+ // ── Action Item Row ──────────────────────────────────────────────────
75
+ function ActionItemRow({ item }) {
76
+ const totalCriteria = item.acceptanceCriteria.length;
77
+ const verifiedCriteria = item.acceptanceCriteria.filter((ac) => ac.verified).length;
78
+ const allVerified = totalCriteria > 0 && verifiedCriteria === totalCriteria;
79
+ return (_jsxs("div", { "data-testid": "action-item", className: "flex flex-col gap-1.5", children: [_jsxs("div", { className: "flex items-start gap-2", children: [allVerified ? (_jsx(CheckCircle2, { className: "mt-0.5 h-3.5 w-3.5 shrink-0 text-emerald-600" })) : (_jsx(CircleDashed, { className: "text-muted-foreground mt-0.5 h-3.5 w-3.5 shrink-0" })), _jsxs("div", { className: "flex min-w-0 flex-col gap-0.5", children: [_jsx("span", { className: "text-xs font-medium", children: item.name }), item.description ? (_jsx("span", { className: "text-muted-foreground text-[11px]", children: item.description })) : null] }), totalCriteria > 0 ? (_jsxs("span", { className: "text-muted-foreground ml-auto shrink-0 text-[11px]", children: [verifiedCriteria, "/", totalCriteria] })) : null] }), totalCriteria > 0 ? (_jsx("div", { className: "ml-5.5 flex flex-col gap-1", children: item.acceptanceCriteria.map((ac, acIndex) => (_jsx(AcceptanceCriterionRow, { criterion: ac }, ac.description || `ac-${acIndex}`))) })) : null] }));
80
+ }
81
+ // ── Acceptance Criterion Row ─────────────────────────────────────────
82
+ function AcceptanceCriterionRow({ criterion, }) {
83
+ return (_jsxs("div", { "data-testid": "acceptance-criterion", className: "flex items-start gap-1.5", children: [criterion.verified ? (_jsx(Check, { className: "mt-0.5 h-3 w-3 shrink-0 text-emerald-600" })) : (_jsx(Circle, { className: "text-muted-foreground mt-0.5 h-3 w-3 shrink-0" })), _jsx("span", { className: cn('text-[11px]', criterion.verified ? 'text-muted-foreground line-through' : 'text-foreground'), children: criterion.description })] }));
84
+ }
@@ -0,0 +1,16 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { TaskProgressView } from './task-progress-view.js';
3
+ declare const meta: Meta<typeof TaskProgressView>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof TaskProgressView>;
6
+ /** Default — tasks in mixed states with action items and acceptance criteria. */
7
+ export declare const Default: Story;
8
+ /** All tasks completed. */
9
+ export declare const AllDone: Story;
10
+ /** All tasks in Todo state. */
11
+ export declare const AllTodo: Story;
12
+ /** Empty — no tasks defined. */
13
+ export declare const Empty: Story;
14
+ /** Single task with detailed action items. */
15
+ export declare const SingleTaskDetailed: Story;
16
+ //# sourceMappingURL=task-progress-view.stories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-progress-view.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/task-progress-view/task-progress-view.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAGxD,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,gBAAgB,CAYvC,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,gBAAgB,CAAC,CAAC;AA+J/C,iFAAiF;AACjF,eAAO,MAAM,OAAO,EAAE,KAIrB,CAAC;AAEF,2BAA2B;AAC3B,eAAO,MAAM,OAAO,EAAE,KAIrB,CAAC;AAEF,+BAA+B;AAC/B,eAAO,MAAM,OAAO,EAAE,KAIrB,CAAC;AAEF,gCAAgC;AAChC,eAAO,MAAM,KAAK,EAAE,KAInB,CAAC;AAEF,8CAA8C;AAC9C,eAAO,MAAM,kBAAkB,EAAE,KAIhC,CAAC"}
@@ -0,0 +1,194 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { TaskProgressView } from './task-progress-view.js';
3
+ const meta = {
4
+ title: 'Composed/TaskProgressView',
5
+ component: TaskProgressView,
6
+ tags: ['autodocs'],
7
+ parameters: { layout: 'padded' },
8
+ decorators: [
9
+ (Story) => (_jsx("div", { style: { width: '400px', border: '1px solid var(--color-border)', padding: '16px' }, children: _jsx(Story, {}) })),
10
+ ],
11
+ };
12
+ export default meta;
13
+ /* ---------------------------------------------------------------------------
14
+ * Data fixtures
15
+ * ------------------------------------------------------------------------- */
16
+ const mixedTasks = [
17
+ {
18
+ title: 'Set up OAuth provider configuration',
19
+ description: 'Configure Google and GitHub OAuth apps with client IDs and secrets',
20
+ state: 'Done',
21
+ actionItems: [
22
+ {
23
+ name: 'Create OAuth config module',
24
+ description: 'Define provider configuration schema and load from env',
25
+ acceptanceCriteria: [
26
+ { description: 'Config loads from environment variables', verified: true },
27
+ { description: 'Validation rejects missing client ID or secret', verified: true },
28
+ ],
29
+ },
30
+ {
31
+ name: 'Register OAuth apps',
32
+ description: 'Register apps with Google and GitHub developer consoles',
33
+ acceptanceCriteria: [
34
+ { description: 'Google OAuth app created with correct scopes', verified: true },
35
+ { description: 'GitHub OAuth app created with correct callback URL', verified: true },
36
+ ],
37
+ },
38
+ ],
39
+ },
40
+ {
41
+ title: 'Implement callback handler',
42
+ description: 'Handle OAuth callback with authorization code exchange',
43
+ state: 'Done',
44
+ actionItems: [
45
+ {
46
+ name: 'Build callback endpoint',
47
+ description: 'GET /auth/callback with code exchange logic',
48
+ acceptanceCriteria: [
49
+ { description: 'Exchanges authorization code for tokens', verified: true },
50
+ ],
51
+ },
52
+ ],
53
+ },
54
+ {
55
+ title: 'Add token refresh logic',
56
+ description: 'Implement silent token refresh using refresh tokens stored in httpOnly cookies',
57
+ state: 'Work in Progress',
58
+ actionItems: [
59
+ {
60
+ name: 'Implement refresh endpoint',
61
+ description: 'POST /auth/refresh to issue new access token',
62
+ acceptanceCriteria: [
63
+ { description: 'Returns new access token when refresh token is valid', verified: true },
64
+ { description: 'Returns 401 when refresh token is expired', verified: false },
65
+ ],
66
+ },
67
+ {
68
+ name: 'Add httpOnly cookie handling',
69
+ description: 'Store refresh tokens in secure httpOnly cookies',
70
+ acceptanceCriteria: [
71
+ { description: 'Cookies are set with Secure and HttpOnly flags', verified: false },
72
+ ],
73
+ },
74
+ ],
75
+ },
76
+ {
77
+ title: 'Create user profile sync',
78
+ description: 'Sync user profile data from identity providers on login',
79
+ state: 'Todo',
80
+ actionItems: [
81
+ {
82
+ name: 'Fetch profile from provider',
83
+ description: 'Use access token to retrieve user profile from Google/GitHub',
84
+ acceptanceCriteria: [
85
+ { description: 'Retrieves name, email, and avatar from Google', verified: false },
86
+ { description: 'Retrieves name, email, and avatar from GitHub', verified: false },
87
+ ],
88
+ },
89
+ ],
90
+ },
91
+ {
92
+ title: 'Write integration tests',
93
+ description: 'Test the full OAuth flow with mocked providers',
94
+ state: 'Todo',
95
+ actionItems: [],
96
+ },
97
+ {
98
+ title: 'Update API documentation',
99
+ description: 'Document new authentication endpoints and flows',
100
+ state: 'Review',
101
+ actionItems: [
102
+ {
103
+ name: 'Document auth endpoints',
104
+ description: 'Add OpenAPI specs for all auth routes',
105
+ acceptanceCriteria: [
106
+ { description: 'All endpoints have OpenAPI documentation', verified: true },
107
+ { description: 'Examples included for each endpoint', verified: true },
108
+ ],
109
+ },
110
+ ],
111
+ },
112
+ ];
113
+ const allDoneTasks = [
114
+ {
115
+ title: 'Design sliding window algorithm',
116
+ description: 'Implement Redis-backed sliding window counter',
117
+ state: 'Done',
118
+ actionItems: [
119
+ {
120
+ name: 'Implement counter',
121
+ description: 'Redis ZADD-based sliding window',
122
+ acceptanceCriteria: [
123
+ { description: 'Counts requests in time window', verified: true },
124
+ { description: 'Cleans up expired entries', verified: true },
125
+ ],
126
+ },
127
+ ],
128
+ },
129
+ {
130
+ title: 'Add rate limit middleware',
131
+ description: 'Express middleware that checks rate limits per IP',
132
+ state: 'Done',
133
+ actionItems: [],
134
+ },
135
+ {
136
+ title: 'Configure limits per endpoint',
137
+ description: 'Set different rate limits for public vs authenticated endpoints',
138
+ state: 'Done',
139
+ actionItems: [],
140
+ },
141
+ ];
142
+ const allTodoTasks = [
143
+ {
144
+ title: 'Define notification types',
145
+ description: 'Enumerate all notification events and their payloads',
146
+ state: 'Todo',
147
+ actionItems: [],
148
+ },
149
+ {
150
+ title: 'Set up WebSocket server',
151
+ description: 'Configure Socket.io with Redis adapter',
152
+ state: 'Todo',
153
+ actionItems: [],
154
+ },
155
+ {
156
+ title: 'Implement notification store',
157
+ description: 'Persist notifications in database',
158
+ state: 'Todo',
159
+ actionItems: [],
160
+ },
161
+ ];
162
+ /* ---------------------------------------------------------------------------
163
+ * Stories
164
+ * ------------------------------------------------------------------------- */
165
+ /** Default — tasks in mixed states with action items and acceptance criteria. */
166
+ export const Default = {
167
+ args: {
168
+ tasks: mixedTasks,
169
+ },
170
+ };
171
+ /** All tasks completed. */
172
+ export const AllDone = {
173
+ args: {
174
+ tasks: allDoneTasks,
175
+ },
176
+ };
177
+ /** All tasks in Todo state. */
178
+ export const AllTodo = {
179
+ args: {
180
+ tasks: allTodoTasks,
181
+ },
182
+ };
183
+ /** Empty — no tasks defined. */
184
+ export const Empty = {
185
+ args: {
186
+ tasks: [],
187
+ },
188
+ };
189
+ /** Single task with detailed action items. */
190
+ export const SingleTaskDetailed = {
191
+ args: {
192
+ tasks: [mixedTasks[2]],
193
+ },
194
+ };