Skip to content

feat(@helm/web): read-only kanban dashboard and item detail (Session 6)#10

Merged
lhpaul merged 9 commits into
developfrom
feature/session-6-dashboard-kanban
May 17, 2026
Merged

feat(@helm/web): read-only kanban dashboard and item detail (Session 6)#10
lhpaul merged 9 commits into
developfrom
feature/session-6-dashboard-kanban

Conversation

@lhpaul
Copy link
Copy Markdown
Owner

@lhpaul lhpaul commented May 17, 2026

Summary

  • API client (src/lib/api.ts): typed ApiResult<T> wrapper around fetch — ok/error discriminated union, network errors returned (never thrown). Imports Product from @helm/shared, WorkflowStage from @helm/workflow; ItemState/WorkflowEvent defined locally mirroring API response shapes.
  • usePolling<T> (src/hooks/usePolling.ts): fetches immediately then polls via setInterval; stable fetcherRef avoids re-running the effect on re-renders; intervalMs=null for fetch-once behaviour. Interval cleared on unmount.
  • Kanban view (src/views/Kanban.tsx): columns from product.workflow.stages_enabled, items filtered per stage, relative time badges, polling every 5s. Items error shown inline; product error shown full-page.
  • ItemDetail view (src/views/ItemDetail.tsx): stage badge, transition history table (newest first), back-to-board link, same 5s polling pattern.
  • Routing: React Router v7, / → Kanban, /items/:id → ItemDetail.

Test plan

  • pnpm --filter @helm/web test — 12 tests pass (6 API client + 6 polling hook)
  • pnpm --filter @helm/web buildtsc --noEmit + Vite build clean

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Kanban board with relative timestamps
    • Item detail page showing transition history and back navigation
    • Client-side routing between board and item pages
    • Background polling to keep views up to date and show last-known data on errors
  • API

    • Typed client for product/item endpoints with structured success/error results
  • Tests

    • Unit tests for polling and API behaviors; test setup integrated
  • Chores

    • Test tooling and scripts added; dependencies expanded for testing and routing

Review Change Stack

lhpaul and others added 4 commits May 17, 2026 09:02
api.ts wraps fetch with typed ApiResult<T> — ok/error discriminated union,
network errors caught and returned (never throw to caller). Imports Product
from @helm/shared and WorkflowStage from @helm/workflow; defines ItemState
and WorkflowEvent locally mirroring apps/api response shapes. 6 tests
covering success, HTTP error, and network error paths.
Adds vitest + jsdom + @testing-library/react test infrastructure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
usePolling<T>(fetcher, intervalMs) fetches immediately then polls via
setInterval; stable fetcherRef avoids re-creating the effect on re-renders;
intervalMs=null fetches once. Kanban renders dynamic columns from
product.workflow.stages_enabled, filters items per stage, shows relative
time and item count badges. Items error shown inline; product error shown
full-page. Polling every 5s with cleanup on unmount.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ItemDetail polls GET /api/items/:id every 5s, renders stage badge,
full transition history table (newest first), and back-to-board link.
Error state shown full-page. App.tsx replaces the WebSocket placeholder
with BrowserRouter + two routes: / → Kanban, /items/:id → ItemDetail.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
6 tests: initial loading state, re-fetch on interval tick, interval
cleanup on unmount, error on fetch failure, error cleared on recovery,
and intervalMs=null fetch-once behaviour. Uses vi.useFakeTimers() to
control setInterval without real time delays.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 17, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7e5b5ba3-4c7c-4d99-95b8-8de9e34c1b53

📥 Commits

Reviewing files that changed from the base of the PR and between 7e98ec8 and 8a04081.

📒 Files selected for processing (1)
  • apps/web/src/views/ItemDetail.tsx

📝 Walkthrough

Walkthrough

Replaces the WebSocket status UI with BrowserRouter routes and adds a typed API client, a reusable usePolling hook, Kanban and ItemDetail views that poll the API, plus Vitest/jsdom test setup and tests for the API client and polling hook.

