feat(dashboard): implement Quick Actions card with navigation links#714
feat(dashboard): implement Quick Actions card with navigation links#714steilerDev merged 4 commits intobetafrom
Conversation
8 tests covering all link hrefs, aria-labels, link count, and absence of loading/error states for story #477. Co-Authored-By: Claude qa-integration-tester (Sonnet 4.5) <noreply@anthropic.com>
Adds a new Quick Actions section to the project dashboard that provides quick access to common application features. Includes a prominent "New Work Item" button and quick navigation links to Work Items, Timeline, Budget, Invoices, and Vendors. All elements are keyboard accessible with appropriate aria-labels. Story #477 acceptance criteria: - New Work Item button navigates to /project/work-items/new - Quick links to all key sections (Work Items, Timeline, Budget, Invoices, Vendors) - Visible on all viewports (desktop, tablet, mobile) - Keyboard accessible with aria-label attributes - Dark mode support with design tokens Co-Authored-By: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com>
- Use transition tokens (--transition-button, --transition-button-border) instead of hardcoded 0.15s ease - Use --color-bg-hover instead of --color-bg-tertiary for link hover - Add prefers-reduced-motion guard to disable transitions - Add min-height: 44px for touch target compliance - Align aria-labels with visible text to avoid screen reader confusion Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Co-Authored-By: Claude frontend-developer (Haiku 4.5) <noreply@anthropic.com> Co-Authored-By: Claude qa-integration-tester (Haiku 4.5) <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]
Security review of PR #714 — Story #477 QuickActionsCard.
Verdict: No security findings. Safe to merge.
Checklist
- No SQL/command/XSS injection vectors — zero dynamic content; all link targets are hardcoded string literals
- Authentication/authorization — frontend-only component; no new API endpoints introduced; existing dashboard auth gate applies
- No sensitive data in logs, errors, or client responses — component is stateless and props-free
- User input — none; component accepts no props and renders no user-supplied data
- No new dependencies
- No hardcoded credentials or secrets
- CORS configuration unaffected
- Error responses — none; no data fetching in this component
Security Analysis
XSS / Link injection: All six Link to="..." targets are hardcoded string literals (/project/work-items/new, /project/work-items, /schedule, /budget/overview, /budget/invoices, /budget/vendors). No user-controlled input flows into any to prop or rendered text. React Router's Link component generates internal anchor hrefs via the router — no javascript: URI risk. No dangerouslySetInnerHTML, innerHTML, or eval usage anywhere in the component.
Open redirect: Not applicable — React Router Link with a path-only string routes internally; no external URL construction.
DashboardPage integration: The card.id === 'quick-actions' branch unconditionally renders QuickActionsCard with no props — no data-dependent rendering path. The emptyAction.href informational finding from PR #709 is not triggered here (no emptyAction prop is passed for this card).
CSS: Pure CSS Modules, no dynamic class construction from user data.
steilerDev
left a comment
There was a problem hiding this comment.
[product-architect] Reviewed -- no issues found. Would approve if not own PR.
Architecture compliance: Component follows established patterns — CSS Modules, design tokens, React Router Link, named export with default re-export. File naming (PascalCase for React components) and directory structure are correct.
Route correctness: All 6 routes verified against App.tsx:
/project/work-items/new(line 107)/project/work-items(line 106)/schedule(line 135)/budget/overview,/budget/invoices,/budget/vendors(budget route group, line 119+)
Design token usage: CSS uses only design tokens (--color-primary, --color-primary-hover, --color-primary-text, --color-bg-secondary, --color-bg-tertiary, --color-border, --color-text-primary, --spacing-*, --radius-md, --font-size-sm, --font-weight-medium, --shadow-focus). No hardcoded colors or spacing values.
Integration: DashboardPage integration is minimal and correct — quick-actions card has no data dependency (no loading/error state needed), consistent with the card config at line 72 having no dataSource property.
Test coverage: 8 tests covering all links, route correctness, keyboard accessibility (link count assertion), and absence of loading/error states. Adequate for a static navigation component.
No schema or API contract changes needed (frontend-only).
steilerDev
left a comment
There was a problem hiding this comment.
[product-owner] PR #714 reviewed — APPROVED (submitted as comment due to same-account constraint)
Acceptance Criteria Verification
| AC | Criterion | Verdict |
|---|---|---|
| 1 | "New Work Item" button navigates to /work-items/new |
PASS — Link to="/project/work-items/new" uses the correct actual route (AC text says /work-items/new but the real app route includes the /project prefix). |
| 2 | Quick links to Work Items, Timeline, Budget Overview, Invoices, Vendors | PASS — All 5 links present: /project/work-items, /schedule, /budget/overview, /budget/invoices, /budget/vendors. |
| 3 | Visible on all viewports without being hidden or collapsed | PASS — CSS uses flex + CSS Grid auto-fill with no media queries hiding content. Grid reflows naturally on smaller viewports. |
| 4 | Keyboard accessible with appropriate aria-label attributes |
PASS — All 5 quick links have aria-label. Primary action uses descriptive link text ("New Work Item") which serves as its accessible name. :focus-visible styles defined on both .primaryAction and .quickLink. |
| 5 | Dark mode with appropriate color tokens | PASS — All colors use semantic CSS custom properties (--color-primary, --color-bg-secondary, --color-border, etc.). No hardcoded color values. |
Additional Checks
- Scope: Clean — only QuickActionsCard component, CSS module, tests, and DashboardPage integration. No scope creep.
- Security review: Present (COMMENTED, no findings).
- UX review: Present (APPROVED).
- Test authorship: Tests co-authored by
qa-integration-tester (Haiku 4.5)— correct per CLAUDE.md. - Test coverage: 8 unit tests covering all links, hrefs, aria-labels, and rendering behavior.
All 5 acceptance criteria pass. CI checks are still running at time of review — merge after CI is green.
steilerDev
left a comment
There was a problem hiding this comment.
[ux-designer]
PR #714 — QuickActionsCard design review.
Summary
The component is structurally clean: all color tokens are correct semantic Layer 2 references, box-shadow: var(--shadow-focus) is applied consistently on both interactive elements, and the grid layout is appropriate. However, there are two medium-severity token adherence issues and two low-severity gaps that need to be addressed before merge.
Findings
Medium — Hardcoded transition durations (lines 19, 50–52)
.primaryAction and .quickLink both use hardcoded 0.15s ease instead of transition tokens.
Line 19 (.primaryAction):
/* current */
transition: background-color 0.15s ease;
/* correct — use composite token */
transition: var(--transition-button);Lines 50–52 (.quickLink):
/* current */
transition:
border-color 0.15s ease,
background-color 0.15s ease;
/* correct — the composite token exists for exactly this case */
transition: var(--transition-button-border);--transition-button-border is defined in tokens.css as background-color var(--transition-normal), border-color var(--transition-normal), which matches the intent here precisely.
Medium — Wrong semantic token for hover background (line 58)
.quickLink:hover sets background-color: var(--color-bg-tertiary).
The Style Guide documents --color-bg-tertiary as "Code blocks, inset regions" — it is not the hover background. The correct token is --color-bg-hover.
/* current */
.quickLink:hover {
border-color: var(--color-primary);
background-color: var(--color-bg-tertiary);
}
/* correct */
.quickLink:hover {
border-color: var(--color-primary);
background-color: var(--color-bg-hover);
}In dark mode: --color-bg-tertiary resolves to #1e293b (slate-600) while --color-bg-hover resolves to the same #1e293b (also slate-600 in the current token file). However, in light mode they differ: --color-bg-tertiary is #f3f4f6 (gray-100) and --color-bg-hover is #f9fafb (gray-50). Using the semantic token with the correct purpose is required regardless of whether the visual difference is visible in one mode.
Low — No prefers-reduced-motion guard
Both transitions are unconditional. Add a reduced-motion guard:
@media (prefers-reduced-motion: reduce) {
.primaryAction,
.quickLink {
transition: none;
}
}This is a recurring pattern gap noted in PR #380 and PR #413 reviews. Every CSS module with transitions must include this guard.
Low — Missing mobile touch targets
Neither .primaryAction nor .quickLink enforces the 44px minimum touch target on mobile. The Style Guide requires a min-height: 44px guard at the mobile/tablet breakpoint:
@media (max-width: 768px) {
.primaryAction,
.quickLink {
min-height: 44px;
}
}Informational — aria-label / visible text mismatch on quick links
The quick links carry aria-label values that diverge from visible text:
- Visible: "Work Items" → aria-label: "View work items"
- Visible: "Timeline" → aria-label: "View timeline"
- etc.
For <a> elements, the visible text content is already the accessible name. Adding a divergent aria-label overrides it, which can cause confusion when a screen reader reads "View work items" but the sighted label says "Work Items". The recommended fix is one of:
- Remove the
aria-labelattributes and let visible text serve as the accessible name (simplest), or - Make
aria-labeland visible text identical if additional context is genuinely needed.
What passes
- All color tokens:
--color-primary,--color-primary-text,--color-primary-hover,--color-bg-secondary,--color-border,--color-text-primary— all correct Layer 2 semantic tokens. - Focus visible:
outline: none; box-shadow: var(--shadow-focus)— correct pattern on both interactive elements. Nooutline:regression. - No hardcoded hex values anywhere in the CSS module.
- No dark mode component-level overrides needed — all colors are semantic and switch automatically.
- Grid layout with
auto-fill minmax(120px, 1fr)is appropriate for the card context.
|
🎉 This PR is included in version 1.15.0-beta.7 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Summary
Linkwith correct routes andaria-labelattributesFixes #477
Test plan
Co-Authored-By: Claude Opus 4.6 noreply@anthropic.com
Co-Authored-By: Claude frontend-developer (Haiku 4.5) noreply@anthropic.com
Co-Authored-By: Claude qa-integration-tester (Haiku 4.5) noreply@anthropic.com