Skip to content

Conversation

@ryota-murakami
Copy link
Contributor

@ryota-murakami ryota-murakami commented Feb 11, 2026

Summary

  • Fix 4 P0 (hardcoded colors, reduced-motion, tap areas), 8 P1 (font rendering, auth checks, atomic swap, undo DB sync, comment max length), 8 P2 (validation, cascade warning, contrast, responsive) bugs identified in qa-summary.md (score 53.5/100 FAIL)
  • 24 files changed across CSS/layout, accessibility, validation/business logic, and UI enhancement
  • All quality gates pass: lint, typecheck, build, 1202/1202 unit tests

Changes by Phase

Phase 1: CSS & Layout (P0-1, P0-2, P1-1, P1-2, P1-3, P1-4, P2-5, P2-6, P2-8)

  • Login/boards pages: hardcoded gray/blue → theme tokens (bg-background, bg-primary)
  • globals.css: SF Pro font stack, prefers-reduced-motion, prefers-contrast: more
  • button.tsx: github variant + xl size; landing page deduped (removed local cn/buttonVariants)
  • Mobile header stacking, responsive landing grid, muted-foreground contrast bump

Phase 2: Accessibility (P0-3, P0-4)

  • 44px tap areas on checkbox, switch, column menu via ::after pseudo-elements
  • useReducedMotion() in 4 components (StatusColumn, MaintenanceClient, CommandPalette, KanbanBoard)

Phase 3: Validation & Business Logic (P1-5, P1-6, P1-7, P1-8, P2-1, P2-3, P2-4)

  • Auth checks (getUser()) on mutation server actions
  • Atomic column swap via PostgreSQL RPC swap_statuslist_positions (new migration)
  • Comment max length 300→2000 (shared COMMENT_MAX_LENGTH), prop-relative warning threshold
  • Undo DB sync: batchUpdateStatusListPositions/batchUpdateRepoCardOrders after Redux dispatch
  • Zod schemas for statusList name/color, grid positions, board name

Phase 4: UI Enhancement (P2-2)

  • Cascade warning in column delete dialog: "This will also remove N cards in this column."

Tests

  • Updated CommentInlineEdit stories/tests for new 2000-char max length
  • Added useReducedMotion to StatusColumn test mock
  • Added swap_statuslist_positions RPC type to Supabase types

Skipped

  • P2-7 (container padding): Different page layouts make uniform padding inappropriate

Test plan

  • Verify login page uses theme-aware colors (no gray/blue in any theme)
  • Verify boards page "Create Board" button uses primary color
  • Switch themes in settings — all pages should adapt correctly
  • Test column drag undo (Z key) → reload → verify positions persisted
  • Test column delete dialog shows card count warning
  • Verify 44px tap targets on checkbox, switch, column menu (mobile)
  • Enable prefers-reduced-motion: reduce → verify no animations
  • Apply Supabase migration and test atomic column swap

Summary by CodeRabbit

  • New Features

    • Comment character limit increased to 2,000 characters.
  • Improvements

    • Animations respect system reduced-motion preferences.
    • Improved contrast, antialiased fonts, and refined theme colors for readability.
    • More responsive layouts and updated create-board CTA/button styles.
    • Delete-column confirmation now shows how many cards will be removed.
    • New button styling variant and extra size option.
  • Tests

    • Added end-to-end theme visual validation covering all themes.

eslint-plugin-react@7.37.5 (via eslint-config-next) uses the removed
context.getFilename() API, causing a crash on ESLint 10.
…/.dark

[data-theme='xxx'] and :root/.dark all share specificity (0,1,0).
Because themes.css is @imported before :root/.dark in globals.css,
the base rules always won the cascade — silently overriding all 14
theme variables.

Fix: html[data-theme='xxx'] → specificity (0,1,1), guaranteed to win.

Add E2E tests verifying computed CSS variables actually change for
every theme (catches this class of specificity bug).
Address 4 P0, 8 P1, 8 P2 bugs across Visual, HIG, Functional, and
Edge Case dimensions identified in qa-summary.md.

Phase 1 - CSS & Layout:
- Replace hardcoded gray/blue colors with theme tokens (login, boards, landing)
- Add prefers-reduced-motion and prefers-contrast media queries
- Add SF Pro font stack + antialiased rendering
- Add github/xl button variants, remove duplicate cn/buttonVariants from landing
- Mobile header stacking, responsive landing grid
- Bump muted-foreground contrast across 7 light themes