Changes

Web UI with routing and polling

Layer / File(s) Summary
Testing infrastructure setup
apps/web/package.json, apps/web/vite.config.ts, apps/web/src/test-setup.ts
Vitest, jsdom, globals, setup file, and Testing Library / jest-dom packages and scripts are configured.
API client and tests
apps/web/src/lib/api.ts, apps/web/src/lib/api.test.ts
Adds WorkflowEvent, ItemState, ApiError, ApiResult<T>, fetchJson, and api methods (getProduct, listItems, getItem) with tests covering success, HTTP errors, network errors, and URL encoding.
Reusable polling hook
apps/web/src/hooks/usePolling.ts, apps/web/src/hooks/usePolling.test.ts
Adds usePolling<T> hook returning {data,error,loading} with immediate fetch, interval polling, overlap protection, unmount cleanup, and single-fetch mode; tests exercise initialization, repeated polling, unmount behavior, error handling and recovery, thrown errors, and null-interval mode.
App routing setup
apps/web/src/App.tsx
Replaces WebSocket-driven status UI with BrowserRouter/Routes; routes /Kanban, /items/:idItemDetail.
Kanban board view
apps/web/src/views/Kanban.tsx
Implements Kanban UI: relativeTime helper, ItemCard, KanbanColumn, polls product and items, groups items by currentStage, renders columns per enabled stage, and surfaces fetch errors.
Item detail view
apps/web/src/views/ItemDetail.tsx
Implements ItemDetail page that polls api.getItem(id), shows loading/error states, renders header with StageBadge, and displays reversed transition history table with HistoryRow entries.

Sequence Diagram

sequenceDiagram
  participant User
  participant App
  participant Kanban
  participant ItemDetail
  participant usePolling
  participant api
  participant Server

  User->>App: navigate to /
  App->>Kanban: render
  Kanban->>usePolling: call usePolling(api.getProduct)
  usePolling->>api: fetcher() product
  api->>Server: GET /api/product
  Server-->>api: Product JSON
  api-->>usePolling: ApiResult{data}
  usePolling-->>Kanban: {data, loading:false}
  Kanban->>usePolling: call usePolling(api.listItems)
  usePolling->>api: fetcher() items
  api->>Server: GET /api/items
  Server-->>api: ItemState[] JSON
  api-->>usePolling: ApiResult{data}
  usePolling-->>Kanban: {data, loading:false}
  Kanban-->>User: render board by stage

  Note over usePolling: after interval tick (5s)
  usePolling->>api: fetcher() items
  api->>Server: GET /api/items
  Server-->>api: ItemState[] JSON
  api-->>usePolling: ApiResult{data}
  usePolling-->>Kanban: {data, loading:false}
  Kanban-->>User: re-render board

  User->>App: click item card
  App->>ItemDetail: navigate /items/:id
  ItemDetail->>usePolling: call usePolling(api.getItem(id))
  usePolling->>api: fetcher() single item
  api->>Server: GET /api/items/{id}
  Server-->>api: ItemState JSON
  api-->>usePolling: ApiResult{data}
  usePolling-->>ItemDetail: {data, loading:false}
  ItemDetail-->>User: render item detail + history
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • lhpaul/helm#3: Backend product endpoint implementation referenced by the web client's api.getProduct() and related API shapes.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.67% 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 identifies the main feature: a read-only kanban dashboard with item detail views for the web app.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 feature/session-6-dashboard-kanban

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

Copy link
Copy Markdown

@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: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/vite.config.ts (1)

1-1: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Use Vitest-aware config typing for the test block.

Import defineConfig from vitest/config (or add Vitest config type reference) so test options are properly typed in tooling.

Suggested change
-import { defineConfig } from 'vite';
+import { defineConfig } from 'vitest/config';

