Skip to content

feat: add subtitle edit form and visibility toggle to Board Settings#134

Merged
ryota-murakami merged 3 commits intomainfrom
feat/board-settings-subtitle-edit-toggle
Feb 19, 2026
Merged

feat: add subtitle edit form and visibility toggle to Board Settings#134
ryota-murakami merged 3 commits intomainfrom
feat/board-settings-subtitle-edit-toggle

Conversation

@ryota-murakami
Copy link
Contributor

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

Summary

  • Add subtitle text input with character count and save button to Board Settings General tab
  • Add "Show Subtitle" switch toggle to control subtitle visibility in the board header
  • Add toggleBoardSubtitleVisibility server action that persists to the board settings JSONB column
  • Fix getByRole('textbox') ambiguity in existing E2E and unit tests (now 2 textboxes in dialog)

Changed Files (10)

File Change
src/lib/types/board-settings.ts Add showSubtitle to BoardSettings interface + parser
src/lib/validations/board.ts Add showSubtitle: z.boolean().optional() to schema
src/lib/actions/board.ts Add toggleBoardSubtitleVisibility server action
src/components/Boards/BoardSettingsDialog.tsx Subtitle form + visibility toggle UI
src/hooks/board/useBoardSettings.ts showSubtitle state + handleShowSubtitleChange
src/app/board/[id]/BoardPageClient.tsx Conditional subtitle rendering + new props
e2e/logged-in/board-settings-subtitle.spec.ts NEW — E2E tests for subtitle features
e2e/helpers/db-query.ts Add resetBoardSettings() helper
e2e/logged-in/board-settings.spec.ts Fix textbox selector ambiguity
src/tests/unit/components/Boards/BoardSettingsDialog.test.tsx Fix textbox selector ambiguity

Test plan

  • Unit tests: 1282/1282 passed
  • ESLint: 0 warnings
  • TypeScript: no errors
  • Build: success
  • E2E: 12/12 shards passed

Summary by CodeRabbit

Release Notes

  • New Features

    • Added board subtitle editing with character limit display
    • Added toggle to show or hide board subtitle in the header
    • Board subtitle preferences now persist to the database
  • Tests

    • Added comprehensive end-to-end tests for board subtitle management
    • Improved test selector specificity for better stability

Add subtitle editing and show/hide toggle to the General tab of the
Board Settings dialog, allowing users to manage the board subtitle
without using inline editing in the header.

Changes:
- Add subtitle text input with character count (100 max) and save button
- Add "Show Subtitle" switch toggle to control header visibility
- Add `toggleBoardSubtitleVisibility` server action for settings JSONB
- Add `showSubtitle` to BoardSettings model, validation, and parser
- Extend `useBoardSettings` hook with visibility state management
- Conditionally render subtitle in board header based on setting
- Add E2E test suite for subtitle editing and visibility toggle
- Fix textbox selector ambiguity in existing E2E and unit tests
@vercel
Copy link
Contributor

vercel bot commented Feb 19, 2026

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

Project Deployment Actions Updated (UTC)
gitbox Ready Ready Preview, Comment Feb 19, 2026 2:29pm

Request Review

@coderabbitai
Copy link

coderabbitai bot commented Feb 19, 2026

Warning

Rate limit exceeded

@ryota-murakami has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 19 minutes and 45 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

This PR implements a board subtitle visibility toggle feature, adding UI controls in the board settings dialog to display and manage subtitle visibility. Changes include conditional subtitle rendering in BoardPageClient, new server actions for persistence, state management through the useBoardSettings hook, type definitions, validation schemas, comprehensive e2e and unit tests, and database helpers.

Changes

