feat: add subtitle edit form and visibility toggle to Board Settings#134
Conversation
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
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the 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. 📝 WalkthroughWalkthroughThis 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
🤖 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 Report❌ Patch coverage is 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. 🚀 New features to boost your workflow:
|
…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
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
src/hooks/board/useBoardSettings.ts (1)
86-93:parseBoardSettingscalled twice on mount.Both lazy initializers parse the same
boardSettingsJSON 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
updateBoardSubtitlethrows (e.g. network failure rather than returning{success: false}), no error toast is shown. Thefinallyblock runs, but the exception propagates uncaught. Same gap exists inhandleToggleSubtitleVisibilitybelow and in the pre-existinghandleTogglePublic.Since you're adding new code here, consider adding a
catchto 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 ofuseActionState+ Form Actions.The rename section directly above uses
useActionStatewith a<form action={...}>, which is the project's preferred React 19 pattern. The subtitle section uses a plainonClickhandler 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 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 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 |
🧪 E2E Coverage Report (Sharded: 12 parallel jobs)
📊 Full report available in workflow artifacts |
Summary
toggleBoardSubtitleVisibilityserver action that persists to the boardsettingsJSONB columngetByRole('textbox')ambiguity in existing E2E and unit tests (now 2 textboxes in dialog)Changed Files (10)
src/lib/types/board-settings.tsshowSubtitletoBoardSettingsinterface + parsersrc/lib/validations/board.tsshowSubtitle: z.boolean().optional()to schemasrc/lib/actions/board.tstoggleBoardSubtitleVisibilityserver actionsrc/components/Boards/BoardSettingsDialog.tsxsrc/hooks/board/useBoardSettings.tsshowSubtitlestate +handleShowSubtitleChangesrc/app/board/[id]/BoardPageClient.tsxe2e/logged-in/board-settings-subtitle.spec.tse2e/helpers/db-query.tsresetBoardSettings()helpere2e/logged-in/board-settings.spec.tssrc/tests/unit/components/Boards/BoardSettingsDialog.test.tsxTest plan
Summary by CodeRabbit
Release Notes
New Features
Tests