Also applies to: 17-21

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/vite.config.ts` at line 1, The Vite config's test block lacks
Vitest-aware typing; replace or augment the current import of defineConfig from
'vite' so the config is typed for Vitest by importing defineConfig from
'vitest/config' (or add a Vitest config type reference) and ensure the existing
defineConfig(...) call that contains the test: { ... } block uses that
Vitest-aware defineConfig import so IDE/typecheck picks up Vitest options
correctly; update any other occurrences of defineConfig/imports accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/package.json`:
- Line 26: The dependency "`@types/react-router-dom`" listed in package.json
conflicts with the installed react-router-dom@7.15.1 which now includes built-in
TypeScript types; remove the "`@types/react-router-dom`" entry from package.json
(the exact string "`@types/react-router-dom`": "^5.3.3") and then run your package
manager install (npm/yarn/pnpm) to update node_modules and lockfile so
TypeScript uses the bundled types from react-router-dom@7.15.1.
- Around line 28-32: The package dependency mismatch is that
"`@vitest/coverage-v8`" is at major version 4 while "vitest" is at major version
2; update the dependency entry for "`@vitest/coverage-v8`" in package.json to a
v2-compatible version (e.g., "^2.0.0" or the latest 2.x) so both packages share
the same major version, then reinstall (npm/yarn/pnpm install) to update
lockfiles; target the "`@vitest/coverage-v8`" key to locate and change the
version.

In `@apps/web/src/hooks/usePolling.test.ts`:
- Around line 22-128: Add a new test to apps/web/src/hooks/usePolling.test.ts
that covers the rejection path of the fetcher: renderHook(() =>
usePolling(fetcher, 1_000)) with fetcher = vi.fn().mockRejectedValue(new
Error('boom')) (or mockRejectedValue('boom')), call act to let the initial fetch
run, then assert result.current.error equals the rejection message,
result.current.data is null, and result.current.loading is false; also advance
timers with vi.advanceTimersByTime(...) and assert no additional successful data
is populated (use fetcher call counts) to lock behavior. Ensure the test
references usePolling, fetcher, renderHook, act, vi.advanceTimersByTime and
checks result.current.error/loading/data to locate the relevant code.

In `@apps/web/src/hooks/usePolling.ts`:
- Around line 30-55: Wrap the body of doFetch with a try/catch/finally to catch
and log/handle any exceptions thrown by fetcherRef.current(), and introduce an
in-flight guard (e.g., local boolean like isFetching or a ref) to prevent
concurrent runs so subsequent setInterval ticks skip while a fetch is pending;
check the cancelled flag before writing state in both success and error paths
and only call setLoading(false) in finally when the fetch was the active one
(and not after cancellation). Update references to doFetch, fetcherRef,
cancelled, intervalMs, id, setData, setError, and setLoading accordingly so
thrown errors are handled and overlapping requests cannot produce
stale/out-of-order state writes.

In `@apps/web/src/lib/api.test.ts`:
- Around line 67-72: The test "URL-encodes the externalId" (using api.getItem
and mockFetch) incorrectly uses 'issue_1' which contains no characters that
require encoding; update the test to pass an externalId containing characters
that must be encoded (e.g., spaces, slashes, question/ampersand or non-ASCII)
and then assert that mockFetch was called with a URL containing
encodeURIComponent(externalId) (i.e., the percent-encoded form) rather than the
raw input, so the test fails if encoding is removed.

In `@apps/web/src/lib/api.ts`:
- Line 40: The BASE_URL initialization uses the nullish coalescing operator
only, which allows whitespace-only VITE_API_URL values to pass through; update
the assignment for BASE_URL to read the env var via (import.meta as { env?: {
VITE_API_URL?: string } }).env?.VITE_API_URL, apply .trim() and a truthiness
check, and fall back to '' when the trimmed value is empty; modify the BASE_URL
constant accordingly so any whitespace-only or empty env value becomes the
empty-string fallback.