Cohort / File(s) Summary
Database Helpers
e2e/helpers/db-query.ts
Added resetBoardSettings() helper function to reset board settings to empty object for test boards, with error handling and validation for update counts.
E2E Tests
e2e/logged-in/board-settings-subtitle.spec.ts, e2e/logged-in/board-settings.spec.ts
New comprehensive subtitle test suite covering input display, prefilled values, character count, saving, and visibility toggle; updated board settings tests with more specific textbox selectors (by accessible name "Board name").
UI Components
src/app/board/[id]/BoardPageClient.tsx, src/components/Boards/BoardSettingsDialog.tsx
BoardPageClient now conditionally renders subtitle based on showSubtitle flag and passes new props to dialog; BoardSettingsDialog extended with subtitle input, character count, Save button, and Show Subtitle toggle with dedicated handlers and loading states.
State Management & Hooks
src/hooks/board/useBoardSettings.ts
Added showSubtitle boolean state and handleShowSubtitleChange() handler to public API, initialized from parsed board settings with default true.
Server Actions & Validation
src/lib/actions/board.ts, src/lib/types/board-settings.ts, src/lib/validations/board.ts
Added toggleBoardSubtitleVisibility() server action; extended BoardSettings type with optional showSubtitle boolean field (default true); updated schema validation to accept showSubtitle property.
Unit Tests
src/tests/unit/components/Boards/BoardSettingsDialog.test.tsx
Added mocks for three new board actions; updated input selectors from role-based to placeholder-based queries for improved specificity.

Sequence Diagram

sequenceDiagram
    actor User
    participant Dialog as BoardSettingsDialog
    participant Server as Server Action:<br/>toggleBoardSubtitleVisibility
    participant DB as Database
    participant State as useBoardSettings<br/>(Local State)

    User->>Dialog: Toggle "Show Subtitle" switch
    Dialog->>Server: Call toggleBoardSubtitleVisibility(boardId, show)
    activate Server
    Server->>DB: Read current board.settings
    Server->>DB: Update board.settings with new showSubtitle flag
    DB-->>Server: Return updated row count
    Server-->>Dialog: Return { showSubtitle }
    deactivate Server
    Dialog->>State: Call onShowSubtitleChange(show) callback
    State->>State: Update showSubtitle state
    Dialog->>Dialog: Update UI, show success/error toast
    Dialog->>User: Render subtitle visibility state reflected in UI
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

A toggle so swift, a subtitle's delight 🎬✨
Show it or hide it, the choice feels just right
Through dialogs and hooks, the settings take flight,
Your board's little whisper now dances in sight! 📝💫

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and specifically summarizes the main change: adding subtitle editing form and visibility toggle to Board Settings.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/board-settings-subtitle-edit-toggle

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 19, 2026

Codecov Report

❌ Patch coverage is 41.37931% with 34 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.69%. Comparing base (2d67f41) to head (a6a3c0a).

Files with missing lines Patch % Lines
src/components/Boards/BoardSettingsDialog.tsx 30.95% 28 Missing and 1 partial ⚠️
src/app/board/[id]/BoardPageClient.tsx 50.00% 4 Missing ⚠️
src/hooks/board/useBoardSettings.ts 80.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #134      +/-   ##
==========================================
- Coverage   71.03%   70.69%   -0.34%     
==========================================
  Files         145      145              
  Lines        4381     4433      +52     
  Branches     1148     1169      +21     
==========================================
+ Hits         3112     3134      +22     
- Misses       1248     1277      +29     
- Partials       21       22       +1     

☔ 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.

…tures

- Board subtitle, inline editable header, public board sharing
- Board Settings 4-tab structure (General/Cards/Sharing/Danger Zone)
- XSS prevention (DOMPurify), showSubtitle in BoardSettings
- New DB columns: board.subtitle, board.is_public, board.share_slug
- New table: user_settings (per-user page customization)
- 4 new E2E test files, 7 Phase 3 items marked done
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: 1

🧹 Nitpick comments (3)
src/hooks/board/useBoardSettings.ts (1)

86-93: parseBoardSettings called twice on mount.

Both lazy initializers parse the same boardSettings JSON independently. Consider computing once:

