@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.
- package/dist/src/presentation/web/app/actions/get-feature-plan.d.ts +10 -0
- package/dist/src/presentation/web/app/actions/get-feature-plan.d.ts.map +1 -1
- package/dist/src/presentation/web/app/actions/get-feature-plan.js +8 -0
- package/dist/src/presentation/web/components/common/feature-drawer-tabs/plan-tab.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/feature-drawer-tabs/plan-tab.js +3 -20
- package/dist/src/presentation/web/components/common/feature-drawer-tabs/plan-tab.stories.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/feature-drawer-tabs/plan-tab.stories.js +74 -0
- package/dist/src/presentation/web/components/common/task-progress-view/index.d.ts +3 -0
- package/dist/src/presentation/web/components/common/task-progress-view/index.d.ts.map +1 -0
- package/dist/src/presentation/web/components/common/task-progress-view/index.js +1 -0
- package/dist/src/presentation/web/components/common/task-progress-view/task-progress-view.d.ts +6 -0
- package/dist/src/presentation/web/components/common/task-progress-view/task-progress-view.d.ts.map +1 -0
- package/dist/src/presentation/web/components/common/task-progress-view/task-progress-view.js +84 -0
- package/dist/src/presentation/web/components/common/task-progress-view/task-progress-view.stories.d.ts +16 -0
- package/dist/src/presentation/web/components/common/task-progress-view/task-progress-view.stories.d.ts.map +1 -0
- package/dist/src/presentation/web/components/common/task-progress-view/task-progress-view.stories.js +194 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/build-manifest.json +2 -2
- package/web/.next/fallback-build-manifest.json +2 -2
- package/web/.next/prerender-manifest.json +3 -3
- package/web/.next/required-server-files.js +1 -1
- package/web/.next/required-server-files.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +18 -18
- package/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +21 -21
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +14 -14
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +18 -18
- package/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +21 -21
- package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +14 -14
- package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +14 -14
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/api/attachments/preview/route.js.nft.json +1 -1
- package/web/.next/server/app/settings/page/server-reference-manifest.json +5 -5
- package/web/.next/server/app/settings/page.js.nft.json +1 -1
- package/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/skills/page/server-reference-manifest.json +6 -6
- package/web/.next/server/app/skills/page.js.nft.json +1 -1
- package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/tools/page/server-reference-manifest.json +6 -6
- package/web/.next/server/app/tools/page.js.nft.json +1 -1
- package/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/version/page/server-reference-manifest.json +1 -1
- package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
- package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js +1 -1
- package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js.map +1 -1
- package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_da7eef33.js +1 -1
- package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_da7eef33.js.map +1 -1
- package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_0a9251aa.js +1 -1
- package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_0a9251aa.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__11033878._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__11033878._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__17143953._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__17143953._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__357d99f9._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__4be9a9bb._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__5d87af2e._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__9de266fa._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__9de266fa._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__b3a4cef4._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__b3a4cef4._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__f0d8d054._.js +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__fdc75809._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__fdc75809._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_135edb9c._.js +1 -1
- package/web/.next/server/chunks/ssr/_135edb9c._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_752e01fd._.js +1 -1
- package/web/.next/server/chunks/ssr/_752e01fd._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_8dacd1e7._.js +1 -1
- package/web/.next/server/chunks/ssr/_8dacd1e7._.js.map +1 -1
- package/web/.next/server/chunks/ssr/{_61480424._.js → _b29b7044._.js} +2 -2
- package/web/.next/server/chunks/ssr/{_61480424._.js.map → _b29b7044._.js.map} +1 -1
- package/web/.next/server/chunks/ssr/_cc77a143._.js +1 -1
- package/web/.next/server/chunks/ssr/_cc77a143._.js.map +1 -1
- package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js +1 -1
- package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_021a05f2.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_021a05f2.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1fa8042e.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1fa8042e.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_24684200.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_24684200.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_c4348b8a._.js +2 -2
- package/web/.next/server/chunks/ssr/src_presentation_web_c4348b8a._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_bf50c527._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_bf50c527._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_de814675._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_de814675._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_e4510d10._.js +2 -2
- package/web/.next/server/chunks/ssr/src_presentation_web_e4510d10._.js.map +1 -1
- package/web/.next/server/pages/500.html +2 -2
- package/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/server/server-reference-manifest.json +28 -28
- package/web/.next/static/chunks/{866566643c6aaecf.js → 07c448f82a47be9b.js} +1 -1
- package/web/.next/static/chunks/13b44f31bc42c75f.js +1 -0
- package/web/.next/static/chunks/{f46a0078f2ee9c44.js → 291ca11b0444a406.js} +1 -1
- package/web/.next/static/chunks/{dd6cd59c8f439583.js → 370ff313c8ecbf40.js} +1 -1
- package/web/.next/static/chunks/3e8082bdb91101cd.js +1 -0
- package/web/.next/static/chunks/{dd6c69087923da49.js → 4e793015b0080efb.js} +2 -2
- package/web/.next/static/chunks/{9d59d178a1e154b2.js → 553ebb57e23539a0.js} +1 -1
- package/web/.next/static/chunks/{0b3852464455cb56.js → 6297253706a181d9.js} +1 -1
- package/web/.next/static/chunks/91d63f8e429f5900.css +2 -0
- package/web/.next/static/chunks/a4c011f21bb06fe3.js +1 -0
- package/web/.next/static/chunks/{c7a4303d3cf08477.js → a7b53fdf8b1142dd.js} +1 -1
- package/web/.next/static/chunks/177579b39fd0f63a.js +0 -1
- package/web/.next/static/chunks/50f89c3112f6dd23.css +0 -2
- package/web/.next/static/chunks/8d82ff1402dcf377.js +0 -1
- package/web/.next/static/chunks/e1edce9ef55b5824.js +0 -1
- /package/web/.next/static/{T8HBuz_VR2eZQbNRNSohp → 8io5aUA9fyFtLguGngwMV}/_buildManifest.js +0 -0
- /package/web/.next/static/{T8HBuz_VR2eZQbNRNSohp → 8io5aUA9fyFtLguGngwMV}/_clientMiddlewareManifest.json +0 -0
- /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;
|
|
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":"
|
|
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
|
|
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(
|
|
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
|
}
|
package/dist/src/presentation/web/components/common/feature-drawer-tabs/plan-tab.stories.d.ts.map
CHANGED
|
@@ -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;
|
|
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 @@
|
|
|
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';
|
package/dist/src/presentation/web/components/common/task-progress-view/task-progress-view.d.ts
ADDED
|
@@ -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
|
package/dist/src/presentation/web/components/common/task-progress-view/task-progress-view.d.ts.map
ADDED
|
@@ -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"}
|
package/dist/src/presentation/web/components/common/task-progress-view/task-progress-view.stories.js
ADDED
|
@@ -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
|
+
};
|