Skip to content

feat(web-ui): empty state with task generation CTA on task board (#477)#499

Merged
frankbria merged 2 commits intomainfrom
feat/issue-477-task-board-empty-state
Mar 24, 2026
Merged

feat(web-ui): empty state with task generation CTA on task board (#477)#499
frankbria merged 2 commits intomainfrom
feat/issue-477-task-board-empty-state

Conversation

@frankbria
Copy link
Owner

@frankbria frankbria commented Mar 24, 2026

Summary

  • Empty state renders when tasks.length === 0 after loading completes (no flash — guarded by existing isLoading skeleton)
  • Task01Icon + explanatory text + PRD availability context dot
  • PRD found: green dot — "PRD found — ready to generate tasks"
  • No PRD: muted dot — "No PRD yet — create one first"
  • Primary CTA: Generate from PRD →/prd
  • Secondary CTA: View PRD (outline) → /prd
  • TaskBoardContent conditionally rendered only when tasks.length > 0
  • PRD existence via useSWR + prdApi.getAll() (separate cache key)

Closes #477

Test plan

  • Empty workspace shows empty state card with both CTAs
  • Empty state shows "PRD found" context when workspace has a PRD
  • Empty state shows "No PRD yet" context when no PRD exists
  • Both CTAs navigate to /prd
  • No flash during initial load (skeleton shows first, then empty state)
  • Board with tasks shows normal kanban (empty state hidden)
  • Build passes clean

Summary by CodeRabbit

  • New Features
    • Task Board now shows a PRD-aware empty state when there are no tasks, with contextual messaging and tailored quick-action buttons to help users get started or navigate to related areas.
    • Kanban board content is rendered only when tasks exist, keeping the interface cleaner for empty projects.

- Add empty state when tasks.length === 0 (after loading completes — no flash)
- Shows Task01Icon, explanatory text, and PRD availability context
- PRD found: green dot "PRD found — ready to generate tasks"
- No PRD: muted dot "No PRD yet — create one first"
- Primary CTA: "Generate from PRD →" → /prd
- Secondary CTA: "View PRD" (outline) → /prd
- TaskBoardContent hidden when tasks.length === 0
- PRD existence from useSWR prdApi.getAll() (separate cache key)
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 24, 2026

Walkthrough

Added a PRD existence lookup and conditional rendering to TaskBoardView: when no tasks exist it shows an empty-state panel with context-aware messaging and CTAs (generate from PRD or create task); otherwise it renders the existing kanban content. PRD presence is determined via SWR prdApi.getAll keyed by workspacePath.

Changes

Cohort / File(s) Summary
Task board & empty state
web-ui/src/components/tasks/TaskBoardView.tsx
Added SWR-based PRD lookup (prdApi.getAll) and hasPrd derivation; conditional rendering to show an empty-state panel when tasks.length === 0 with context-specific text and CTAs (/prd link and create-task button); retained TaskBoardContent for non-empty case. Updated imports to include Link, Button, Task01Icon, prdApi, and PrdListResponse.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped along the empty board today,
Found buttons bright to lead the way.
"Generate from PRD" — a friendly nudge,
Or "Create Task" when plans won't budge.
A tiny rabbit cheers: go build, don't stray!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly summarizes the main change: adding an empty state with task generation CTA to the task board component, which matches the changeset.
Linked Issues check ✅ Passed The PR implements most acceptance criteria from #477: empty state rendering, PRD status context, and navigation to /prd. However, the secondary CTA differs—'View PRD' instead of 'Create Task' dialog.
Out of Scope Changes check ✅ Passed The changes are focused on TaskBoardView.tsx and implement the empty state feature as specified in #477. No unrelated modifications or scope creep detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/issue-477-task-board-empty-state

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude
Copy link

claude bot commented Mar 24, 2026

Review: feat(web-ui): empty state with task generation CTA (#477)

Clean, focused implementation for the empty state. A few observations:

Issues

1. Both CTAs navigate to the same route

The primary CTA says "Generate from PRD →" but both buttons go to /prd. The primary action should either trigger generation directly (e.g., /prd?action=generate) or at minimum navigate somewhere more specific than the secondary CTA. As-is, the distinction between the two buttons is purely cosmetic and may confuse users.

2. PRD context dot flashes "No PRD yet" on first render

hasPrd defaults to false while prdData is loading (prdData?.total ?? 0). This means a workspace with a PRD will briefly show the muted "No PRD yet" dot before the SWR fetch resolves. Consider hiding the context row until prdData is defined:

const hasPrd = prdData !== undefined && (prdData.total ?? 0) > 0;
// Only render the PRD context <p> when prdData !== undefined

3. workspacePath not passed to destination

Both CTAs link to /prd without workspace context. If the PRD page derives its workspace from a query param or similar, users in multi-workspace setups may land on the wrong workspace. Verify the PRD page gets its workspace context from elsewhere (router, global state) before shipping.

Minor

JSX formatting on TaskBoardContent — The {tasks.length > 0 && <TaskBoardContent ... />} pattern leaves the closing />} on the same line as the last prop, which is hard to read for a component with 10+ props. A conditional wrapper or early-return pattern would be cleaner, but this is cosmetic.

Arrow glyph in buttonGenerate from PRD → uses a literal character. Fine for now, but an icon component is easier to manage if this gets localized later.

What's working well

  • Relying on the existing isLoading skeleton to guard against the empty-state flash is the right call — no need to duplicate that logic here.
  • SWR cache key is distinct from the tasks cache key, so no cross-invalidation risk.
  • hasPrd fallback pattern (?? 0) is safe and concise.
  • Conditional render swap (tasks.length === 0 / tasks.length > 0) is simple and correct.

The two-CTA-same-destination issue is the most user-facing concern — worth resolving before merge.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
web-ui/src/components/tasks/TaskBoardView.tsx (1)

38-43: Consider PRD loading state to prevent misleading status.

While prdData is still loading, hasPrd defaults to false, which could briefly display "No PRD yet" even when a PRD exists. Since the task skeleton guards against the empty state flash, users may see the empty state with incorrect PRD context for a moment.

Consider either:

  1. Also checking prdData === undefined to show a neutral state while loading
  2. Using the PRD isLoading state to conditionally render the status indicator
💡 Optional: Show loading/neutral state for PRD status
-  const { data: prdData } = useSWR<PrdListResponse>(
+  const { data: prdData, isLoading: isPrdLoading } = useSWR<PrdListResponse>(
     `/api/v2/prd?path=${workspacePath}`,
     () => prdApi.getAll(workspacePath)
   );
   const hasPrd = (prdData?.total ?? 0) > 0;

Then in the empty state, conditionally show a neutral indicator while isPrdLoading is true:

{isPrdLoading ? (
  <span className="flex items-center gap-1 justify-center">
    <span className="h-1.5 w-1.5 rounded-full bg-muted-foreground/40 animate-pulse" />
    Checking PRD status…
  </span>
) : hasPrd ? (
  // existing PRD found UI
) : (
  // existing no PRD UI
)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/src/components/tasks/TaskBoardView.tsx` around lines 38 - 43, The PRD
presence check currently sets hasPrd from prdData which defaults to false while
loading, causing a brief incorrect "No PRD" UI; update the useSWR call in
TaskBoardView.tsx to also destructure the loading flag (e.g., isValidating or
isLoading) alongside prdData, create an isPrdLoading boolean, and change the
empty-state rendering to treat loading as neutral (check isPrdLoading first,
then hasPrd via (prdData?.total ?? 0) > 0) so the UI shows a loading/neutral
indicator while PRD data is being fetched instead of immediately showing "No PRD
yet".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web-ui/src/components/tasks/TaskBoardView.tsx`:
- Around line 385-396: The secondary CTA currently labeled "View PRD" navigates
to /prd but should be "Create Task" and open the new task dialog per the issue;
in the TaskBoardView component replace the secondary Button/Link pair with a
Button that triggers the task-creation flow (e.g., call the existing
onCreateTask handler or modal opener such as
openNewTaskDialog/openCreateTaskModal) instead of linking to /prd, update the
button text to "Create Task", and if no handler yet, wire a prop like
onCreateTask to TaskBoardView and invoke it on click (or add a TODO comment if
leaving View PRD temporarily).

---

Nitpick comments:
In `@web-ui/src/components/tasks/TaskBoardView.tsx`:
- Around line 38-43: The PRD presence check currently sets hasPrd from prdData
which defaults to false while loading, causing a brief incorrect "No PRD" UI;
update the useSWR call in TaskBoardView.tsx to also destructure the loading flag
(e.g., isValidating or isLoading) alongside prdData, create an isPrdLoading
boolean, and change the empty-state rendering to treat loading as neutral (check
isPrdLoading first, then hasPrd via (prdData?.total ?? 0) > 0) so the UI shows a
loading/neutral indicator while PRD data is being fetched instead of immediately
showing "No PRD yet".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 34e653d4-4b0d-4105-bde4-031982c50423

📥 Commits

Reviewing files that changed from the base of the PR and between a72dc7d and de111b6.

📒 Files selected for processing (1)
  • web-ui/src/components/tasks/TaskBoardView.tsx

Comment on lines +385 to +396
<div className="mt-6 flex gap-3">
<Button asChild>
<Link href="/prd">
Generate from PRD →
</Link>
</Button>
<Button variant="outline" asChild>
<Link href="/prd">
View PRD
</Link>
</Button>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Secondary CTA differs from issue requirements.

The linked issue #477 specifies the secondary CTA should be "Create Task" to open the new task dialog, but the implementation has "View PRD" which navigates to /prd — the same destination as the primary CTA.

Having both buttons navigate to the same page provides limited value. Consider aligning with the original requirements to offer a direct "Create Task" action that opens the task creation dialog.

,

💡 Suggested fix to match issue requirements

If a task creation handler exists (e.g., onCreateTask or opening a modal), the secondary CTA could be:

-            <Button variant="outline" asChild>
-              <Link href="/prd">
-                View PRD
-              </Link>
-            </Button>
+            <Button variant="outline" onClick={handleOpenCreateTaskDialog}>
+              Create Task
+            </Button>

If task creation functionality doesn't exist yet, consider keeping "View PRD" as a temporary solution but noting this as a TODO for a follow-up.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/src/components/tasks/TaskBoardView.tsx` around lines 385 - 396, The
secondary CTA currently labeled "View PRD" navigates to /prd but should be
"Create Task" and open the new task dialog per the issue; in the TaskBoardView
component replace the secondary Button/Link pair with a Button that triggers the
task-creation flow (e.g., call the existing onCreateTask handler or modal opener
such as openNewTaskDialog/openCreateTaskModal) instead of linking to /prd,
update the button text to "Create Task", and if no handler yet, wire a prop like
onCreateTask to TaskBoardView and invoke it on click (or add a TODO comment if
leaving View PRD temporarily).

@claude
Copy link

claude bot commented Mar 24, 2026

Review: feat(web-ui): empty state with task generation CTA (#477)

The prior Claude review and CodeRabbit both flagged the two key issues well — I won't repeat the full analysis, but here's where things stand and what's still unresolved:


Still unaddressed from prior reviews

1. Duplicate CTA destination (primary blocker)

Both the "Generate from PRD →" and "View PRD" buttons link to /prd. This was flagged in the prior Claude review and confirmed by CodeRabbit's actionable comment. Per the issue spec, the secondary CTA was meant to trigger "Create Task" (open the new task dialog), not navigate to the same route as the primary. A user clicking either button gets the same result — the distinction is meaningless.

The fix is either:

  • Wire a onCreateTask prop (or equivalent modal opener) to the secondary button, or
  • If the "Create Task" dialog isn't ready, remove the secondary CTA entirely rather than shipping a misleading duplicate

2. PRD context dot flashes "No PRD yet" on first render

Also flagged previously. The fix is straightforward — destructure isLoading from the useSWR call and suppress the status indicator while loading:

const { data: prdData, isLoading: isPrdLoading } = useSWR<PrdListResponse>(...)

Then guard the status <p> with !isPrdLoading. CodeRabbit provided a complete code snippet for this.


New observation: workspacePath not encoded in the CTA link

workspacePath is received as a prop but is not passed to /prd. If navigation to /prd loses the workspace context (e.g., no global store or cookie carries it), users in multi-workspace setups will land on the wrong workspace. Worth verifying how /prd resolves its workspace before merge — if it relies on a URL param, the links should be href={/prd?path=${encodeURIComponent(workspacePath)}}.


What's solid

  • The isLoading skeleton guard preventing the empty-state flash is the right approach — clean and non-duplicative.
  • SWR cache key (/api/v2/prd?path=...) is correctly scoped, no cross-invalidation with the tasks cache.
  • Conditional render swap (tasks.length === 0 / tasks.length > 0) is simple and correct.
  • Overall scope is tight and aligned with UX: Add empty state with task generation CTA to task board #477.

The duplicate-destination CTA is the most user-facing issue and should be resolved before merge. The PRD loading flash fix is straightforward and also worth landing in this PR since both reviewers flagged it.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web-ui/src/components/tasks/TaskBoardView.tsx`:
- Around line 39-44: The code treats PRD as absent while the SWR request is
unresolved; change the hasPrd logic to only compute a boolean after the request
completes by reading useSWR's return (e.g., const { data: prdData, error:
prdError, isValidating } = useSWR...) and set hasPrd to a tri-state (undefined
while loading) or only true/false when prdData is defined (e.g., hasPrd =
prdData ? (prdData.total ?? 0) > 0 : undefined), then update any UI checks that
show the “No PRD yet” empty state to only show when prdData is settled (use
prdData/prdError/isValidating) rather than relying on a default false.
- Line 375: In TaskBoardView locate the status token span with class "h-1.5
w-1.5 rounded-full bg-green-500" and replace the hardcoded green class with the
project's gray-scale token from the Shadcn/UI (Nova preset) design system (or
swap the raw span for the shadcn Indicator/Badge component configured for the
gray color scheme); ensure you use the gray utility (e.g., the Nova gray token
used across web-ui) so the status dot matches the gray-only UI palette.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4bf319b2-b887-4147-b158-117517fbf80b

📥 Commits

Reviewing files that changed from the base of the PR and between de111b6 and 69cecd4.

📒 Files selected for processing (1)
  • web-ui/src/components/tasks/TaskBoardView.tsx

Comment on lines +39 to +44
const { data: prdData } = useSWR<PrdListResponse>(
`/api/v2/prd?path=${workspacePath}`,
() => prdApi.getAll(workspacePath)
);
const hasPrd = (prdData?.total ?? 0) > 0;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid treating PRD status as “absent” before the PRD query resolves.

Line 43 defaults hasPrd to false while PRD data is still loading (or errored), so the empty state can briefly show incorrect “No PRD yet” context.

Suggested fix
-  const { data: prdData } = useSWR<PrdListResponse>(
+  const { data: prdData, isLoading: isPrdLoading, error: prdError } = useSWR<PrdListResponse>(
     `/api/v2/prd?path=${workspacePath}`,
     () => prdApi.getAll(workspacePath)
   );
   const hasPrd = (prdData?.total ?? 0) > 0;
-            {hasPrd ? (
+            {isPrdLoading ? (
+              <span className="flex items-center gap-1 justify-center">
+                <span className="h-1.5 w-1.5 rounded-full bg-muted-foreground/40" />
+                Checking PRD status…
+              </span>
+            ) : prdError ? (
+              <span className="flex items-center gap-1 justify-center">
+                <span className="h-1.5 w-1.5 rounded-full bg-muted-foreground/40" />
+                Couldn’t verify PRD status
+              </span>
+            ) : hasPrd ? (
               <span className="flex items-center gap-1 justify-center">
                 <span className="h-1.5 w-1.5 rounded-full bg-green-500" />
                 PRD found — ready to generate tasks
               </span>
             ) : (

Also applies to: 373-383

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/src/components/tasks/TaskBoardView.tsx` around lines 39 - 44, The code
treats PRD as absent while the SWR request is unresolved; change the hasPrd
logic to only compute a boolean after the request completes by reading useSWR's
return (e.g., const { data: prdData, error: prdError, isValidating } =
useSWR...) and set hasPrd to a tri-state (undefined while loading) or only
true/false when prdData is defined (e.g., hasPrd = prdData ? (prdData.total ??
0) > 0 : undefined), then update any UI checks that show the “No PRD yet” empty
state to only show when prdData is settled (use prdData/prdError/isValidating)
rather than relying on a default false.

<p className="mt-2 text-xs text-muted-foreground">
{hasPrd ? (
<span className="flex items-center gap-1 justify-center">
<span className="h-1.5 w-1.5 rounded-full bg-green-500" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use a gray-scale status token instead of bg-green-500.

The hardcoded green class breaks the gray-only UI palette used in this project.

Suggested fix
-                <span className="h-1.5 w-1.5 rounded-full bg-green-500" />
+                <span className="h-1.5 w-1.5 rounded-full bg-foreground/60" />

As per coding guidelines, web-ui/src/**/*.{ts,tsx} must use Shadcn/UI (Nova preset) with gray color scheme and Hugeicons.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<span className="h-1.5 w-1.5 rounded-full bg-green-500" />
<span className="h-1.5 w-1.5 rounded-full bg-foreground/60" />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/src/components/tasks/TaskBoardView.tsx` at line 375, In TaskBoardView
locate the status token span with class "h-1.5 w-1.5 rounded-full bg-green-500"
and replace the hardcoded green class with the project's gray-scale token from
the Shadcn/UI (Nova preset) design system (or swap the raw span for the shadcn
Indicator/Badge component configured for the gray color scheme); ensure you use
the gray utility (e.g., the Nova gray token used across web-ui) so the status
dot matches the gray-only UI palette.

@frankbria frankbria merged commit cb074e3 into main Mar 24, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

UX: Add empty state with task generation CTA to task board

1 participant