Skip to content

fix: UI polish foundation — shared utils, component tweaks, accessibility pass#71

Merged
TerrifiedBug merged 16 commits intomainfrom
fix/ui-polish-foundation
Mar 8, 2026
Merged

fix: UI polish foundation — shared utils, component tweaks, accessibility pass#71
TerrifiedBug merged 16 commits intomainfrom
fix/ui-polish-foundation

Conversation

@TerrifiedBug
Copy link
Copy Markdown
Owner

Summary

Phase 1 of the UI polish full sweep (Vercel/Linear aesthetic).

  • Shared badge utilities (src/lib/badge-variants.ts): Extracted tagBadgeClass and reductionBadgeClass from duplicated inline functions across 2 files into a single shared module
  • Badge sm size variant: Added a compact size option (text-[11px] px-1.5 py-0) to replace ad-hoc text-[10px] hacks in table cells
  • shadcn component tweaks: rounded-smrounded-md on menu items (select + dropdown), darker dialog overlay (bg-black/80), larger tooltip text (text-sm), flat cards (removed shadow-sm)
  • Accessibility & interaction pass (13 files): Added missing cursor-pointer to all plain interactive buttons, aria-label to icon-only buttons, transition-colors to hover states, fixed double labeling in theme toggle

Test plan

  • Verify badges render identically (no visual regression from size variant refactor)
  • Open any dropdown/select menu — items should have rounder corners
  • Open any dialog — overlay should be noticeably darker
  • Hover over tooltips — text should be slightly larger than before
  • Cards should appear flat (no shadow)
  • Hover over custom buttons (popover triggers, tab buttons) — cursor should show pointer
  • Tab through interactive elements — focus rings should be visible

@github-actions github-actions bot added the fix label Mar 8, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 8, 2026

Greptile Summary

This PR is a broad UI polish pass across 47 files, implementing three categories of changes: (1) extracting duplicated badge color utilities into a new src/lib/badge-variants.ts shared module, (2) tweaking shadcn/ui primitives (rounder menu items, darker overlay, larger tooltip text, flat cards, new sm badge size), and (3) an accessibility/interaction sweep adding cursor-pointer, aria-label, and transition-colors across 13 components.

Key findings:

  • The new PageHeader component hardcodes mb-6, which is redundant with the space-y-6 wrapping all its call sites — in a flex-column context this doubles the vertical gap to 48 px between the header and the first content block.
  • statusBadgeClass is exported from badge-variants.ts but never imported anywhere; it's dead code as shipped.
  • The fleet node breadcrumb (Fleet /) renders a trailing slash without a current-page label — the node name renders in a sibling div at gap-4 distance, visually detaching it from the separator. The parallel environments/[id] breadcrumb correctly includes the page name inline.
  • The sr-only span removal in ThemeToggle is correct: aria-label="Toggle theme" already existed on the Button, so the span was genuinely double-labeling.
  • The change-password-dialog form-reset logic (handleOpenChange) is correct — forced dialogs continue to ignore the handler since onOpenChange is set to undefined.

Confidence Score: 4/5

  • Safe to merge — all findings are minor visual/style issues with no functional or security impact.
  • No logic errors, security issues, or data-loss risks introduced. Three minor issues: an unused export in the new shared module, a hardcoded margin that may double spacing in certain layout contexts, and an incomplete breadcrumb on the fleet node detail page.
  • src/components/page-header.tsx (hardcoded mb-6), src/lib/badge-variants.ts (unused statusBadgeClass), src/app/(dashboard)/fleet/[nodeId]/page.tsx (trailing slash in breadcrumb)

Important Files Changed

