Skip to content

Close the remaining 2026-10-12 design gaps #332

Closed

Description

@ryota-murakami

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:

  • MoveToAnotherBoardDialog
  • OverflowMenu Remove 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:132
  • src/components/Modals/CreateLinkTypeDialog.tsx:187
  • src/components/Boards/BoardSettingsDialog.tsx:504
  • src/components/Modals/StatusListDialog.tsx:167
  • src/components/ui/editable-url-item.tsx:260
  • src/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 Board
  • RenameBoardDialog: Renaming… / Rename
  • DeleteBoardDialog: Deleting… / Delete Board
  • DeleteAccountDialog: Deleting… / Delete Account
  • CreateLinkTypeDialog: Creating… / Create
  • NoteModal: Saving… / Save
  • MoveHereDialog: 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 when message is empty. Otherwise <p role="alert" className="text-destructive text-sm">{message}</p>. Wrap in memo with no server-rendered compare (client-only, not rendered by a Server Component layout).
  • src/lib/utils/pending-button-label.ts — pendingButtonLabel(isPending, idle, pending) returns pending when isPending is true, else idle. 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

  1. rg 'transition-all' --glob '*.tsx' src is empty.
  2. Sidebar <aside> has md:transition-none and still has transition-[width] for the overlay.
  3. Closing Note (Cancel, Escape, overlay, Save) returns focus to the card that opened it.
  4. The six destructive inline errors render through FormError. Unconfirmed warnings are unchanged.
  5. The listed pending labels go through pendingButtonLabel.
  6. /settings shows the same sidebar chrome as /account (logo, Boards, Favorites, Settings, Theme, account, Sign Out). Below 768px the rail is 64px and the overlay sets inert on main.
  7. Column options menu has a separator between Edit Column and Delete Column.
  8. Visibility filter is a Radix select (role combobox), not a native <select>.
  9. Picker star counts have no ⭐ character; they use lucide Star.
  10. Delete TODOS.md from the line ### Smaller design gaps left from the 2026-10-12 review through the line **Depends on:** None (inclusive). Keep the parent ## Design review of 2026-10-12: deferred design debt section and the ## Completed section.
  11. The numbered acceptance list is the scope. Each test covers only the slice in its protects line. Green tests do not replace the list.
  12. Each new test states protects=…; fails_when=…; no_claim=….
  13. Live checks, split:
  • Fixture E2E (pnpm e2e:parallel): Public hides testuser/private-project; Private shows it; Note Escape focuses fixture card 1; /settings has the sidebar.
  • Local owner (ryota-murakami on http://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 includes md:transition-none (it may still include transition-[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.
  1. Version at /ship: write VERSION to 0.7.21.0, package.json version to 0.7.21, and a CHANGELOG.md 0.7.21.0 entry. If main's VERSION is no longer 0.7.20.0 when shipping, bump one PATCH step from whatever VERSION is 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

  1. The numbered acceptance list is the product scope. Each test's protects / fails_when / no_claim is only the slice that test proves. Never "make the tests pass". A green suite does not replace the list.
  2. Real code and real tests land in the same change.
  3. Any change to a test, validator, CI step, timeout, tolerance, snapshot or suppression is reported on its own line.
  4. A reported command names the command and its observed result.
  5. 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 AuthenticatedLayout to 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

Activity

  1. ryota-murakami commented on Oct 18, 2026

    @ryota-murakami
    ContributorAuthor

    Closed after PR #333 (v0.7.21.0) merged. The leftover 2026-10-12 design gaps are in production via that PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions