Repository navigation
Description
Context
The 2026-10-12 design review of issue #213 left one heading in TODOS.md: "Smaller design gaps left from the 2026-10-12 review". That heading lists seven leftovers. Leftover (1) is two edits (transition-all plus sidebar width), so this PR has eight implementation tasks. The 27/21 counts in the design-review report are historical; they do not define this PR. This spec closes that heading in one PR. Do not cite TODOS.md in CHANGELOG.md.
Verified 2026-10-18 on main at 16dbd4e.
Current State
1. transition-all (16 call sites, not 18)
rg 'transition-all' --glob '*.tsx' on src/ returns 16. The sidebar already uses transition-[width], not transition-all.
| File | Line(s) | What actually changes |
|---|---|---|
src/components/Board/CommentDisplay.tsx |
177, 208 | color, background |
src/components/Boards/BoardCard.tsx |
141 | border-color, box-shadow |
src/components/Boards/BoardCard.tsx |
161 | opacity, color |
src/components/Board/NewRowDropZone.tsx |
54 | color, background, border-color |
src/components/Board/RepoCard.tsx |
227 | box-shadow, box-shadow (ring) |
src/components/ui/font-color-toolbar-button.tsx |
44 | transform |
src/app/(landing)/KanbanPreview.tsx |
413, 474 | color, background, border-color |
src/components/Modals/StatusListDialog.tsx |
139 | border-color, transform |
src/components/Boards/BoardSettingsDialog.tsx |
107, 147 | color, background, border-color |
src/components/Board/SortableColumn.tsx |
111 | transform, opacity, box-shadow, box-shadow (ring) |
src/components/Boards/SortableBoardCard.tsx |
70 | transform, opacity, box-shadow, box-shadow (ring) |
src/components/Board/ColumnInsertZone.tsx |
58, 74 | color, background, border-color |
2. Sidebar width animation from 768px
src/components/Sidebar/Sidebar.tsx:329 has transition-[width] duration-300 ease-out on the <aside> in every viewport. From md (768px) the sidebar sits in the page row (md:contents on the place wrapper). Expanding or collapsing it changes w-16 / w-64 and the page content moves on every animation frame. Below 768px the sidebar is fixed and opens over the page; the page width does not change, so width animation there is acceptable.
3. Card dialog focus return
useCardDialogFocusReturn (src/hooks/board/useCardDialogFocusReturn.ts) is used by:
MoveToAnotherBoardDialogOverflowMenuRemove from Board confirmation
NoteModal (src/components/Modals/NoteModal.tsx) is opened from a card (Open note / Enter on the card). It has no onCloseAutoFocus and does not call the hook. Closing it leaves focus on the page.
Not card dialogs (do not add the hook):
MoveHereDialog(picker)CreateLinkTypeDialog(opened from the note, must return into the note)StatusListDialog(column)- Board rename / delete / settings
Refresh from GitHub is a menu action, not a dialog.
4. Inline errors and pending labels
No shared FormError exists. These six places render a destructive sentence by hand with className="text-destructive text-sm":
src/components/Boards/RenameBoardDialog.tsx:132src/components/Modals/CreateLinkTypeDialog.tsx:187src/components/Boards/BoardSettingsDialog.tsx:504src/components/Modals/StatusListDialog.tsx:167src/components/ui/editable-url-item.tsx:260src/components/Modals/MoveToAnotherBoardDialog.tsx:397
Unconfirmed / unknown-outcome copy stays text-warning. Do not wrap those.
Pending labels are written by hand:
CreateBoardForm:Creating…/Create BoardRenameBoardDialog:Renaming…/RenameDeleteBoardDialog:Deleting…/Delete BoardDeleteAccountDialog:Deleting…/Delete AccountCreateLinkTypeDialog:Creating…/CreateNoteModal:Saving…/SaveMoveHereDialog:Moving…/Move Here
Closed pending pairs (every one of these must call pendingButtonLabel):
| File | idle | pending |
|---|---|---|
src/app/boards/new/CreateBoardForm.tsx |
Create Board | Creating… |
src/components/Boards/RenameBoardDialog.tsx |
Rename | Renaming… |
src/components/Boards/DeleteBoardDialog.tsx |
Delete Board | Deleting… |
src/components/Modals/DeleteAccountDialog.tsx |
Delete Account | Deleting… |
src/components/Modals/CreateLinkTypeDialog.tsx |
Create | Creating… |
src/components/Modals/NoteModal.tsx |
Save | Saving… |
src/components/Board/MoveHereDialog.tsx |
Move Here | Moving… |
src/components/Modals/CreateDestinationBoardForm.tsx |
Create Board | Creating… |
src/components/Modals/MoveToAnotherBoardDialog.tsx |
Move | Moving… |
MoveToAnotherBoardDialog and CreateDestinationBoardForm keep their Loader2 spinner. Only the visible string goes through pendingButtonLabel. Do not add the helper to Reload page / Reload boards.
5. /settings has no sidebar
src/app/settings/layout.tsx renders ThemeApplicator and {children} only. /boards and /account render Sidebar + main as siblings of one flex row (BoardsLayoutClient, AccountLayoutClient) so a sidebar that lies over the page can set inert on the page. /settings does not. e2e/logged-in/sidebar-narrow-viewport.spec.ts opens the overlay on a board and /account only (lines 108-109), not /settings.
AuthenticatedLayout exists but lacks ThemeApplicator and areServerRenderedPropsEqual. Do not reuse it as-is.
6. Column menu has no separators
OverflowMenu puts DropdownMenuSeparator between link items and actions, and again before Remove. StatusColumn.tsx:155-171 lists Edit Column then Delete Column with no separator.
7. Picker visibility is a native <select>
AddRepositoryCombobox.tsx:613-644 uses Radix Select for organization. Lines 653-666 use a native <select> for visibility (All / Public / Private).
8. Star counts are an emoji
AddRepositoryCombobox.tsx:819 and :872 render <span>⭐ {repo.stargazers_count}</span>. Favorite stars elsewhere use lucide Star (BoardCard.tsx).
Proposed Change
One PR. One heading deleted. Version PATCH 0.7.21.0 at /ship.
1. Replace every transition-all
Replace each site with the properties that change (table above). After the change, rg 'transition-all' --glob '*.tsx' src prints nothing. Do not add transition-all in tests as a positive class.
Suggested mappings (use these unless a site only changes a subset):
- CommentDisplay:
transition-colors - BoardCard surface:
transition-[border-color,box-shadow] - BoardCard star button:
transition-[opacity,color] - NewRowDropZone / ColumnInsertZone:
transition-colors - RepoCard:
transition-shadow - font-color-toolbar-button:
transition-transform - KanbanPreview cards:
transition-colors - StatusListDialog swatch:
transition-[border-color,transform] - BoardSettingsDialog tabs / chips:
transition-colors - SortableColumn / SortableBoardCard:
transition-[transform,opacity,box-shadow]
2. Stop the sidebar width animation from 768px
On the <aside> in Sidebar.tsx, keep transition-[width] duration-300 ease-out below md, and add md:transition-none. Do not remove overflow-hidden whitespace-nowrap. Do not change the rail / overlay behavior below 768px. Existing sidebar-narrow-viewport measurements stay: rail 64px, page beside it, overlay does not move main.
3. NoteModal uses useCardDialogFocusReturn
NoteModal receives cardId. Call useCardDialogFocusReturn(cardId) and pass handleCloseAutoFocus to the dialog content's onCloseAutoFocus. The note stays on the card, so do not call rememberCardSuccessor. Cancel / overlay / Escape / successful save all close through that handler. Focus lands on the card (data-repo-card-id).
4. Shared FormError and pending label helper
Add:
src/components/ui/form-error.tsx—FormError. Renders nothing whenmessageis empty. Otherwise<p role="alert" className="text-destructive text-sm">{message}</p>. Wrap inmemowith no server-rendered compare (client-only, not rendered by a Server Component layout).src/lib/utils/pending-button-label.ts—pendingButtonLabel(isPending, idle, pending)returnspendingwhenisPendingis true, elseidle. Idle strings stay Title Case commands (Create Board,Rename). Pending strings stay the existing ellipsis forms (Creating…).
Replace the six destructive hand-built <p className="text-destructive text-sm"> with <FormError message={...} />. Replace the listed idle/pending ternary labels with pendingButtonLabel. Leave text-warning unconfirmed copy alone. Leave disabled={isPending} as it is.
5. Settings layout matches account / boards
Add src/app/settings/SettingsLayoutClient.tsx copied from AccountLayoutClient (ThemeApplicator, flex h-screen, Sidebar sibling of main, areServerRenderedPropsEqual). settings/layout.tsx calls requireClaims(), reads getClaimsDisplayInfo(claims), and renders that client. page.tsx stays a settings form. The settings page keeps its current fields. src/tests/unit/server-rendered-client-components.test.ts must list the new client. Add /settings to the overlay table in e2e/logged-in/sidebar-narrow-viewport.spec.ts next to /account.
6. Column menu separator
In StatusColumn.tsx, put <DropdownMenuSeparator /> between Edit Column and Delete Column.
7. Visibility filter is Radix Select
Replace the native <select> with the same Radix Select used for organization: h-9, aria-label="Visibility filter", items All / Public / Private with the same values (all / public / private). Keep flex-1 on the organization trigger. Visibility trigger is a sibling, not flex-1 unless the current native control already grows — match the current width behavior (auto width is fine).
8. Star counts use lucide Star
Replace ⭐ with <Star className="h-3 w-3" /> in a flex row with the count. No fill. Color inherits the row (OPTION_ROW_COLOR_CLASS already sets text-accent-foreground on the accent fill). Do not use text-favorite (that token is the favorite star). After the change, rg '⭐' src prints nothing.
Acceptance Criteria
rg 'transition-all' --glob '*.tsx' srcis empty.- Sidebar
<aside>hasmd:transition-noneand still hastransition-[width]for the overlay. - Closing Note (Cancel, Escape, overlay, Save) returns focus to the card that opened it.
- The six destructive inline errors render through
FormError. Unconfirmed warnings are unchanged. - The listed pending labels go through
pendingButtonLabel. /settingsshows the same sidebar chrome as/account(logo, Boards, Favorites, Settings, Theme, account, Sign Out). Below 768px the rail is 64px and the overlay setsinertonmain.- Column options menu has a separator between Edit Column and Delete Column.
- Visibility filter is a Radix select (role combobox), not a native
<select>. - Picker star counts have no
⭐character; they use lucideStar. - Delete
TODOS.mdfrom the line### Smaller design gaps left from the 2026-10-12 reviewthrough the line**Depends on:** None(inclusive). Keep the parent## Design review of 2026-10-12: deferred design debtsection and the## Completedsection. - The numbered acceptance list is the scope. Each test covers only the slice in its
protectsline. Green tests do not replace the list. - Each new test states
protects=…; fails_when=…; no_claim=…. - Live checks, split:
- Fixture E2E (
pnpm e2e:parallel): Public hidestestuser/private-project; Private shows it; Note Escape focuses fixture card 1;/settingshas the sidebar. - Local owner (
ryota-murakamionhttp://localhost:3008, a board with cards in at least two columns): settings sidebar; Note Escape/Cancel return focus; column menu separator; Visibility is a combobox and Public/Private still filter the owner's catalog; at 1280px the aside class list includesmd:transition-none(it may still includetransition-[width]for the overlay). - Production after deploy, Laststance cards read-only: settings sidebar visible; do not change the stored theme; restore any Compact / metadata toggle you flip. Do not Sign Out. Do not run account deletion.
- Version at
/ship: writeVERSIONto0.7.21.0,package.jsonversion to0.7.21, and aCHANGELOG.md0.7.21.0entry. Ifmain'sVERSIONis no longer0.7.20.0when shipping, bump one PATCH step from whateverVERSIONis then (N.N.N.0).
Testing Plan
| Layer | What | Count |
|---|---|---|
| Unit | Source lock: no transition-all in src/**/*.tsx |
+1 |
| Unit | pendingButtonLabel(true/false) |
+2 |
| Unit | FormError hidden when message empty; role="alert" + text-destructive when set |
+2 |
| Unit | SettingsLayoutClient listed in server-rendered client components | existing test grows |
| Story | NoteModal: Cancel, overlay click, and mocked Save each return focus to the card stand-in | +3 |
| Unit | Column menu source lock: StatusColumn.tsx contains DropdownMenuSeparator |
+1 |
| Unit | Picker source lock: AddRepositoryCombobox.tsx has no ⭐ and no native <select |
+1 |
| Unit | Sidebar aside class includes md:transition-none and transition-[width] |
+1 (or extend existing Sidebar test) |
| E2E | /settings has data-testid=sidebar-place and a Boards link |
+1 |
| E2E | sidebar-narrow-viewport overlay table includes /settings |
+1 (row) |
| E2E | Visibility filter is getByRole('combobox', { name: 'Visibility filter' }); page has no select[aria-label="Visibility filter"]; Public hides testuser/private-project; Private shows it |
+1 |
| E2E | Picker row for seed A test repository for GitBox has no ⭐ and shows 42 |
+1 |
| E2E | Open note on fixture card 1, Escape, that card is focused | +1 |
CI gates class names and the flows above. Desktop width animation is accepted by md:transition-none plus the existing Sidebar unit. Do not add a video job to CI. Live verify records collapse at 1280px (page may jump; it must not tween).
protects / fails_when / no_claim examples:
- transition-all lock:
protects=no leftover catch-all transition; fails_when=any src tsx still has transition-all; no_claim=the animation looks right - settings sidebar:
protects=/settings has the signed-in sidebar; fails_when=sidebar-place is missing; no_claim=every settings toggle - note focus E2E:
protects=Escape from Note returns to the card; fails_when=focus is on the page; no_claim=Cancel, overlay, or Save - note focus stories:
protects=Cancel / overlay / Save return focus; fails_when=focus is on the page; no_claim=the server stored the note - visibility:
protects=visibility is a Radix select and Public hides private-project; fails_when=native select remains or private-project stays under Public; no_claim=every org filter
Rules for the implementing agent
- The numbered acceptance list is the product scope. Each test's
protects / fails_when / no_claimis only the slice that test proves. Never "make the tests pass". A green suite does not replace the list. - Real code and real tests land in the same change.
- Any change to a test, validator, CI step, timeout, tolerance, snapshot or suppression is reported on its own line.
- A reported command names the command and its observed result.
- Your report is a claim until the parent re-executes it.
Rollback Plan
Revert the PR. No schema, no cookies, no env. A settings layout revert returns /settings to a page without a sidebar.
Effort Estimate
- transitions + sidebar: 1h
- Note focus: 1h
- FormError + pending helper + call sites: 1.5h
- Settings layout + e2e: 1.5h
- Column separator + picker Select + Star icon: 1h
- Tests + local/prod verify: 2h
Files Reference
| File | Change |
|---|---|
src/components/Board/CommentDisplay.tsx |
transition-colors |
src/components/Boards/BoardCard.tsx |
named transitions |
src/components/Board/NewRowDropZone.tsx |
transition-colors |
src/components/Board/RepoCard.tsx |
transition-shadow |
src/components/ui/font-color-toolbar-button.tsx |
transition-transform |
src/app/(landing)/KanbanPreview.tsx |
transition-colors |
src/components/Modals/StatusListDialog.tsx |
named transition; FormError |
src/components/Boards/BoardSettingsDialog.tsx |
named transitions; FormError |
src/components/Board/SortableColumn.tsx |
named transition |
src/components/Boards/SortableBoardCard.tsx |
named transition |
src/components/Board/ColumnInsertZone.tsx |
transition-colors |
src/components/Sidebar/Sidebar.tsx |
md:transition-none |
src/components/Modals/NoteModal.tsx |
useCardDialogFocusReturn |
src/components/ui/form-error.tsx |
new |
src/lib/utils/pending-button-label.ts |
new |
src/app/settings/SettingsLayoutClient.tsx |
new |
src/app/settings/layout.tsx |
wrap with sidebar client |
src/components/Board/StatusColumn.tsx |
separator |
src/components/Board/AddRepositoryCombobox.tsx |
Radix visibility + lucide Star |
TODOS.md |
delete only the smaller-design-gaps heading |
e2e/logged-in/sidebar-narrow-viewport.spec.ts |
/settings row |
e2e/logged-in/settings.spec.ts or new spec |
sidebar + focus + picker locks as listed |
Out of Scope
- Splitting the seven items into multiple PRs
- Rewriting
AuthenticatedLayoutto replace the three layout clients - Changing sidebar width values (64 / 256)
- Changing comment Save color,
--link,--warning,--favorite - Account deletion, Sign Out, production writes on Laststance / Hobby / Complete / Practice cards
- New settings fields
- Animating something other than the listed properties
- Replacing every
disabled={isPending}with a new button component
Related
- Maintainnaiceモードの廃止 #213 — design review source
- Stop using the theme color and the muted color where they cannot be read #330 / v0.7.20.0 feat: add a readable theme color for text on surfaces #331 — previous design-debt heading (theme link token), already shipped
Activity
ryota-murakami commentedon Oct 18, 2026
Closed after PR #333 (v0.7.21.0) merged. The leftover 2026-10-12 design gaps are in production via that PR.