In `@apps/web/src/views/ItemDetail.tsx`:
- Around line 34-35: The history table rows conditionally render a sixth cell
only when event.note exists, which breaks alignment with the five-column header;
update the row rendering in ItemDetail.tsx (the JSX that outputs {event.note &&
<td ...>{event.note}</td>}) to always output a note <td> (render event.note or
an empty/placeholder string) so the column structure remains consistent, and
apply the same change to the other occurrence of this pattern later in the file
(the second history-row rendering block around the other {event.note && ...}
usage).

In `@apps/web/src/views/Kanban.tsx`:
- Around line 80-82: The render repeatedly filters allItems per stage; instead
compute a single grouping once (e.g., groupedItems via allItems.reduce or a Map)
keyed by stage id (use the same key as stages from
product?.workflow.stages_enabled) and then use groupedItems.get(stageId) or
groupedItems[stageId] inside the column rendering (replace allItems.filter(...)
uses around the stages loop and the occurrences noted near lines 102-107).
Ensure groupedItems falls back to an empty array for missing keys and compute it
outside the stages.map so it is reused across renders.

---

Outside diff comments:
In `@apps/web/vite.config.ts`:
- Line 1: The Vite config's test block lacks Vitest-aware typing; replace or
augment the current import of defineConfig from 'vite' so the config is typed
for Vitest by importing defineConfig from 'vitest/config' (or add a Vitest
config type reference) and ensure the existing defineConfig(...) call that
contains the test: { ... } block uses that Vitest-aware defineConfig import so
IDE/typecheck picks up Vitest options correctly; update any other occurrences of
defineConfig/imports accordingly.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 20f8f7fb-8df1-4305-9adf-a854d3b523f6

📥 Commits

Reviewing files that changed from the base of the PR and between 7da59f6 and 35c3382.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (10)
  • apps/web/package.json
  • apps/web/src/App.tsx
  • apps/web/src/hooks/usePolling.test.ts
  • apps/web/src/hooks/usePolling.ts
  • apps/web/src/lib/api.test.ts
  • apps/web/src/lib/api.ts
  • apps/web/src/test-setup.ts
  • apps/web/src/views/ItemDetail.tsx
  • apps/web/src/views/Kanban.tsx
  • apps/web/vite.config.ts

Comment thread apps/web/package.json Outdated
Comment thread apps/web/package.json Outdated
Comment thread apps/web/src/hooks/usePolling.test.ts
Comment thread apps/web/src/hooks/usePolling.ts
Comment thread apps/web/src/lib/api.test.ts Outdated
Comment thread apps/web/src/lib/api.ts Outdated
Comment thread apps/web/src/views/ItemDetail.tsx Outdated
Comment thread apps/web/src/views/Kanban.tsx
1. Remove conflicting @types/react-router-dom@5 (RR v7 ships own types)
2. Align @vitest/coverage-v8 to ^2.1.9 (v4 requires Vite 6+)
3. Add rejection test for usePolling (fetcher throws vs returns {ok:false})
4. Harden usePolling: inFlight guard prevents overlapping requests;
   try-catch maps thrown errors to error state (never-throw parity)
5. URL-encoding test now uses 'issue/1 with space' to actually exercise
   encodeURIComponent
6. VITE_API_URL: trim() + truthiness check (empty-string-in-input category)
7. ItemDetail note column: always rendered with "—" fallback so thead/tbody
   column counts stay consistent
8. Kanban: useMemo groups items by stage once per render instead of
   running filter() once per stage per render

Addresses CodeRabbit review comments on PR #10.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@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: 3

♻️ Duplicate comments (1)
apps/web/src/hooks/usePolling.ts (1)

52-55: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard setLoading(false) with cancellation check in finally.

On Line 54, setLoading(false) runs even after cleanup sets cancelled = true. Keep cancellation behavior consistent across all state writes.

Proposed fix
       } finally {
         inFlight = false;
-        setLoading(false);
+        if (!cancelled) setLoading(false);
       }