♻️ Suggested refactor
-  const [cardDisplaySettings, setCardDisplaySettings] =
-    useState<CardDisplaySettings>(() => {
-      const parsed = parseBoardSettings(boardSettings)
-      return parsed.cardDisplay ?? DEFAULT_CARD_DISPLAY_SETTINGS
-    })
-  const [showSubtitle, setShowSubtitle] = useState<boolean>(() => {
-    const parsed = parseBoardSettings(boardSettings)
-    return parsed.showSubtitle ?? true
-  })
+  const _initialParsed = parseBoardSettings(boardSettings)
+  const [cardDisplaySettings, setCardDisplaySettings] =
+    useState<CardDisplaySettings>(
+      _initialParsed.cardDisplay ?? DEFAULT_CARD_DISPLAY_SETTINGS,
+    )
+  const [showSubtitle, setShowSubtitle] = useState<boolean>(
+    _initialParsed.showSubtitle ?? true,
+  )

Or keep lazy initializers but share the result via a single lazy init if more state is added later.

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

In `@src/hooks/board/useBoardSettings.ts` around lines 86 - 93, Both state lazy
initializers call parseBoardSettings(boardSettings) twice on mount; compute
parsed once and reuse it. Change the hook to compute parsed =
parseBoardSettings(boardSettings) inside a single lazy initializer or before
calling useState, then initialize card display state using parsed.cardDisplay ??
DEFAULT_CARD_DISPLAY_SETTINGS and showSubtitle using parsed.showSubtitle ?? true
(references: parseBoardSettings, DEFAULT_CARD_DISPLAY_SETTINGS,
showSubtitle/setShowSubtitle, card display state initializer).
src/components/Boards/BoardSettingsDialog.tsx (2)

252-267: No catch block — network errors surface as unhandled rejections.

If updateBoardSubtitle throws (e.g. network failure rather than returning {success: false}), no error toast is shown. The finally block runs, but the exception propagates uncaught. Same gap exists in handleToggleSubtitleVisibility below and in the pre-existing handleTogglePublic.

Since you're adding new code here, consider adding a catch to at least show a generic error toast:

Proposed fix
   async function handleSaveSubtitle() {
     setIsSavingSubtitle(true)
     try {
       const result = await updateBoardSubtitle(boardId, subtitle)
       if (result.success) {
         onSubtitleSuccess?.(result.data.subtitle)
         toast.success('Subtitle updated')
       } else {
         toast.error('Failed to update subtitle', {
           description: result.error,
         })
       }
+    } catch {
+      toast.error('Failed to update subtitle')
     } finally {
       setIsSavingSubtitle(false)
     }
   }

Same applies to handleToggleSubtitleVisibility (lines 272-290).

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

In `@src/components/Boards/BoardSettingsDialog.tsx` around lines 252 - 267,
handleSaveSubtitle and handleToggleSubtitleVisibility currently lack catch
blocks so thrown errors (e.g. network failures from updateBoardSubtitle or
toggleSubtitleVisibility) become unhandled rejections; update both functions to
wrap the await calls in try/catch (keeping the existing finally to reset
isSaving state) and in the catch show a generic toast.error (include
error.message or a short description) and optionally log the error; also audit
handleTogglePublic for the same pattern and add similar try/catch handling
around its async calls to avoid unhandled rejections.

580-645: Subtitle save uses a manual async handler instead of useActionState + Form Actions.

The rename section directly above uses useActionState with a <form action={...}>, which is the project's preferred React 19 pattern. The subtitle section uses a plain onClick handler with manual loading state (isSavingSubtitle). Consider aligning with the rename pattern for consistency — it would also give you form-level validation state and pending tracking for free.

Separately, the Save button is always enabled (when not saving), even if the subtitle hasn't changed. A small dirty-check would avoid unnecessary server round-trips:

 <Button
   type="button"
-  disabled={isSavingSubtitle}
+  disabled={isSavingSubtitle || subtitle === (initialBoardSubtitle ?? '')}
   onClick={handleSaveSubtitle}
   data-testid="save-subtitle"
 >

As per coding guidelines, "Use React 19.2 hooks: useOptimistic, useActionState, use API, and Form Actions instead of older patterns".

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