Filename Overview
src/lib/badge-variants.ts New shared module extracting tagBadgeClass and reductionBadgeClass from duplicate inline functions — correct consolidation, but adds an unused statusBadgeClass export that has no consumer in this PR.
src/components/page-header.tsx New shared PageHeader component standardizing page titles across all dashboard pages — the hardcoded mb-6 can double vertical spacing when the parent uses space-y-6 in a flex column context.
src/app/(dashboard)/fleet/[nodeId]/page.tsx Replaces back-button with a breadcrumb, but the breadcrumb renders a trailing "/" with no current-page segment — the node name lives in a separate sibling div with a larger gap, visually disconnecting the separator from the page label.
src/components/ui/badge.tsx Adds a compact sm size variant by moving padding/text classes from the base into variant-aware slots — clean refactor with correct VariantProps usage.
src/components/theme-toggle.tsx Removes the redundant sr-only span; aria-label="Toggle theme" already existed on the Button, so this correctly eliminates duplicate accessible labeling.
src/components/change-password-dialog.tsx Adds handleOpenChange to clear form fields on close — forced dialogs correctly remain unaffected since onOpenChange is set to undefined in that branch.
src/components/ui/dialog.tsx Darkens dialog overlay from bg-black/50 to bg-black/80 — intentional visual change, no functional impact.
src/components/ui/dropdown-menu.tsx Bumps all menu item corners from rounded-sm to rounded-md globally across MenuItem, CheckboxItem, RadioItem, and SubTrigger — consistent with PR goal.
src/components/ui/card.tsx Removes shadow-sm to produce flat cards per the Vercel/Linear aesthetic goal — straightforward and correct.
src/app/(dashboard)/pipelines/page.tsx Migrates to shared tagBadgeClass/reductionBadgeClass from badge-variants, adopts new size="sm" Badge variant, and adds tabular-nums to metric columns — all clean changes.
src/components/flow/pipeline-settings.tsx Replaces local tagBadgeClass duplicate with the shared import from badge-variants — correct deduplication with no behavioral change.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    BV["src/lib/badge-variants.ts\n(new shared module)"]
    BV -->|tagBadgeClass| PP["pipelines/page.tsx"]
    BV -->|tagBadgeClass| PS["flow/pipeline-settings.tsx"]
    BV -->|reductionBadgeClass| PP

    BC["ui/badge.tsx\n+ size='sm' variant"]
    BC -->|size='sm'| PP
    BC -->|size='sm'| Other["other consumers"]

    PH["page-header.tsx\n(new component)"]
    PH --> D["page.tsx (Dashboard)"]
    PH --> P["pipelines/page.tsx"]
    PH --> F["fleet/page.tsx"]
    PH --> FN["fleet/[nodeId]/page.tsx"]
    PH --> A["analytics/page.tsx"]
    PH --> AU["audit/page.tsx"]
    PH --> E["environments/page.tsx"]
    PH --> ED["environments/[id]/page.tsx"]
    PH --> AL["alerts/page.tsx"]
    PH --> PR["profile/page.tsx"]
    PH --> S["settings/page.tsx"]
    PH --> T["templates/page.tsx"]

    UI["shadcn/ui tweaks"]
    UI --> Card["card.tsx: -shadow-sm"]
    UI --> Dialog["dialog.tsx: overlay /80"]
    UI --> DM["dropdown-menu.tsx: rounded-md"]
    UI --> Sel["select.tsx: rounded-md"]
    UI --> TT["tooltip.tsx: text-sm"]
Loading

Last reviewed commit: 0f94c80

@TerrifiedBug
Copy link
Copy Markdown
Owner Author

@greptile review

- Pipeline editor: keep skeleton loading + correct -mx-6 -my-2 margins
- Pipelines list: keep Labels popover column from main, apply Badge size="sm"
- Settings: keep single-team display logic from main, add cursor-pointer
Comment on lines +94 to 97
<div className="px-3 pb-3 space-y-2 group-data-[collapsible=icon]:hidden">
<TeamSelector />
<EnvironmentSelector />
</div>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Environment selector inaccessible when sidebar is collapsed to icon mode.

The EnvironmentSelector was moved from the top header bar (always visible) into the sidebar under a group-data-[collapsible=icon]:hidden wrapper. When a user collapses the sidebar to icon-only mode, the EnvironmentSelector becomes entirely hidden with no fallback. Previously the environment selector lived in the header and was always accessible regardless of sidebar state.

Users who collapse the sidebar to save space will have no way to switch environments without first expanding the sidebar. This is a regression from the original UX.

Consider either:

  • Keeping the environment selector in the header (restore to previous location)
  • Adding a fallback selector in the header when the sidebar is collapsed
  • Or updating the UI to show a toast/tooltip indicating users must expand the sidebar to change environments
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/components/app-sidebar.tsx
Line: 94-97

Comment:
Environment selector inaccessible when sidebar is collapsed to icon mode.

The `EnvironmentSelector` was moved from the top header bar (always visible) into the sidebar under a `group-data-[collapsible=icon]:hidden` wrapper. When a user collapses the sidebar to icon-only mode, the `EnvironmentSelector` becomes entirely hidden with no fallback. Previously the environment selector lived in the header and was always accessible regardless of sidebar state.

Users who collapse the sidebar to save space will have no way to switch environments without first expanding the sidebar. This is a regression from the original UX.

Consider either:
- Keeping the environment selector in the header (restore to previous location)
- Adding a fallback selector in the header when the sidebar is collapsed
- Or updating the UI to show a toast/tooltip indicating users must expand the sidebar to change environments

How can I resolve this? If you propose a fix, please make it concise.

When sidebar is collapsed, show icon buttons (Users, Layers) that
open popovers with the full TeamSelector and EnvironmentSelector.
Fixes selectors being inaccessible in collapsed mode.
Comment on lines +21 to +28
case "CRASHED": return "bg-status-error-bg text-status-error-foreground";
case "STOPPED": return "bg-status-neutral-bg text-status-neutral-foreground";
case "STARTING":
case "PENDING": return "bg-status-degraded-bg text-status-degraded-foreground";
default: return "bg-muted text-muted-foreground";
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused exported function

statusBadgeClass is exported here but never imported anywhere in the codebase (only tagBadgeClass and reductionBadgeClass are consumed by the pipeline pages). Dead exports in shared utility modules tend to drift out of sync with actual component usage over time. Either wire it up to a consumer in this PR or hold off on adding it until it's needed.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/badge-variants.ts
Line: 21-28

Comment:
**Unused exported function**