#!/bin/bash
# Verify that setLoading(false) is currently unguarded in finally.
rg -n -C3 'finally|setLoading\(false\)|cancelled' apps/web/src/hooks/usePolling.ts
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/hooks/usePolling.ts` around lines 52 - 55, In the usePolling
hook's finally block, avoid calling setLoading(false) after cleanup has marked
cancelled=true; change the finally so that setLoading(false) is only called when
cancelled is false (e.g., if (!cancelled) setLoading(false)). Keep the
assignment inFlight = false as-is or adjust similarly if you want it guarded,
but at minimum wrap the setLoading(false) call with the cancelled check to
prevent state updates after cancellation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/views/ItemDetail.tsx`:
- Around line 82-83: The heading and the monospace subtext both render the same
prop item.externalId; update the <h1> in ItemDetail.tsx to show the
human-friendly field (e.g., item.name, item.title, or item.displayName) instead
of item.externalId, leaving item.externalId only in the monospace <p> for the
technical identifier, or if no human-friendly field exists, remove the duplicate
element so the identifier is only shown once.

In `@apps/web/src/views/Kanban.tsx`:
- Around line 29-30: The UI currently renders item.externalId twice (first <p>
with className "truncate text-sm font-medium text-gray-900" and second <p> with
"mt-0.5 line-clamp-2 text-xs text-gray-500"), which is redundant; update the
second element in the Kanban view to show the appropriate descriptive field
(e.g., item.description or item.name) instead of item.externalId, or remove the
second <p> entirely if no descriptive field exists — keep the first <p> as the
primary ID display and ensure the "line-clamp-2" element reflects multi-line
descriptive content when present.
- Around line 10-19: The relativeTime helper currently computes diff using new
Date(iso).getTime() which yields NaN for malformed ISO strings; update
relativeTime to validate the parsed timestamp (e.g., const ts = new
Date(iso).getTime(); if (isNaN(ts)) return a safe fallback like 'unknown' or
'invalid date') before computing diff, and also handle negative diffs (future
dates) if needed; modify the function body around the new Date(iso).getTime()
usage to perform this check and return the fallback early.

---

Duplicate comments:
In `@apps/web/src/hooks/usePolling.ts`:
- Around line 52-55: In the usePolling hook's finally block, avoid calling
setLoading(false) after cleanup has marked cancelled=true; change the finally so
that setLoading(false) is only called when cancelled is false (e.g., if
(!cancelled) setLoading(false)). Keep the assignment inFlight = false as-is or
adjust similarly if you want it guarded, but at minimum wrap the
setLoading(false) call with the cancelled check to prevent state updates after
cancellation.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 08dbb08b-4784-4a79-8fd5-074849d65a42

📥 Commits

Reviewing files that changed from the base of the PR and between 35c3382 and aee8561.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • apps/web/package.json
  • apps/web/src/hooks/usePolling.test.ts
  • apps/web/src/hooks/usePolling.ts
  • apps/web/src/lib/api.test.ts
  • apps/web/src/lib/api.ts
  • apps/web/src/views/ItemDetail.tsx
  • apps/web/src/views/Kanban.tsx

Comment thread apps/web/src/views/ItemDetail.tsx Outdated
Comment thread apps/web/src/views/Kanban.tsx
Comment thread apps/web/src/views/Kanban.tsx Outdated
1. ItemDetail: externalId was shown twice; replace redundant <p> with
   productSlug as contextual subtitle (ItemState has no title field)
2. Kanban ItemCard: externalId was shown twice; replace second line with
   currentStage so the card conveys two distinct pieces of information
3. relativeTime: guard against NaN from malformed ISO strings with
   isNaN(date.getTime()) → return 'unknown'