Phase 2 - Accessibility:
- 44px tap areas on checkbox, switch, column menu via ::after pseudo-elements
- useReducedMotion() in StatusColumn, MaintenanceClient, CommandPalette, KanbanBoard

Phase 3 - Validation & Business Logic:
- Auth checks on mutation server actions
- Atomic column swap via PostgreSQL RPC (swap_statuslist_positions)
- Comment max length 300→2000 (shared COMMENT_MAX_LENGTH)
- Undo DB sync (batchUpdate after Redux dispatch)
- Zod validation for statusList name/color, grid positions, board name

Phase 4 - UI Enhancement:
- Cascade warning in column delete dialog ("also removes N cards")

Tests updated for new max length, useReducedMotion mock, RPC types.
@vercel
Copy link
Contributor

vercel bot commented Feb 11, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
gitbox Ready Ready Preview, Comment Feb 11, 2026 0:58am

Request Review

@coderabbitai
Copy link

coderabbitai bot commented Feb 11, 2026

📝 Walkthrough

Walkthrough

Adds theme visual E2E tests; moves theme selectors to html[data-theme] and adjusts contrast tokens; implements reduced-motion support across interactive components; increases comment max length and exposes maxLength prop; adds validation schemas and an atomic DB RPC to swap status list positions; minor UI and dependency tweaks.

Changes

Cohort / File(s) Summary
E2E Tests
e2e/logged-in/theme-visual-application.spec.ts
New Playwright test validating 14 themes (7 light, 7 dark), asserting html[data-theme], .dark class, body/bg CSS values, and uniqueness of --background per theme. Uses persistent auth storageState.
Theme & Global Styles
src/styles/themes.css, src/styles/globals.css
Theme selector scope changed to html[data-theme='X']; muted-foreground token adjustments; added antialiased font stack, reduced-motion and high-contrast CSS blocks.
UI Components & Tokens
src/components/ui/button.tsx, src/components/ui/checkbox.tsx, src/components/ui/switch.tsx
Added github button variant and xl size; checkbox/switch root classes extended with positioned pseudo-elements for hit area/styling.
Reduced-Motion Across UI
src/app/maintenance/MaintenanceClient.tsx, src/components/Board/KanbanBoard.tsx, src/components/Board/StatusColumn.tsx, src/components/CommandPalette/CommandPalette.tsx, src/tests/unit/components/Board/StatusColumn.test.tsx
Introduced useReducedMotion handling: disable/simplify initial/exit animations and transitions when user prefers reduced motion; updated unit test mocks.
Comment Component & Tests
src/components/Board/CommentInlineEdit.tsx, src/components/Board/CommentInlineEdit.stories.tsx, src/components/Board/CommentEditingWorkflow.stories.tsx, src/tests/unit/components/Board/CommentInlineEdit.test.tsx
Default max length increased from 300→2000; warning threshold now 90% of max; added optional maxLength prop; updated stories and tests to match new limits.
Board UI & Layout
src/app/board/[id]/BoardPageClient.tsx, src/app/boards/page.tsx, src/app/page.tsx, src/components/Boards/BoardGrid.tsx
Header layout and title sizing made responsive; action container allows wrapping; delete-column confirmation now reports impacted card count; create-board CTA unified to shared Button with Plus icon; responsive grid adjustments.
Login & Root Layout
src/app/login/page.tsx, src/app/layout.tsx
Removed login gradient in favor of bg-background; updated color-token button/link styles; added antialiased to root body.
Backend / Validation / DB
src/lib/validations/board.ts, src/lib/actions/board.ts, src/lib/supabase/types.ts, src/supabase/migrations/20260211_swap_statuslist_positions_rpc.sql
Added STATUS_LIST validation schemas (name, color, grid position); added auth and input validation to board/status-list actions; replaced manual swap with RPC swap_statuslist_positions and added migration implementing atomic row-locked swap.
Tests & Stories
src/components/Board/CommentEditingWorkflow.stories.tsx, src/components/Board/CommentInlineEdit.stories.tsx, src/tests/unit/components/Board/CommentInlineEdit.test.tsx
Updated story and unit test expectations to reflect comment length changes and explicit maxLength args in some stories.
Deps
package.json
ESLint version changed from ^10.0.0^9.39.2 (dependency declarations updated).

Sequence Diagram(s)

