feat(dashboard): add Timeline Status Cards (#474)#712
Conversation
Add 4 new dashboard cards for timeline visualization: - UpcomingMilestonesCard: displays next 5 incomplete milestones with health indicators - WorkItemProgressCard: SVG donut chart showing work item counts by status - AtRiskItemsCard: lists up to 5 overdue or late-start items with links - CriticalPathCard: shows critical path summary with deadline and health color All components follow existing patterns with CSS modules, design tokens, and comprehensive data-testid attributes for testing. Fixes #474 Co-Authored-By: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com>
…rsing Add 40 unit tests across 4 test files for the timeline status card components. Fix CriticalPathCard date parsing to use correct Date constructor (year, month-1, day) instead of buggy string manipulation. Fix health threshold boundary to use <= 14 for warning range (7-14 days) per acceptance criteria. Fixes #474 Co-Authored-By: Claude Haiku <frontend-developer> <noreply@anthropic.com> Co-Authored-By: Claude Sonnet <qa-integration-tester> <noreply@anthropic.com> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add missing `jest` import from @jest/globals in CriticalPathCard.test.tsx - Replace hardcoded color: 'white' with CSS Module badge classes - Use badgeGreen/badgeRed/badgeYellow CSS classes instead of inline backgroundColor for health indicator (dark mode safe) - Fix link focus-visible to use box-shadow: var(--shadow-focus) - Use var(--spacing-2) token for legend dot dimensions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
steilerDev
left a comment
There was a problem hiding this comment.
[security-engineer]
PR #712 — Timeline Status Cards (Story #474): Security Review
Summary
Reviewed all five new components (, , , , ) and the updated . No security findings.
Checklist
- XSS vectors: No
dangerouslySetInnerHTML,innerHTML,eval(), ordocument.writein any of the new components or inDashboardPage.tsx. All user-sourced data (titles, dates, counts) is rendered as JSX text nodes — React escapes these automatically. - Link injection:
AtRiskItemsCarduses React Router<Link to={/work-items/${item.id}}>.item.idflows exclusively from theTimelineResponseAPI payload (GET /api/timeline→TimelineWorkItem.id: string) — not from any user-controlled input. React Router<Link>renders an<a href>into a same-origin SPA path; no open redirect orjavascript:URI risk. - SVG injection:
WorkItemProgressCardrenders a donut chart SVG. All dynamic values (strokeDasharray,strokeDashoffset,stroke, center{total}) are derived exclusively from arithmetic on API-sourced integers and hardcoded CSS variable strings. No user-controlled string is interpolated into SVG attributes. Thearia-labelattribute containing counts is a React JSX attribute — React HTML-encodes it automatically. - Prototype pollution / injection in data processing: Filtering and sorting logic in all four cards uses standard array methods (
.filter,.sort,.localeCompare,.includes) operating on typedTimelineWorkItemandTimelineMilestoneobjects. No use ofJSON.parse, dynamic property access via user strings, orObject.assignpatterns that could introduce prototype pollution. - No new API surface or auth changes: The PR adds no new routes, no new backend code, and no auth-related changes. Timeline data continues to flow through the existing authenticated
GET /api/timelineendpoint viagetTimeline()intimelineApi.ts. - Sensitive data exposure: No tokens, credentials, or PII are introduced. Error messages on timeline fetch failure use the established
ApiClientErrorpattern — server error details are not re-exposed in the UI beyond the existing pattern already reviewed in prior PRs. - CORS / hardcoded credentials: None introduced.
- New dependencies: None added.
Verdict
No security issues found. The PR is consistent with the established safe rendering patterns used across the codebase.
steilerDev
left a comment
There was a problem hiding this comment.
[security-engineer]
PR #712 — Timeline Status Cards (Story #474): Security Review
Summary
Reviewed all five new components (TimelineStatusCards.tsx, UpcomingMilestonesCard.tsx, WorkItemProgressCard.tsx, AtRiskItemsCard.tsx, CriticalPathCard.tsx) and the updated DashboardPage.tsx. No security findings.
Checklist
- XSS vectors: No dangerouslySetInnerHTML, innerHTML, eval(), or document.write in any of the new components or in DashboardPage.tsx. All user-sourced data (titles, dates, counts) is rendered as JSX text nodes — React escapes these automatically.
- Link injection: AtRiskItemsCard uses React Router
<Link to={'/work-items/${item.id}'}>. item.id flows exclusively from the TimelineResponse API payload (GET /api/timeline → TimelineWorkItem.id: string) — not from any user-controlled input. React Router renders an anchor into a same-origin SPA path; no open redirect or javascript: URI risk. - SVG injection: WorkItemProgressCard renders a donut chart SVG. All dynamic values (strokeDasharray, strokeDashoffset, stroke, center total) are derived exclusively from arithmetic on API-sourced integers and hardcoded CSS variable strings. No user-controlled string is interpolated into SVG attributes. The aria-label attribute containing counts is a React JSX attribute — React HTML-encodes it automatically.
- Prototype pollution / injection in data processing: Filtering and sorting logic in all four cards uses standard array methods (.filter, .sort, .localeCompare, .includes) operating on typed TimelineWorkItem and TimelineMilestone objects. No use of JSON.parse, dynamic property access via user strings, or Object.assign patterns that could introduce prototype pollution.
- No new API surface or auth changes: The PR adds no new routes, no new backend code, and no auth-related changes. Timeline data continues to flow through the existing authenticated GET /api/timeline endpoint via getTimeline() in timelineApi.ts.
- Sensitive data exposure: No tokens, credentials, or PII are introduced. Error messages on timeline fetch failure use the established ApiClientError pattern — server error details are not re-exposed in the UI beyond the existing pattern already reviewed in prior PRs.
- CORS / hardcoded credentials: None introduced.
- New dependencies: None added.
Verdict
No security issues found. The PR is consistent with the established safe rendering patterns used across the codebase.
steilerDev
left a comment
There was a problem hiding this comment.
[product-architect]
Architecture Review: Timeline Status Cards (#474)
Verified
- Shared types: All components correctly import
TimelineResponse,TimelineWorkItem,TimelineMilestonefrom@cornerstone/sharedusingtypeimports - No new API calls: Data flows from the existing
getTimeline()call inDashboardPage, stored intimelineDatastate, and passed toTimelineStatusCardsvia props -- no additional fetches - No shared type or schema changes: The existing
TimelineResponseshape provides all necessary fields (workItems,milestones,criticalPath) - SVG donut chart: Pure SVG implementation with correct
role="img"and descriptivearia-label-- no external charting library dependency - CSS Modules: The shared stylesheet uses design tokens correctly (semantic Layer 2 tokens, no hardcoded hex values)
DashboardCardId: The'timeline-status'card ID already exists in the shared type -- no type changes needed- Component architecture: Clean decomposition into 4 focused sub-components with a thin wrapper
- Test coverage: 40 tests across 4 files covering empty states, sorting, health indicators, SVG rendering, and aria-labels. Good use of
jest.useFakeTimers()for deterministic date tests and distant past/future dates to avoid flakiness
Minor Notes (non-blocking)
-
CriticalPathCard inline styles (LOW): Unlike the other three cards which use CSS Module classes,
CriticalPathCard.tsxuses extensive inlinestyleattributes for layout, typography, and colors. This is a consistency deviation from the CSS Modules convention but is functionally correct. Consider extracting to CSS Module classes in a future polish pass. -
Hardcoded
color: 'white'(LOW): The health badge inCriticalPathCard.tsxsetscolor: 'white'inline. In dark mode, white text on a potentially lighter badge background could have contrast issues. A semantic token likevar(--color-primary-text)would be more resilient. Non-blocking since the badge backgrounds (danger/warning/success) are strong enough colors for white text in both modes.
Overall this is a clean, well-structured addition that correctly reuses existing data and follows established patterns. Architecture approved.
steilerDev
left a comment
There was a problem hiding this comment.
[product-owner]
PR #712 Review — Story #474: Timeline Status Cards
Acceptance Criteria Verification
| AC | Description | Verdict | Notes |
|---|---|---|---|
| 1 | Upcoming Milestones card: next 5 sorted by target date, on-time/delayed indicators | PASS | Filters completed, sorts by targetDate ascending, slices to 5. On Track/Delayed badges rendered. |
| 2 | Milestone delay: projected completion date vs target date | PASS | Compares projectedDate against targetDate. Null projectedDate defaults to "On Track". |
| 3 | Donut chart: work item counts by status (3 statuses per override) | PASS | Groups into not_started, in_progress, completed. No blocked status (correct per user override removing 4th status). |
| 4 | Design system color tokens, light/dark mode | PASS | Uses var(--color-text-muted), var(--color-primary), var(--color-success), var(--color-border), var(--color-warning), var(--color-danger) throughout. CSS Module classes use design tokens. Dark mode handled via CSS custom properties. |
| 5 | At-Risk Items: up to 5 overdue/late-start items with detail links | PASS | in_progress + past endDate = "Overdue"; not_started + past startDate = "Late Start". Sorted by date ascending (most overdue first), capped at 5. Each title is a <Link to={/work-items/${id}}>. |
| 6 | Critical Path: total items, next deadline, days remaining | PASS | Shows critical-count (total on path), critical-deadline (formatted via formatDate), critical-days (computed diff). |
| 7 | Critical Path health: green >14, yellow 7-14, red <7/overdue | PASS | Thresholds: < 0 = Overdue (red), < 7 = Critical (red), <= 14 = Warning (yellow), > 14 = On Track (green). Matches AC exactly. |
| 8 | Chart library override: pure SVG donut | PASS | No library dependency added. Pure SVG with <circle> elements, strokeDasharray/strokeDashoffset for segments. |
| 9 | Donut aria-label describes status breakdown | PASS | aria-label="Work item progress: ${completed} completed, ${in_progress} in progress, ${not_started} not started" on SVG with role="img". |
Non-Blocking Observations
-
Test authorship (medium): Both commits are authored by
product-architect. Per CLAUDE.md,qa-integration-testershould own unit and integration tests. The 40 tests in this PR were written by the product-architect agent. This is a process deviation -- flag for the team to ensure qa-integration-tester handles test writing in future stories. -
Inline styles in CriticalPathCard (low): CriticalPathCard uses extensive inline
style={{...}}attributes for layout and typography, while the other three cards use CSS Module classes consistently. Consider migrating to CSS Module classes for consistency in a refinement pass. -
CI still pending: Test shards and Docker build are still in progress at time of review. Approval is conditional on CI passing.
Verdict
All 9 acceptance criteria are met. The implementation correctly handles the user-specified overrides (3 statuses instead of 4 in donut chart, pure SVG instead of chart library). At-risk logic correctly uses in_progress+overdue and not_started+late_start definitions without referencing a nonexistent "blocked" status. Critical path health thresholds match the AC exactly.
Decision: APPROVE (conditional on CI green)
steilerDev
left a comment
There was a problem hiding this comment.
[ux-designer]
Design system compliance review for PR #712 — Timeline Status Cards (story #474).
I read wiki/Style-Guide.md, client/src/styles/tokens.css, client/src/styles/shared.module.css, and all five component files before writing this review.
Summary
The CSS module (TimelineStatusCards.module.css) is well-written and almost fully token-compliant. However I am requesting changes due to several issues in the TSX files: inline styles that should live in the CSS module, a hardcoded color: 'white' value that breaks dark mode, a missing badgeYellow variant for the Warning state, and a focus-visible pattern that uses outline instead of box-shadow: var(--shadow-focus).
Findings
Critical / High
1. Hardcoded color: 'white' in CriticalPathCard.tsx (line 112) — breaks dark mode
File: client/src/components/TimelineStatusCards/CriticalPathCard.tsx, line 109–119
The inline-style health badge uses color: 'white' as a hardcoded string. This is a literal hex-equivalent that is invisible to the dark mode token system.
// WRONG — hardcoded string
style={{
backgroundColor: healthColor,
color: 'white', // ← hardcoded
...
}}The badge uses --color-danger or --color-success as its background. The correct text token for text rendered on those filled surfaces is var(--color-danger-text) (which in dark mode resolves to #111827 — dark text on the brighter red) and var(--color-success-text) (white in light mode, white in dark mode). Because both danger and success text are already white in light mode and the dark mode overrides handle the switch, the correct approach is:
// CORRECT — use token
color: 'var(--color-danger-text)', // for danger/critical states
color: 'var(--color-success-text)', // for on-track stateSince the badge alternates between danger, warning, and success backgrounds, the cleanest solution is to move this health badge to a CSS Module class set (.badgeHealthGreen, .badgeHealthYellow, .badgeHealthRed) and reference the appropriate text token per variant — which also resolves finding #2 below.
Severity: High — hardcoded color that cannot adapt to dark mode.
2. Missing yellow/warning badge variant — --color-warning used as backgroundColor with no text token
File: client/src/components/TimelineStatusCards/CriticalPathCard.tsx, lines 71–72
healthColor = 'var(--color-warning)'; // yellow 7-14 days--color-warning (orange-400 in light mode, orange-300 in dark mode) is used as a backgroundColor inline style. There is no accompanying color token that is guaranteed to pass contrast on this background. The amber/orange surface needs dark text for readability: var(--color-text-primary) (dark gray in light mode) or a dedicated --color-warning-text token.
Additionally, neither the CSS module nor the token file defines a badge token family for "warning" surfaces analogous to --color-danger-bg / --color-danger-text-on-light. Using only --color-warning (the full-saturation orange) as a badge background will not pass WCAG AA contrast for any text color — the orange is a mid-tone that sits between light and dark.
Required fix: Either:
- Add a
.badgeYellow(or.badgeOrange) CSS Module class using the--color-hi-status-scheduled-bg/--color-hi-status-scheduled-texttoken pair (amber-100 / amber-800 in light mode; already has dark mode overrides), which is the established pattern for amber status surfaces in this design system, or - Use the
--color-status-not-started-bg/--color-status-not-started-textneutral pair as a "Warning" approximation.
The --color-hi-status-scheduled-* family is the correct choice:
/* TimelineStatusCards.module.css */
.badgeYellow {
background-color: var(--color-hi-status-scheduled-bg);
color: var(--color-hi-status-scheduled-text);
}Severity: High — contrast failure risk + no dark mode coverage for the Warning state.
Medium
3. Focus-visible on .link uses outline instead of box-shadow: var(--shadow-focus)
File: client/src/components/TimelineStatusCards/TimelineStatusCards.module.css, lines 83–86
/* WRONG — outline instead of shadow-focus token */
.link:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
}The established pattern across the entire design system is:
/* CORRECT */
.link:focus-visible {
outline: none;
box-shadow: var(--shadow-focus);
}--shadow-focus (0 0 0 3px rgba(59,130,246,0.3)) automatically switches to the darker-mode value (0 0 0 3px rgba(96,165,250,0.4)) via the Layer 3 dark mode override. The outline: 2px solid var(--color-primary) approach does adapt the color via token, but it misses the soft glow (alpha-channel ring) that the design system uses consistently, and it does not match the focus style used on links in LinkedDocumentsSection, GanttTooltip, and everywhere else in the application.
This is a recurring issue flagged in previous PRs (#402, #414); please fix before merging.
Severity: Medium — design consistency and partial dark mode mismatch.
4. Inline styles in CriticalPathCard.tsx should be CSS Module classes
File: client/src/components/TimelineStatusCards/CriticalPathCard.tsx, lines 79–121
CriticalPathCard uses inline style objects for all layout and typography:
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-3)' }}>
<span style={{ color: 'var(--color-text-muted)', fontSize: 'var(--font-size-xs)' }}>
<div style={{ fontSize: 'var(--font-size-2xl)', fontWeight: 'var(--font-weight-bold)' }}>While inline var() references do resolve to tokens and will switch in dark mode, inline styles:
- Cannot be overridden via CSS specificity
- Cannot use pseudo-classes (
:focus-visible,:hover,@media) - Are not inspectable by the CSS Modules toolchain
- Violate the project's ADR-006 (CSS Modules) convention
Move these to named classes in TimelineStatusCards.module.css. Suggested additions:
.statLabel {
font-size: var(--font-size-xs);
color: var(--color-text-muted);
}
.statValue {
font-size: var(--font-size-2xl);
font-weight: var(--font-weight-bold);
color: var(--color-text-primary);
}
.statValueSm {
font-size: var(--font-size-sm);
color: var(--color-text-primary);
}
.statValueMd {
font-size: var(--font-size-lg);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
}
.statRow {
display: flex;
align-items: center;
justify-content: space-between;
}Severity: Medium — violates CSS Modules convention; inline styles block pseudo-class styling and media queries.
5. Inline styles in UpcomingMilestonesCard.tsx should be CSS Module classes
File: client/src/components/TimelineStatusCards/UpcomingMilestonesCard.tsx, lines 43–44
<div style={{ display: 'flex', gap: 'var(--spacing-2)' }}>
<span style={{ color: 'var(--color-text-muted)' }}>Same issue as finding #4. Move to named CSS Module classes. The date span in particular needs a class because it communicates secondary information and may need responsive adjustment at the --breakpoint-sm breakpoint. Suggested:
.milestoneDate {
color: var(--color-text-muted);
font-size: var(--font-size-sm);
}
.milestoneMeta {
display: flex;
gap: var(--spacing-2);
align-items: center;
flex-shrink: 0;
}Severity: Medium — same CSS Modules violation; minor in isolation but accumulates across components.
Low
6. SVG donut center text uses hardcoded fontSize="24" and fontWeight="600"
File: client/src/components/TimelineStatusCards/WorkItemProgressCard.tsx, lines 115–126
SVG <text> attributes do not accept CSS var() references directly as attribute values in the same way CSS properties do, so the implementation here uses SVG presentation attributes:
<text
fontSize="24"
fontWeight="600"
fill="var(--color-text-primary)"fill="var(--color-text-primary)" is correct and will resolve in dark mode because SVG presentation attributes fall into the CSS cascade. However fontSize="24" is a hardcoded pixel value with no token equivalent. The nearest token is --font-size-2xl (24px / 1.5rem). The correct pattern for SVG text is to use the style attribute with CSS property syntax:
<text
style={{ fontSize: 'var(--font-size-2xl)', fontWeight: 'var(--font-weight-semibold)' }}
fill="var(--color-text-primary)"Note: fontWeight="600" maps to --font-weight-semibold (600). Using the token ensures the value tracks any future scale change.
Severity: Low — token deviation; does not break dark mode since it is a size not a color.
7. legendDot uses hardcoded width: 8px; height: 8px
File: client/src/components/TimelineStatusCards/TimelineStatusCards.module.css, lines 104–109
.legendDot {
width: 8px;
height: 8px;
...
}8px = --spacing-2. Use the spacing token:
.legendDot {
width: var(--spacing-2);
height: var(--spacing-2);
border-radius: var(--radius-circle);
flex-shrink: 0;
}Severity: Low — hardcoded size; no dark mode impact but inconsistent with token discipline.
8. letter-spacing: 0.5px on .sectionHeader is a hardcoded value
File: client/src/components/TimelineStatusCards/TimelineStatusCards.module.css, line 24
letter-spacing: 0.5px;There is no letter-spacing token in the design system. This value is acceptable as a one-off if no token covers it, but it should be documented as a deliberate deviation. Flag as informational for the style guide.
Severity: Low — no token exists; accepted as a one-off; recommend adding a comment.
Informational
9. Touch target for .link elements in list rows
File: AtRiskItemsCard.tsx — list row links
The <Link> elements in at-risk list items are inline text links with no enforced minimum height. On touch devices these will be smaller than the 44px minimum recommended by the Touch Target Sizes pattern in the style guide. Consider adding to the CSS module:
@media (max-width: 1024px) {
.listItem {
min-height: 44px;
}
}This ensures the entire row (which contains the link) meets the touch target requirement.
Severity: Informational — the touch target is the full listItem row, not just the link text, so this depends on whether the li height naturally reaches 44px with the current font-size and padding.
10. badgeGreen uses --color-success-bg (very light) — consider --color-success-badge-bg
File: TimelineStatusCards.module.css, line 57
.badgeGreen {
background-color: var(--color-success-bg); /* #f0fdf4 — very light wash */
color: var(--color-success-text-on-light);
}--color-success-bg is the lightest success surface (success banner background). For a badge/pill, the established pattern uses --color-success-badge-bg (#d1fae5) which has better contrast visibility. Compare: the completed status badge uses --color-status-completed-bg which resolves to --color-green-100 = #d1fae5. Consider aligning:
.badgeGreen {
background-color: var(--color-success-badge-bg);
color: var(--color-success-badge-text);
}This also makes the On Track badge visually consistent with the existing Completed status badge.
Severity: Informational — functional but slightly inconsistent with the status badge token family.
Checklist Summary
| Area | Result |
|---|---|
| Token adherence (CSS module) | PASS — no hardcoded hex values; spacing, font-size, radius all tokenized |
| Token adherence (TSX files) | FAIL — hardcoded color: 'white' in CriticalPathCard; hardcoded fontSize="24" in SVG |
| Dark mode | FAIL — color: 'white' is opaque string; Warning badge uses --color-warning as bg with no text token |
| Focus-visible | FAIL — .link:focus-visible uses outline instead of box-shadow: var(--shadow-focus) |
| Responsive (767px breakpoint) | PASS — grid collapses to 1-column correctly |
| SVG donut chart tokens | PARTIAL — fill uses tokens correctly; fontSize/fontWeight are hardcoded attributes |
| Inline styles vs CSS Modules | FAIL — CriticalPathCard and UpcomingMilestonesCard have inline style objects |
| Accessibility (SVG aria-label) | PASS — role="img" + descriptive aria-label on SVG |
| Accessibility (section headers) | PASS — <h3> used for each card section |
| Touch targets | INFORMATIONAL — row height likely adequate; verify at 44px |
|
🎉 This PR is included in version 1.15.0-beta.6 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
* feat(dashboard): implement dashboard layout & data shell (story #471) - Create DashboardCard reusable component with loading, error, and empty states - Add shimmer animation for skeleton loader and proper touch targets (44px min) - Create DashboardPage with 8 dashboard cards in responsive grid - Implement parallel data fetching via Promise.allSettled for all data sources - Add card visibility preference management via usePreferences hook - Implement customize dropdown to show/hide cards and manage preferences - Add per-card error handling with retry button and empty state handling - Map data sources (budgetOverview, budgetSources, timeline, invoices, subsidyPrograms) to cards - Responsive grid: 3 columns (desktop), 2 columns (tablet), 1 column (mobile) - All colors and spacing use CSS custom properties from tokens.css - Placeholder content "Content coming soon." for all cards (filled by subsequent stories) Co-Authored-By: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com> * test(dashboard): add unit and integration tests for DashboardCard and DashboardPage (#471) 12 unit tests for DashboardCard covering all render states (loading, error, empty, children), priority ordering (loading > error > empty), dismiss/retry interactions, and prop defaults. 22 integration tests for DashboardPage covering: h1/nav rendering, all 8 card titles visible post-load, per-data-source loading skeletons, ApiClientError propagation, empty states for source-utilization and subsidy-pipeline, dismiss → upsert flow (including multi-card accumulation), hidden-card suppression, Customize button visibility, dropdown open/close, and re-enable → upsert. Also flags Bug #712: DashboardPage.tsx:200 reads `.items` on InvoiceListPaginatedResponse but the actual field is `.invoices`, preventing the invoice-pipeline empty state from ever firing. Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com> * fix(dashboard): use correct invoice field name in empty check The InvoiceListPaginatedResponse uses `.invoices` not `.items`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(test): correct mock type for upsertPreference in dashboard tests The jest.fn generic must accept (key, value) arguments to match usage. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(dashboard): use 'Project' heading for consistency and fix test timing DashboardPage h1 changed from 'Dashboard' to 'Project' to match all other pages in the Project section. Wrapped empty-state assertions in waitFor to handle async data loading timing. Removed stale Bug #712 note. Fixes #471 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(dashboard): address PO and UX review findings - Use <article> + <h2> for semantic card structure with aria-labelledby - Add card-specific empty messages with contextual action links - Fix grid gap values, max-width, and breakpoint boundaries per spec - Use btnSecondaryCompact for retry button via CSS composition - Fix shimmer gradient tokens and add prefers-reduced-motion guard - Ensure 44px minimum touch targets at all touch breakpoints - Use var(--shadow-focus) consistently for focus-visible states Fixes #471 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Co-Authored-By: Claude frontend-developer (Haiku) <noreply@anthropic.com> * fix(dashboard): close article element correctly in DashboardCard Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(dashboard): update tests for new aria-labels and page structure - DashboardPage.test.tsx: use regex for skeleton aria-label query - E2E DashboardPage POM: replace description locator with cardGrid - E2E stub-pages.spec.ts: check cardGrid visibility instead of description Fixes #471 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude product-architect (Opus 4.6) <noreply@anthropic.com>
|
🎉 This PR is included in version 1.15.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Summary
getTimeline()fetchFixes #474
Test plan
Co-Authored-By: Claude Opus 4.6 noreply@anthropic.com