In `@src/components/Boards/BoardSettingsDialog.tsx` around lines 580 - 645,
Replace the manual onClick/save flow for the subtitle (currently using subtitle
state, isSavingSubtitle, and handleSaveSubtitle) with the project's React 19
form-action pattern: create a Form Action to handle subtitle saves and use
useActionState in the component, render a <form action={...}> around the Input
and Save button, and remove the manual isSavingSubtitle state and
handleSaveSubtitle usage; wire the Save button's pending state to useActionState
so pending UI is automatic. Also add a simple dirty-check (compare subtitle
local state to the incoming prop/initial value) and disable the Save button (and
prevent calling the form action) when the value is unchanged or when subtitle
length exceeds BOARD_SUBTITLE_MAX_LENGTH; keep the subtitleVisible Toggle
(handleToggleSubtitleVisibility) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@e2e/logged-in/board-settings-subtitle.spec.ts`:
- Around line 114-120: The test uses querySingle('board', { id:
BOARD_IDS.testBoard }) and then dereferences board.subtitle inside the toPass
callback without null-guarding; change the callback used with toPass to first
assert board is not null (e.g., expect(board).not.toBeNull() or throw a clear
assertion) before accessing board.subtitle so a failing lookup yields a clear
assertion failure instead of a TypeError; update both occurrences mentioned (the
current block and the similar block around lines 182-187) to follow the same
pattern referencing the querySingle call and the board local variable.

---

Nitpick comments:
In `@src/components/Boards/BoardSettingsDialog.tsx`:
- Around line 252-267: handleSaveSubtitle and handleToggleSubtitleVisibility
currently lack catch blocks so thrown errors (e.g. network failures from
updateBoardSubtitle or toggleSubtitleVisibility) become unhandled rejections;
update both functions to wrap the await calls in try/catch (keeping the existing
finally to reset isSaving state) and in the catch show a generic toast.error
(include error.message or a short description) and optionally log the error;
also audit handleTogglePublic for the same pattern and add similar try/catch
handling around its async calls to avoid unhandled rejections.
- Around line 580-645: Replace the manual onClick/save flow for the subtitle
(currently using subtitle state, isSavingSubtitle, and handleSaveSubtitle) with
the project's React 19 form-action pattern: create a Form Action to handle
subtitle saves and use useActionState in the component, render a <form
action={...}> around the Input and Save button, and remove the manual
isSavingSubtitle state and handleSaveSubtitle usage; wire the Save button's
pending state to useActionState so pending UI is automatic. Also add a simple
dirty-check (compare subtitle local state to the incoming prop/initial value)
and disable the Save button (and prevent calling the form action) when the value
is unchanged or when subtitle length exceeds BOARD_SUBTITLE_MAX_LENGTH; keep the
subtitleVisible Toggle (handleToggleSubtitleVisibility) unchanged.

In `@src/hooks/board/useBoardSettings.ts`:
- Around line 86-93: Both state lazy initializers call
parseBoardSettings(boardSettings) twice on mount; compute parsed once and reuse
it. Change the hook to compute parsed = parseBoardSettings(boardSettings) inside
a single lazy initializer or before calling useState, then initialize card
display state using parsed.cardDisplay ?? DEFAULT_CARD_DISPLAY_SETTINGS and
showSubtitle using parsed.showSubtitle ?? true (references: parseBoardSettings,
DEFAULT_CARD_DISPLAY_SETTINGS, showSubtitle/setShowSubtitle, card display state
initializer).

@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

- Add expect(board).not.toBeNull() before property access
- Use optional chaining board?.subtitle and board?.settings
@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

@github-actions
Copy link

github-actions bot commented Feb 19, 2026

🧪 E2E Coverage Report (Sharded: 12 parallel jobs)

Metric Coverage
Lines 94.38%
Functions 17.76%
Branches 17.38%
Statements 31.03%

📊 Full report available in workflow artifacts

@ryota-murakami ryota-murakami merged commit 36f73ae into main Feb 19, 2026
20 checks passed
@ryota-murakami ryota-murakami deleted the feat/board-settings-subtitle-edit-toggle branch February 19, 2026 14:44
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

Comments