sequenceDiagram
  participant UI as Client (Browser)
  participant Server as App Server / Actions
  participant RPC as Supabase RPC
  participant DB as Postgres Table

  UI->>Server: request swapStatusListPositions(idA,idB)
  Server->>Server: validate ids, auth, input
  Server->>RPC: call swap_statuslist_positions(idA,idB)
  RPC->>DB: SELECT ... FOR UPDATE (lock rows)
  DB-->>RPC: return rows
  RPC->>DB: UPDATE swap grid_row/grid_col for both rows
  DB-->>RPC: confirm updates
  RPC-->>Server: success/void
  Server-->>UI: success response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

✨ Themes aligned on HTML's crown,
Motion bowed when users frown,
Comments grew to reach the sky,
Buttons, checks and switches spry,
Swaps commit in one safe bound. 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.73% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main purpose of the PR: resolving QA-identified bugs (P0-P2 severity levels), which aligns with the substantial changes across CSS, accessibility, validation, and business logic.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/qa-p0-p2-fixes

No actionable comments were generated in the recent review. 🎉


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@morph-subagents
Copy link

🤖 Morph Preview Test

Looks like you hit your rate limits!

Please upgrade your limits here, or wait a few minutes and try again.

If you need help, reach out to support@morphllm.com.


Automated testing by Morph

@codecov-commenter
Copy link

codecov-commenter commented Feb 11, 2026

Codecov Report

❌ Patch coverage is 65.00000% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.53%. Comparing base (ccbaf90) to head (08c6500).

Files with missing lines Patch % Lines
src/app/maintenance/MaintenanceClient.tsx 52.94% 4 Missing and 4 partials ⚠️
src/components/Board/KanbanBoard.tsx 50.00% 8 Missing ⚠️
src/components/Board/StatusColumn.tsx 66.66% 1 Missing and 1 partial ⚠️
src/components/CommandPalette/CommandPalette.tsx 75.00% 1 Missing and 1 partial ⚠️
src/app/board/[id]/BoardPageClient.tsx 83.33% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #54      +/-   ##
==========================================
- Coverage   74.65%   74.53%   -0.12%     
==========================================
  Files         118      118              
  Lines        3764     3821      +57     
  Branches     1013     1050      +37     
==========================================
+ Hits         2810     2848      +38     
- Misses        938      951      +13     
- Partials       16       22       +6     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

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

Caution

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

⚠️ Outside diff range comments (3)
src/components/Board/CommentInlineEdit.tsx (1)

68-68: ⚠️ Potential issue | 🟡 Minor

Stale JSDoc: default is 2000, not 300.

The @param maxLength docs say "default: 300" but the actual default is now COMMENT_MAX_LENGTH (2000).

Fix
- * `@param` maxLength - Character limit (default: 300)
+ * `@param` maxLength - Character limit (default: COMMENT_MAX_LENGTH, currently 2000)
src/components/Board/CommentInlineEdit.stories.tsx (1)

99-127: ⚠️ Potential issue | 🟡 Minor

WarningState and OverLimit stories no longer demonstrate their intended states.

With the default maxLength now at 2000:

  • WarningState (~228 chars) is far below the 90% warning threshold of 1800.
  • OverLimit (~301 chars) is far below 2000.

These stories need either explicit maxLength: 300 args (like TestWarningThreshold/TestOverLimitDisabled) or longer initialValue strings to visually match their names.