`statusBadgeClass` is exported here but never imported anywhere in the codebase (only `tagBadgeClass` and `reductionBadgeClass` are consumed by the pipeline pages). Dead exports in shared utility modules tend to drift out of sync with actual component usage over time. Either wire it up to a consumer in this PR or hold off on adding it until it's needed.

How can I resolve this? If you propose a fix, please make it concise.


export function PageHeader({ title, description, actions, className }: PageHeaderProps) {
return (
<div className={cn("flex items-start justify-between gap-4 mb-6", className)}>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded mb-6 doubles vertical spacing

PageHeader bakes in mb-6 (24 px), but every call site wraps it in a space-y-6 container. In a non-flex/non-grid block context adjacent margins collapse to the larger value (no-op here), but the moment a caller's parent is a flex column the margins stack, producing 48 px between the header and the next element instead of 24 px. The mb-6 is also redundant when the parent already governs spacing via space-y-*, and it prevents overriding the gap without fighting the hardcoded value.

Removing the mb-6 from the component and letting the parent's space-y-* do the work would be the safer approach:

Suggested change
<div className={cn("flex items-start justify-between gap-4 mb-6", className)}>
<div className={cn("flex items-start justify-between gap-4", className)}>
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/components/page-header.tsx
Line: 12

Comment:
**Hardcoded `mb-6` doubles vertical spacing**

`PageHeader` bakes in `mb-6` (24 px), but every call site wraps it in a `space-y-6` container. In a non-flex/non-grid block context adjacent margins collapse to the larger value (no-op here), but the moment a caller's parent is a flex column the margins stack, producing 48 px between the header and the next element instead of 24 px. The `mb-6` is also redundant when the parent already governs spacing via `space-y-*`, and it prevents overriding the gap without fighting the hardcoded value.

Removing the `mb-6` from the component and letting the parent's `space-y-*` do the work would be the safer approach:

```suggestion
    <div className={cn("flex items-start justify-between gap-4", className)}>
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines 241 to +247
<div className="flex items-center gap-4">
<Button variant="ghost" size="icon" onClick={() => router.push("/fleet")} aria-label="Back to fleet">
<ArrowLeft className="h-4 w-4" />
</Button>
<div className="flex items-center gap-1.5 text-sm">
<Link href="/fleet" className="text-muted-foreground hover:text-foreground transition-colors">
Fleet
</Link>
<span className="text-muted-foreground">/</span>
</div>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Breadcrumb renders a trailing slash with no current-page segment

The breadcrumb div ends with <span>/</span> but the node name <div> that follows it is a sibling in the outer flex items-center gap-4 container, not inside the gap-1.5 breadcrumb box. This means "/" sits 6 px from "Fleet" but 16 px from the node name, making the separator visually disconnected. Compare this to environments/[id]/page.tsx, where the current-page name ({env.name}) is included inside the same gap-1.5 row.

Since the node name div also contains the inline rename UI, the cleanest fix is to include just the static display name as the third breadcrumb segment and keep the rename widget as the page's <h1>. Alternatively, keep the current layout but remove the trailing slash so the breadcrumb reads "Fleet" rather than "Fleet /".

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/(dashboard)/fleet/[nodeId]/page.tsx
Line: 241-247

Comment:
**Breadcrumb renders a trailing slash with no current-page segment**

The breadcrumb div ends with `<span>/</span>` but the node name `<div>` that follows it is a sibling in the outer `flex items-center gap-4` container, not inside the `gap-1.5` breadcrumb box. This means "/" sits 6 px from "Fleet" but 16 px from the node name, making the separator visually disconnected. Compare this to `environments/[id]/page.tsx`, where the current-page name (`{env.name}`) is included inside the same `gap-1.5` row.

Since the node name div also contains the inline rename UI, the cleanest fix is to include just the static display name as the third breadcrumb segment and keep the rename widget as the page's `<h1>`. Alternatively, keep the current layout but remove the trailing slash so the breadcrumb reads "Fleet" rather than "Fleet /".

How can I resolve this? If you propose a fix, please make it concise.

@TerrifiedBug TerrifiedBug merged commit afaf2a0 into main Mar 8, 2026
9 checks passed
@TerrifiedBug TerrifiedBug deleted the fix/ui-polish-foundation branch March 8, 2026 16:47
TerrifiedBug added a commit that referenced this pull request Mar 8, 2026
PR #71 added `"postinstall": "prisma generate"` to package.json. This
breaks the Docker build because Stage 1 (deps) only copies package.json
and pnpm-lock.yaml before running pnpm install — prisma generate fails
when it can't find the schema. Copy prisma/ and prisma.config.ts into
the deps stage so the postinstall hook succeeds.
TerrifiedBug added a commit that referenced this pull request Mar 8, 2026
)

PR #71 added `"postinstall": "prisma generate"` to package.json. This
breaks the Docker build because Stage 1 (deps) only copies package.json
and pnpm-lock.yaml before running pnpm install — prisma generate fails
when it can't find the schema. Copy prisma/ and prisma.config.ts into
the deps stage so the postinstall hook succeeds.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant