Improve UI design: color tokens, accessibility, and UX#35
Conversation
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (13)
📝 WalkthroughWalkthroughAdds UI/UX changes: a delete confirmation flow for documents, retry on documents page, improved loading spinner, homepage dark-theme token consolidation and styling updates, accessibility/focus improvements, and avatar initials generation. Changes
Sequence Diagram(s)sequenceDiagram
participant User as User
participant UI as DocumentList UI
participant Dialog as ConfirmDialog
participant API as Backend API
participant Query as Documents Query Cache
User->>UI: Click "Delete" in actions menu
UI->>UI: set deletingDoc (id, title) -> open Dialog
User->>Dialog: Click "Confirm Delete"
Dialog->>API: deleteDocumentMutation(id)
API-->>Dialog: 200 OK
Dialog->>Query: invalidate/refetch documents
Query-->>UI: updated documents list
Dialog->>UI: clear deletingDoc -> close
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
📝 Coding Plan
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 |
Verification: verify:selfResult: ✅ PASS in 98.4s
Verification: verify:integrationResult: ✅ PASS |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (2)
docs/tasks/active/20260315-ui-design-improvements-todo.md (1)
24-28: Consider adding a short “Verification” note before marking all items complete.A brief section with validation status (and later archive status) will make task closure auditable.
Based on learnings, "Before marking a task done: (1) Update
docs/design/if architecture changed, (2) Runpnpm verify:fastand confirm pass, (3) Archive withpnpm tasks:archive && pnpm tasks:index."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/tasks/active/20260315-ui-design-improvements-todo.md` around lines 24 - 28, Add a short "Verification" subsection to the Phase 4 checklist that must be completed before checking off all items: list the steps to (1) update docs/design/ if any architecture changed (reference docs/design/), (2) run and confirm passing of pnpm verify:fast, and (3) archive and reindex via pnpm tasks:archive && pnpm tasks:index; ensure the verification note appears above the completed checklist items and requires confirmation before marking the phase complete.packages/frontend/src/app/home/footer.tsx (1)
16-20: Set an explicit button type for the theme toggle.Line [16] should declare
type="button"to prevent accidental form submissions in reused layouts.🔧 Suggested fix
<button + type="button" onClick={toggleTheme} aria-label={`Switch to ${resolvedTheme === "dark" ? "light" : "dark"} mode`} className="relative bg-homepage-dark-card border-0 rounded-full w-11 h-6 cursor-pointer" >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/frontend/src/app/home/footer.tsx` around lines 16 - 20, The theme toggle button lacks an explicit type, which can cause accidental form submissions; update the JSX button element that uses onClick={toggleTheme} and aria-label referencing resolvedTheme to include type="button" so the <button ...> in the component (where toggleTheme and resolvedTheme are used) will not act as a submit button when placed inside forms.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/tasks/active/20260315-ui-design-improvements-todo.md`:
- Around line 1-28: The task file
docs/tasks/active/20260315-ui-design-improvements-todo.md is missing its
required paired lessons file; create
docs/tasks/active/20260315-ui-design-improvements-lessons.md containing a brief
summary of what was done, key implementation decisions (e.g., token
consolidation, ARIA/focus changes, AvatarFallback change), accessibility/UX
outcomes, unresolved/deferred items, links to related PRs/commits, and the
author/reviewer and date so future maintainers can trace why choices in
functions/components like AvatarFallback, site-header/footer updates, and
CTA/feature-card changes were made.
In `@packages/frontend/src/app/documents/document-list.tsx`:
- Around line 529-533: The delete success callback currently calls
setDeletingDoc(null) unconditionally and can clear a newly opened dialog if the
earlier delete resolves late; update the deleteDocumentMutation.mutate call (and
its callbacks) to only clear deletingDoc when the mutation's finished response
corresponds to the same doc id (compare the id passed to mutate with
deletingDoc?.id or capture the id in mutation variables/context), and add an
onError handler to surface failure feedback (e.g., show a toast or set an error
state) instead of silently failing; reference deletingDoc,
deleteDocumentMutation.mutate, setDeletingDoc, and the mutation callbacks when
implementing this fix.
In `@packages/frontend/src/app/home/features-section.tsx`:
- Line 46: The icon container currently has conflicting semantics (role="img"
with aria-hidden="true"); in the FeaturesSection component remove the role="img"
from the <div className="text-3xl mb-3">{f.icon}</div> if the icon is decorative
so it remains aria-hidden, otherwise if the icon conveys meaning keep role="img"
and add an accessible label (aria-label or aria-labelledby) derived from the
feature text (e.g., f.title or a generated id) to make the image discoverable to
screen readers.
In `@packages/frontend/src/components/nav-user.tsx`:
- Around line 26-33: getInitials can return an empty string for empty or
delimiter-only names; update the function (getInitials) to trim the input, split
on /[\s_-]+/, filter out empty tokens, and pick up to two token initials safely
(guarding token[0] exists). If that still yields nothing, fall back to taking
the first up to two non-whitespace characters from the original trimmed name (if
any), otherwise return a fixed placeholder like "?" so the avatar fallback is
never blank.
In `@packages/frontend/src/components/site-header.tsx`:
- Around line 69-81: The heading currently loses semantic role by assigning
role="button" and keyboard handlers directly on the <h1>; remove role, tabIndex
and onKeyDown from the heading and keep it purely semantic, then add a nested
<button> (or an adjacent inline <button>) that uses the same focus/hover classes
and calls setEditing(true) on click and Enter/Space, and set its aria-label to
`Rename "${title}"`; transfer the cursor/hover/focus styles and aria-label from
the heading to that button so editable, setEditing, title and the existing
onClick logic are preserved but applied to a real <button> element instead of
the <h1>.
---
Nitpick comments:
In `@docs/tasks/active/20260315-ui-design-improvements-todo.md`:
- Around line 24-28: Add a short "Verification" subsection to the Phase 4
checklist that must be completed before checking off all items: list the steps
to (1) update docs/design/ if any architecture changed (reference docs/design/),
(2) run and confirm passing of pnpm verify:fast, and (3) archive and reindex via
pnpm tasks:archive && pnpm tasks:index; ensure the verification note appears
above the completed checklist items and requires confirmation before marking the
phase complete.
In `@packages/frontend/src/app/home/footer.tsx`:
- Around line 16-20: The theme toggle button lacks an explicit type, which can
cause accidental form submissions; update the JSX button element that uses
onClick={toggleTheme} and aria-label referencing resolvedTheme to include
type="button" so the <button ...> in the component (where toggleTheme and
resolvedTheme are used) will not act as a submit button when placed inside
forms.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 91648159-300f-428c-b7d5-82535f830e1e
📒 Files selected for processing (13)
docs/tasks/active/20260315-ui-design-improvements-todo.mdpackages/frontend/src/app/documents/document-list.tsxpackages/frontend/src/app/documents/page.tsxpackages/frontend/src/app/home/demo-section.tsxpackages/frontend/src/app/home/developer-section.tsxpackages/frontend/src/app/home/features-section.tsxpackages/frontend/src/app/home/footer.tsxpackages/frontend/src/app/home/hero-section.tsxpackages/frontend/src/app/home/nav-bar.tsxpackages/frontend/src/app/home/opensource-section.tsxpackages/frontend/src/components/nav-user.tsxpackages/frontend/src/components/site-header.tsxpackages/frontend/src/index.css
| # UI Design Improvements | ||
|
|
||
| ## Phase 1: Color Token Consolidation | ||
| - [x] Add homepage dark-section tokens to index.css | ||
| - [x] Update footer.tsx to use design tokens | ||
| - [x] Update developer-section.tsx to use design tokens | ||
| - [x] Update opensource-section.tsx to use design tokens | ||
| - [x] Verify dark/light mode consistency | ||
|
|
||
| ## Phase 2: Accessibility | ||
| - [x] Fix missing ARIA labels and roles (site-header, footer) | ||
| - [x] Fix focus states (outline-none → focus-visible ring) | ||
| - [x] Fix AvatarFallback hardcoded "CN" → user initials | ||
| - [x] Add hover/focus states to homepage CTA buttons | ||
|
|
||
| ## Phase 3: Homepage Visual Polish | ||
| - [x] Add hover effects to feature cards | ||
| - [x] Add hover ring to developer code cards | ||
| - [x] Add hover to open-source badges and footer links | ||
| - [x] Make hero CTA buttons stack vertically on mobile | ||
| - [x] Unify max-width to 960px across sections | ||
| - [x] Add spinner to demo loading state | ||
|
|
||
| ## Phase 4: UX Improvements | ||
| - [x] Add delete confirmation dialog for documents | ||
| - [x] Add error retry button on documents page | ||
| - [ ] ~~Loading skeleton~~ (already implemented) | ||
| - [ ] ~~Truncated text tooltips~~ (deferred — low impact) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
fd '20260315-ui-design-improvements-(todo|lessons)\.md' docs/tasks/active -t f | sortRepository: wafflebase/wafflebase
Length of output: 122
Create the paired lessons file for this task.
The matching 20260315-ui-design-improvements-lessons.md file is missing. Per coding guidelines, non-trivial tasks must maintain paired task files in docs/tasks/active/ with both YYYYMMDD-<slug>-todo.md and YYYYMMDD-<slug>-lessons.md files.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/tasks/active/20260315-ui-design-improvements-todo.md` around lines 1 -
28, The task file docs/tasks/active/20260315-ui-design-improvements-todo.md is
missing its required paired lessons file; create
docs/tasks/active/20260315-ui-design-improvements-lessons.md containing a brief
summary of what was done, key implementation decisions (e.g., token
consolidation, ARIA/focus changes, AvatarFallback change), accessibility/UX
outcomes, unresolved/deferred items, links to related PRs/commits, and the
author/reviewer and date so future maintainers can trace why choices in
functions/components like AvatarFallback, site-header/footer updates, and
CTA/feature-card changes were made.
| onClick={() => { | ||
| if (deletingDoc) { | ||
| deleteDocumentMutation.mutate(deletingDoc.id, { | ||
| onSuccess: () => setDeletingDoc(null), | ||
| }); |
There was a problem hiding this comment.
Prevent stale delete callbacks from clearing a newer dialog state.
If a delete request resolves after the user has reopened the dialog for another doc, the old success callback can still call setDeletingDoc(null) and close the current dialog unexpectedly. Also, this path has no failure feedback.
Suggested safe callback handling + error feedback
<Button
variant="destructive"
disabled={deleteDocumentMutation.isPending}
onClick={() => {
if (deletingDoc) {
+ const deletingId = deletingDoc.id;
deleteDocumentMutation.mutate(deletingDoc.id, {
- onSuccess: () => setDeletingDoc(null),
+ onSuccess: () =>
+ setDeletingDoc((current) =>
+ current?.id === deletingId ? null : current,
+ ),
+ onError: () => {
+ toast.error("Failed to delete document");
+ },
});
}
}}
>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/frontend/src/app/documents/document-list.tsx` around lines 529 -
533, The delete success callback currently calls setDeletingDoc(null)
unconditionally and can clear a newly opened dialog if the earlier delete
resolves late; update the deleteDocumentMutation.mutate call (and its callbacks)
to only clear deletingDoc when the mutation's finished response corresponds to
the same doc id (compare the id passed to mutate with deletingDoc?.id or capture
the id in mutation variables/context), and add an onError handler to surface
failure feedback (e.g., show a toast or set an error state) instead of silently
failing; reference deletingDoc, deleteDocumentMutation.mutate, setDeletingDoc,
and the mutation callbacks when implementing this fix.
| <div key={f.title} className="bg-homepage-bg border border-homepage-accent/30 rounded-xl p-7"> | ||
| <div className="text-3xl mb-3">{f.icon}</div> | ||
| <div key={f.title} className="bg-homepage-bg border border-homepage-accent/30 rounded-xl p-6 hover:border-homepage-accent/60 hover:shadow-md transition-all duration-200"> | ||
| <div className="text-3xl mb-3" role="img" aria-hidden="true">{f.icon}</div> |
There was a problem hiding this comment.
Remove conflicting accessibility semantics on the icon container.
Line [46] sets role="img" and aria-hidden="true" together. If decorative, keep aria-hidden and drop role; if meaningful, keep role="img" and add an accessible label.
♿ Suggested fix (decorative icon)
- <div className="text-3xl mb-3" role="img" aria-hidden="true">{f.icon}</div>
+ <div className="text-3xl mb-3" aria-hidden="true">{f.icon}</div>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <div className="text-3xl mb-3" role="img" aria-hidden="true">{f.icon}</div> | |
| <div className="text-3xl mb-3" aria-hidden="true">{f.icon}</div> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/frontend/src/app/home/features-section.tsx` at line 46, The icon
container currently has conflicting semantics (role="img" with
aria-hidden="true"); in the FeaturesSection component remove the role="img" from
the <div className="text-3xl mb-3">{f.icon}</div> if the icon is decorative so
it remains aria-hidden, otherwise if the icon conveys meaning keep role="img"
and add an accessible label (aria-label or aria-labelledby) derived from the
feature text (e.g., f.title or a generated id) to make the image discoverable to
screen readers.
| function getInitials(name: string): string { | ||
| return name | ||
| .split(/[\s_-]+/) | ||
| .slice(0, 2) | ||
| .map((w) => w[0]) | ||
| .join("") | ||
| .toUpperCase(); | ||
| } |
There was a problem hiding this comment.
Handle empty/whitespace usernames in getInitials.
Right now an empty or delimiter-only username yields an empty fallback. Provide a safe default so avatar fallback is never blank.
Suggested hardening
function getInitials(name: string): string {
- return name
- .split(/[\s_-]+/)
+ return (
+ name
+ .trim()
+ .split(/[\s_-]+/)
+ .filter(Boolean)
.slice(0, 2)
.map((w) => w[0])
.join("")
- .toUpperCase();
+ .toUpperCase() || "U"
+ );
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function getInitials(name: string): string { | |
| return name | |
| .split(/[\s_-]+/) | |
| .slice(0, 2) | |
| .map((w) => w[0]) | |
| .join("") | |
| .toUpperCase(); | |
| } | |
| function getInitials(name: string): string { | |
| return ( | |
| name | |
| .trim() | |
| .split(/[\s_-]+/) | |
| .filter(Boolean) | |
| .slice(0, 2) | |
| .map((w) => w[0]) | |
| .join("") | |
| .toUpperCase() || "U" | |
| ); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/frontend/src/components/nav-user.tsx` around lines 26 - 33,
getInitials can return an empty string for empty or delimiter-only names; update
the function (getInitials) to trim the input, split on /[\s_-]+/, filter out
empty tokens, and pick up to two token initials safely (guarding token[0]
exists). If that still yields nothing, fall back to taking the first up to two
non-whitespace characters from the original trimmed name (if any), otherwise
return a fixed placeholder like "?" so the avatar fallback is never blank.
| className={`text-base font-medium ${editable ? "cursor-pointer hover:bg-muted/50 px-1 rounded transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" : ""}`} | ||
| onClick={() => { | ||
| if (editable) setEditing(true); | ||
| }} | ||
| title={editable ? "Click to rename" : undefined} | ||
| onKeyDown={(e) => { | ||
| if (editable && (e.key === "Enter" || e.key === " ")) { | ||
| e.preventDefault(); | ||
| setEditing(true); | ||
| } | ||
| }} | ||
| role={editable ? "button" : undefined} | ||
| tabIndex={editable ? 0 : undefined} | ||
| aria-label={editable ? `Rename "${title}"` : undefined} |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n packages/frontend/src/components/site-header.tsx | sed -n '60,90p'Repository: wafflebase/wafflebase
Length of output: 1465
🏁 Script executed:
wc -l packages/frontend/src/components/site-header.tsxRepository: wafflebase/wafflebase
Length of output: 116
🏁 Script executed:
rg -i "role.*button.*h[1-6]|h[1-6].*role.*button" packages/frontend/src --type tsx --type ts -A 2 -B 2Repository: wafflebase/wafflebase
Length of output: 92
🏁 Script executed:
rg "editable.*heading|editable.*title" packages/frontend/src --type tsx --type ts -iRepository: wafflebase/wafflebase
Length of output: 92
🏁 Script executed:
fd -e tsx -e ts packages/frontend/src -x grep -l "button" {} \; | head -10 | xargs rg "type=\"button\"" -A 3Repository: wafflebase/wafflebase
Length of output: 19753
🏁 Script executed:
rg "setEditing|setEditing\(" packages/frontend/src -B 5 -A 5 | head -100Repository: wafflebase/wafflebase
Length of output: 7517
🏁 Script executed:
rg "role.*button" packages/frontend/src --max-count=5 -B 2 -A 2Repository: wafflebase/wafflebase
Length of output: 517
Preserve heading semantics; avoid role="button" on <h1>.
Line 79 assigns role="button" to the heading, which removes heading semantics for screen readers and other assistive technologies. Keep <h1> as a semantic heading and use a nested <button> for edit interactions instead. This also aligns with the codebase pattern of using real button elements throughout.
♿ Suggested semantic fix
- <h1
- className={`text-base font-medium ${editable ? "cursor-pointer hover:bg-muted/50 px-1 rounded transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" : ""}`}
- onClick={() => {
- if (editable) setEditing(true);
- }}
- onKeyDown={(e) => {
- if (editable && (e.key === "Enter" || e.key === " ")) {
- e.preventDefault();
- setEditing(true);
- }
- }}
- role={editable ? "button" : undefined}
- tabIndex={editable ? 0 : undefined}
- aria-label={editable ? `Rename "${title}"` : undefined}
- >
- {title}
- </h1>
+ <h1 className="text-base font-medium">
+ {editable ? (
+ <button
+ type="button"
+ className="cursor-pointer hover:bg-muted/50 px-1 rounded transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring text-left"
+ onClick={() => setEditing(true)}
+ aria-label={`Rename "${title}"`}
+ >
+ {title}
+ </button>
+ ) : (
+ title
+ )}
+ </h1>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| className={`text-base font-medium ${editable ? "cursor-pointer hover:bg-muted/50 px-1 rounded transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" : ""}`} | |
| onClick={() => { | |
| if (editable) setEditing(true); | |
| }} | |
| title={editable ? "Click to rename" : undefined} | |
| onKeyDown={(e) => { | |
| if (editable && (e.key === "Enter" || e.key === " ")) { | |
| e.preventDefault(); | |
| setEditing(true); | |
| } | |
| }} | |
| role={editable ? "button" : undefined} | |
| tabIndex={editable ? 0 : undefined} | |
| aria-label={editable ? `Rename "${title}"` : undefined} | |
| <h1 className="text-base font-medium"> | |
| {editable ? ( | |
| <button | |
| type="button" | |
| className="cursor-pointer hover:bg-muted/50 px-1 rounded transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring text-left" | |
| onClick={() => setEditing(true)} | |
| aria-label={`Rename "${title}"`} | |
| > | |
| {title} | |
| </button> | |
| ) : ( | |
| title | |
| )} | |
| </h1> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/frontend/src/components/site-header.tsx` around lines 69 - 81, The
heading currently loses semantic role by assigning role="button" and keyboard
handlers directly on the <h1>; remove role, tabIndex and onKeyDown from the
heading and keep it purely semantic, then add a nested <button> (or an adjacent
inline <button>) that uses the same focus/hover classes and calls
setEditing(true) on click and Enter/Space, and set its aria-label to `Rename
"${title}"`; transfer the cursor/hover/focus styles and aria-label from the
heading to that button so editable, setEditing, title and the existing onClick
logic are preserved but applied to a real <button> element instead of the <h1>.
- Consolidate hardcoded homepage colors (stone-*, amber-*) into CSS variable design tokens for centralized theme management - Improve accessibility: ARIA attributes on editable title, focus-visible rings, keyboard support, dynamic avatar initials - Add hover/focus effects to homepage cards, buttons, badges, code blocks, and footer links - Make hero CTA buttons stack vertically on mobile screens - Add delete confirmation dialog to prevent accidental deletion - Add retry button to document list error state - Add loading spinner to demo section Co-Authored-By: Claude Opus 4.6 <[email protected]>
a814400 to
b88f4ab
Compare
Move cli and cli-oauth-login task files to archive after completion in recent PRs (#34, #35). Update task index accordingly. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Summary
Test plan
/): verify hover effects on feature cards, CTA buttons, code cards, badges, footer link⋯→ Delete — confirmation dialog should appear🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
UI & Accessibility Improvements
Documentation