Addresses CodeRabbit review comments on PR #10.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/views/ItemDetail.tsx`:
- Around line 7-12: The formatDate function can produce "Invalid Date" when
given malformed ISO strings; update formatDate to parse the input into a Date,
check validity via isNaN(date.getTime()) (or Number.isNaN), and return a safe
fallback (e.g., empty string or hyphen) when invalid, otherwise return
date.toLocaleString(...) as before; reference the formatDate function to locate
the change and ensure the fallback is used whenever the Date is invalid.
- Around line 105-107: The current key for HistoryRow uses
`${event.at}-${event.toStage}` and falls back to the unstable array index from
the reversed array; to fix it, create a stable, collision-resistant key: if each
event has a unique id (e.g., event.id) use that as the key in HistoryRow;
otherwise attach the original index before reversing (e.g., item.history.map((e,
idx) => ({ e, idx })).reverse().map(({ e, idx }) => <HistoryRow
key={`${e.at}-${e.toStage}-${idx}`} ... />)) so keys remain stable across
renders and do not depend on the reversed array index.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: beb62aca-7f0a-43b9-bfcb-792b856c719d

📥 Commits

Reviewing files that changed from the base of the PR and between aee8561 and 7ac157e.

📒 Files selected for processing (2)
  • apps/web/src/views/ItemDetail.tsx
  • apps/web/src/views/Kanban.tsx

Comment thread apps/web/src/views/ItemDetail.tsx
Comment thread apps/web/src/views/ItemDetail.tsx
1. formatDate: guard against malformed ISO strings with Number.isNaN
   check — same defensive pattern applied to relativeTime in round 2
2. History row key: replace at+toStage with at+fromStage+toStage+
   triggeredBy to avoid collisions when two events share the same
   timestamp and destination stage

Addresses CodeRabbit review comments on PR #10.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/views/ItemDetail.tsx`:
- Around line 57-68: The current ItemDetail render returns the full-page error
UI whenever error is truthy, which replaces previously loaded content on
transient poll failures; change the condition to only render the full-page error
when error is present AND item is falsy (i.e., if (error && !item) ...) so that
existing item data remains visible on transient errors, and instead render an
inline warning when error && item; apply the same pattern to the other error
branches in this file (the similar checks around the other error render blocks)
and update any JSX that currently shows the full-page error to use the inline
warning component when item exists.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 01f94bb1-9e0c-4943-8304-bb216b91b14f

📥 Commits

Reviewing files that changed from the base of the PR and between 7ac157e and 7e98ec8.

📒 Files selected for processing (1)
  • apps/web/src/views/ItemDetail.tsx

Comment thread apps/web/src/views/ItemDetail.tsx Outdated
…etail

Full-page error replaced content even when prior data was available, making
brief network hiccups look like hard outages. Now: full-page error only on
first-load failure (no data yet); inline amber banner when polling fails
but last-known data is present. Matches the pattern already used in Kanban
(itemsError shown inline without hiding the board).

Addresses CodeRabbit review comment on PR #10.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/views/ItemDetail.tsx`:
- Around line 46-47: The polling starts even when the route param id is missing
because fetcher falls back to '' and triggers failing requests; update the
fetcher and polling usage in ItemDetail (the fetcher created via useCallback and
the usePolling call) to short-circuit when id is falsy: return a noop/undefined
result or disable polling by passing null/false/etc. to usePolling (or skip
calling it) when id is not present, so requests to api.getItem are only made
once a valid id exists. Ensure you reference the fetcher (useCallback(() =>
api.getItem(...))) and the usePolling(...) invocation when implementing the
guard.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: fd2f6370-7b13-4557-b1a9-9e8bc999dd53

📥 Commits

Reviewing files that changed from the base of the PR and between 7e98ec8 and 2f5ef5e.

📒 Files selected for processing (1)
  • apps/web/src/views/ItemDetail.tsx

Comment thread apps/web/src/views/ItemDetail.tsx Outdated
id ?? '' triggered repeated GET /api/items/ failures when the route
param was absent. Now: returns {ok:false} immediately if id is falsy
and disables the polling interval (intervalMs=null) for the same case,
so no network requests are made until a valid id is present.

Addresses CodeRabbit review comment on PR #10.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@lhpaul
Copy link
Copy Markdown
Owner Author

lhpaul commented May 17, 2026

@coderabbitai resume

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 17, 2026

✅ Actions performed

Reviews resumed.

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.

1 participant