Proposed fix
 export const WarningState: Story = {
   args: {
     initialValue:
       'This is a very long comment that is approaching the character limit. We want to show the warning state when the user types more than 270 characters. The counter should turn orange to indicate that they are close to the limit.',
+    maxLength: 300,
     onSave: async (value, options) => {
 export const OverLimit: Story = {
   args: {
     initialValue:
       'This comment exceeds the maximum character limit of 300 characters. The counter should turn red and the save button should be disabled. This is to prevent users from submitting comments that are too long for the database to store. The textarea should still allow typing but saving should not be possible.',
+    maxLength: 300,
     onSave: async (value, options) => {
src/lib/actions/board.ts (1)

351-383: ⚠️ Potential issue | 🟡 Minor

batchUpdateStatusListPositions skips the gridPositionSchema validation added to its sibling.

updateStatusListPosition now validates with gridPositionSchema, but this batch variant accepts the same gridRow/gridCol fields without validation. Invalid positions (negative, non-integer) can slip through here.

Proposed fix
 export async function batchUpdateStatusListPositions(
   updates: Array<{ id: string; gridRow: number; gridCol: number }>,
 ): Promise<void> {
+  // Validate all grid positions (P2-4)
+  for (const u of updates) {
+    gridPositionSchema.parse({ gridRow: u.gridRow, gridCol: u.gridCol })
+  }
+
   const supabase = await createClient()
🤖 Fix all issues with AI agents
In `@e2e/logged-in/theme-visual-application.spec.ts`:
- Around line 145-156: The uniqueness test loops over THEMES.dark and
THEMES.light but do not assert visibility before clicking, causing flaky
failures; update both loops (the one using THEMES.dark that creates themeButton
via page.locator(...) and the analogous THEMES.light loop) to await
expect(themeButton).toBeVisible() immediately before await themeButton.click(),
then proceed to call getBackgroundVar(page) and store into
backgrounds[theme.id]; this ensures themeButton is rendered before interaction.

In `@src/styles/globals.css`:
- Around line 184-193: The media query for high-contrast only updates :root and
.dark; add matching overrides for all 14 theme selectors so each theme gets
high-contrast values for --muted-foreground and --border. Update the `@media`
(prefers-contrast: more) block to include the light theme selectors
html[data-theme='default'], html[data-theme='sunrise'],
html[data-theme='sandstone'], html[data-theme='mint'], html[data-theme='sky'],
html[data-theme='lavender'], html[data-theme='rose'] with appropriate light
high-contrast oklch values and the dark theme selectors html[data-theme='dark'],
html[data-theme='midnight'], html[data-theme='graphite'],
html[data-theme='forest'], html[data-theme='ocean'], html[data-theme='plum'],
html[data-theme='rust'] with appropriate dark high-contrast oklch values so
--muted-foreground and --border are explicitly overridden for each theme instead
of relying on :root/.dark only.

In `@src/supabase/migrations/20260211_swap_statuslist_positions_rpc.sql`:
- Around line 19-24: The current SELECT ... FOR UPDATE on statuslist using id_a
and id_b can deadlock if two transactions call swap(A,B) and swap(B,A); change
the locking to acquire row locks in a deterministic order by comparing id_a and
id_b and selecting/locking the smaller id first and the larger id second (e.g.,
use a branch on id_a < id_b or a single query that orders ids) so that the rows
read into row_a/col_a and row_b/col_b are always locked in the same sequence;
update references to statuslist, id_a, id_b, row_a, col_a, row_b, col_b and the
FOR UPDATE statements accordingly.
- Around line 20-28: The current checks only validate row_a and row_b and rely
on FOUND which reflects only the last SELECT INTO; update the validation to
ensure both selected rows and columns exist by checking col_a and col_b as well
(or check FOUND after each SELECT INTO). Specifically, after the two SELECT ...
INTO statements that populate row_a, col_a, row_b, col_b, add validation that
row_a IS NOT NULL AND col_a IS NOT NULL AND row_b IS NOT NULL AND col_b IS NOT
NULL (or immediately test FOUND after the first SELECT INTO for id_a and after
the second for id_b) and raise the same exception if any are NULL/missing so the
swap cannot write NULL grid_col values.
🧹 Nitpick comments (9)
src/components/Board/StatusColumn.tsx (1)

118-125: Tap target is ~42px, slightly under the 44px guideline.

The button is h-6 w-6 (24px) with after:-inset-[9px], yielding 24 + 18 = 42px. For consistency with the checkbox/switch (which target exactly 44px), consider after:-inset-[10px].

Proposed fix
-                  className="hover:bg-accent relative h-6 w-6 p-0 after:absolute after:-inset-[9px] after:content-['']"
+                  className="hover:bg-accent relative h-6 w-6 p-0 after:absolute after:-inset-[10px] after:content-['']"
src/app/login/page.tsx (1)

34-54: Consider using the <Button> component with the new github variant.

This PR adds a github variant and xl size to <Button>, which seems tailor-made for this login button. Using a raw <button> with hand-rolled classes duplicates what's already available in the shared component. As per coding guidelines, prefer reusing UI components from /components/ui.

Proposed refactor
+import { Button } from '@/components/ui/button'
+
 <form action={signInWithGitHub}>
-  <button
-    type="submit"
-    className="bg-primary text-primary-foreground hover:bg-primary/90 focus:ring-ring flex w-full items-center justify-center gap-3 rounded-lg px-4 py-3 transition-colors focus:ring-2 focus:ring-offset-2 focus:outline-none"
-  >
+  <Button type="submit" variant="github" size="xl" className="w-full gap-3">
     {/* GitHub Icon */}
     <svg ...>...</svg>
     <span className="font-semibold">Sign in with GitHub</span>
-  </button>
+  </Button>
 </form>
src/app/page.tsx (1)

23-38: Unnecessary indirection — alias adds no value.

const Button = SharedButton is a no-op rename. Since the import can be aliased directly, this intermediate binding is dead weight.

♻️ Simplify the import
-import { Button as SharedButton } from '@/components/ui/button'
+import { Button } from '@/components/ui/button'
 import { cn } from '@/lib/utils'
 
-// Use shared Button from `@/components/ui/button` (supports github variant + xl size)
-const Button = SharedButton
src/app/board/[id]/BoardPageClient.tsx (1)

399-408: Extract the card count computation out of JSX.

The IIFE inside AlertDialogDescription adds unnecessary cognitive overhead. Compute it before the return statement for clarity.

♻️ Move card count to a variable

Compute alongside other derived state (e.g., near line 82):

+ const pendingDeleteCardCount = repoCards.filter(
+   (c) => c.statusId === statusListDialog.pendingDeleteStatusId,
+ ).length

Then simplify the JSX:

              &quot;?
-              {(() => {
-                const cardCount = repoCards.filter(
-                  (c) => c.statusId === statusListDialog.pendingDeleteStatusId,
-                ).length
-                if (cardCount > 0) {
-                  return ` This will also remove ${cardCount} card${cardCount !== 1 ? 's' : ''} in this column.`
-                }
-                return ''
-              })()}{' '}
+              {pendingDeleteCardCount > 0 &&
+                ` This will also remove ${pendingDeleteCardCount} card${pendingDeleteCardCount !== 1 ? 's' : ''} in this column.`}{' '}
              This action cannot be undone.

As per coding guidelines: "Extract helper functions as pure functions below component definition rather than keeping logic inline."

e2e/logged-in/theme-visual-application.spec.ts (1)

69-69: Prefer waitFor or locator assertions over waitForTimeout.

Hard-coded waitForTimeout(1000) and waitForTimeout(500) make tests slow and flaky. Consider replacing these with Playwright's built-in auto-waiting, e.g. wait for the theme button to be visible or for a CSS variable to change. The 500ms waits after click could be replaced by asserting the expected data-theme attribute (which already has a 5s timeout on line 84).

Also applies to: 108-108, 141-141, 169-169

src/components/Board/KanbanBoard.tsx (2)

389-401: Undo DB sync is fire-and-forget — no UI revert on failure.

If batchUpdateStatusListPositions fails, the Redux state already reflects the undo but the DB retains the pre-undo positions. The toast notification is good, but the user has no way to retry or know the exact consequence. Consider whether a retry or explicit revert is needed, or if this is acceptable given the low failure likelihood.

Same pattern applies to the card undo on lines 413-425.


784-793: exit animation won't fire without AnimatePresence.

The exit={{ opacity: 0 }} on line 788 requires the parent to wrap this conditional in <AnimatePresence>. Currently, when undoMessage becomes null, React unmounts the element immediately without the exit transition. This is pre-existing behavior but worth noting if you want the fade-out to actually work.

src/lib/actions/board.ts (2)

156-158: parse() throws a raw ZodError to the client — prefer safeParse() for server actions.

renameBoardAction and deleteBoardAction use safeParse() and return structured error objects. Here (and in updateStatusList, updateStatusListPosition, createBoard), .parse() throws a ZodError that will surface as an opaque server error to the caller. Consider aligning on safeParse() for consistent, user-friendly error handling.


245-268: Auth check added to deleteStatusList but absent from updateStatusList — inconsistent.

Both are mutation actions on the same resource. While RLS ultimately enforces ownership, the explicit auth guard pattern should be consistent across all mutations to fail fast with a clear error.

@github-actions
Copy link

github-actions bot commented Feb 11, 2026

🧪 E2E Coverage Report (Sharded: 12 parallel jobs)

Metric Coverage
Lines 93.51%
Functions 17.49%
Branches 16.68%
Statements 30.26%

📊 Full report available in workflow artifacts

- Add toBeVisible() assertion before clicking theme buttons in uniqueness E2E tests
- Add high-contrast overrides for all 14 theme selectors (not just :root/.dark)
- Fix deadlock risk in swap RPC with deterministic lock ordering (id_a < id_b)
- Add col_a/col_b NULL checks to swap RPC validation
@morph-subagents
Copy link

🤖 Morph Preview Test

Looks like you hit your rate limits!

Please upgrade your limits here, or wait a few minutes and try again.

If you need help, reach out to support@morphllm.com.


Automated testing by Morph

@ryota-murakami ryota-murakami merged commit bf235e5 into main Feb 11, 2026
16 of 20 checks passed
@ryota-murakami ryota-murakami deleted the fix/qa-p0-p2-fixes branch February 11, 2026 01:09
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.

2 participants