Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.7.17.0] - 2026-10-18

### Changed

- Account now shows Profile, Account Data, and Danger Zone as sections of one page. The two counts sit in a row without a tile around each.

## [0.7.16.0] - 2026-10-18

### Changed
Expand Down
12 changes: 0 additions & 12 deletions TODOS.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,6 @@ From the `/ship` review of `fix/owner-filter-rollback-guard-sentry-tunnel`. None

From `/design-review` of the change for issue #213. The review found 27 issues and fixed 21; these are what is left. All of them are in the app as it was before that change. The full report with screenshots is in `~/.gstack/projects/laststance-gitbox/designs/design-audit-20261012/`.

### Lay out the account page as sections, not stacked cards

**What:** Show Profile, Account Data and Danger Zone as sections of one page, and the two numbers of Account Data without a bordered tile each.

**Why:** Medium impact, AI slop (FINDING-022). Three cards under each other with tiles inside one is the layout a generator produces; the page has nothing to compare side by side that would need cards.

**Context:** `src/app/account/AccountClient.tsx` and its skeleton `src/app/account/loading.tsx`, which mirrors the layout block by block and has to change with it. One difference is left between the two: below about 434px the label "Repository Cards" takes two lines and the skeleton shows one, so the two tiles grow by a line when the data arrives.

**Effort:** S
**Priority:** P4
**Depends on:** None

### Use the ellipsis character in loading labels

**What:** Write "…" instead of three dots in the labels that say something is in progress ("Loading repositories...", "Signing out...", "Creating..."), about 27 strings in `src`.
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.7.16.0
0.7.17.0
66 changes: 53 additions & 13 deletions e2e/logged-in/account-profile.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,31 +58,71 @@ test.describe('Account Profile Page (Authenticated)', () => {
}
})

test('shows the Boards and Repository Cards tiles side by side, equally wide', async ({
test('shows Profile, Account Data and Danger Zone as page sections', async ({
page,
}) => {
// Arrange
const main = page.getByRole('main')
const boardsTile = main.getByText('Boards', { exact: true }).locator('..')
const repositoryCardsTile = main.getByText('Repository Cards').locator('..')
await expect(boardsTile).toBeVisible({ timeout: 10000 })
await expect(
page.getByRole('heading', { name: 'Account', level: 1 }),
).toBeVisible({ timeout: 10000 })

// Assert
await expect(main.locator('section')).toHaveCount(3)
await expect(
main.getByRole('heading', { name: 'Profile', level: 2 }),
).toBeVisible()
await expect(
main.getByRole('heading', { name: 'Account Data', level: 2 }),
).toBeVisible()
await expect(
main.getByRole('heading', { name: 'Danger Zone', level: 2 }),
).toBeVisible()
})

test('shows the Boards and Repository Cards numbers in one row without a tile border', async ({
page,
}) => {
// Arrange
const boardsCell = page.getByTestId('account-stat-boards')
const cardsCell = page.getByTestId('account-stat-cards')
await expect(boardsCell).toBeVisible({ timeout: 10000 })
await expect(cardsCell).toBeVisible()

// Act
const boardsBox = await boardsTile.boundingBox()
const repositoryCardsBox = await repositoryCardsTile.boundingBox()
const boardsBox = await boardsCell.boundingBox()
const cardsBox = await cardsCell.boundingBox()

// Assert: one row, the second tile to the right of the first
// Assert: one row, the second cell to the right of the first, no tile border
expect(boardsBox).not.toBeNull()
expect(repositoryCardsBox).not.toBeNull()
expect(repositoryCardsBox!.y).toBe(boardsBox!.y)
expect(repositoryCardsBox!.x).toBeGreaterThanOrEqual(
boardsBox!.x + boardsBox!.width,
expect(cardsBox).not.toBeNull()
expect(cardsBox!.y).toBe(boardsBox!.y)
expect(cardsBox!.x).toBeGreaterThanOrEqual(boardsBox!.x + boardsBox!.width)
expect(Math.round(cardsBox!.width)).toBe(Math.round(boardsBox!.width))
expect(await boardsCell.getAttribute('class')).not.toMatch(
/(?:^|\s)border(?:-|\s|$)/,
)
expect(Math.round(repositoryCardsBox!.width)).toBe(
Math.round(boardsBox!.width),
expect(await cardsCell.getAttribute('class')).not.toMatch(
/(?:^|\s)border(?:-|\s|$)/,
)
})

test('keeps both Account Data labels visible on a 375-wide screen', async ({
page,
}) => {
// Arrange
await page.setViewportSize({ width: 375, height: 812 })
await page.goto('/account')
const boardsLabel = page.getByRole('main').getByText('Boards', {
exact: true,
})
const cardsLabel = page.getByRole('main').getByText('Repository Cards')

// Assert
await expect(boardsLabel).toBeVisible({ timeout: 10000 })
await expect(cardsLabel).toBeVisible()
})

test('should open delete confirmation dialog', async ({ page }) => {
// Click Delete Account button
const deleteButton = page.getByTestId('delete-account-button')
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "gitbox",
"version": "0.7.16",
"version": "0.7.17",
"private": true,
"type": "module",
"description": "A web application for managing GitHub repositories in Kanban board format",
Expand Down
19 changes: 17 additions & 2 deletions src/app/account/AccountClient.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,23 @@ export const Default: Story = {
{ timeout: 3000 },
)

expect(canvas.getByText('Boards')).toBeInTheDocument()
expect(canvas.getByText('2', { exact: true })).toBeInTheDocument()
expect(
canvas.getByRole('heading', { name: 'Profile', level: 2 }),
).toBeVisible()
expect(
canvas.getByRole('heading', { name: 'Account Data', level: 2 }),
).toBeVisible()
expect(
canvas.getByRole('heading', { name: 'Danger Zone', level: 2 }),
).toBeVisible()
expect(canvas.getByText('Boards')).toBeVisible()
expect(canvas.getByText('2', { exact: true })).toBeVisible()
expect(canvas.getByTestId('account-stat-boards').className).not.toMatch(
/(?:^|\s)border(?:-|\s|$)/,
)
expect(canvas.getByTestId('account-stat-cards').className).not.toMatch(
/(?:^|\s)border(?:-|\s|$)/,
)
},
}

Expand Down
188 changes: 94 additions & 94 deletions src/app/account/AccountClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,6 @@ import { GithubIcon } from '@/components/icons/GithubIcon'
import { LocalDate } from '@/components/LocalDate'
import { DeleteAccountDialog } from '@/components/Modals/DeleteAccountDialog'
import { Button } from '@/components/ui/button'
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card'
import { signOut } from '@/lib/actions/auth'
import { forgetAccountOnThisBrowser } from '@/lib/redux/forget-account-on-this-browser'
import { store } from '@/lib/redux/store'
Expand Down Expand Up @@ -70,103 +63,110 @@ export const AccountClient = memo(function AccountClient({
</p>
</div>

<div className="space-y-6">
{/* Profile Section */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<div className="space-y-8">
<section className="space-y-4">
<div className="space-y-1.5">
<h2 className="flex items-center gap-2 text-2xl leading-none font-semibold tracking-tight">
<GithubIcon className="h-5 w-5" />
Profile
</CardTitle>
<CardDescription>Your GitHub account information</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center gap-4">
{userProfile.userAvatar ? (
<Image
src={userProfile.userAvatar}
alt={userProfile.userName}
width={64}
height={64}
className="h-16 w-16 rounded-full"
/>
) : (
<div className="bg-muted flex h-16 w-16 items-center justify-center rounded-full">
<GithubIcon className="text-muted-foreground h-8 w-8" />
</div>
)}
<div>
<p className="text-lg font-semibold">@{userProfile.userName}</p>
<p className="text-muted-foreground text-sm">
Connected via GitHub
</p>
</h2>
<p className="text-muted-foreground text-sm">
Your GitHub account information
</p>
</div>
<div className="flex items-center gap-4">
{userProfile.userAvatar ? (
<Image
src={userProfile.userAvatar}
alt={userProfile.userName}
width={64}
height={64}
className="h-16 w-16 rounded-full"
/>
) : (
<div className="bg-muted flex h-16 w-16 items-center justify-center rounded-full">
<GithubIcon className="text-muted-foreground h-8 w-8" />
</div>
</div>
<div className="border-t pt-4">
)}
<div>
<p className="text-lg font-semibold">@{userProfile.userName}</p>
<p className="text-muted-foreground text-sm">
Linked since{' '}
<LocalDate isoDate={userProfile.linkedSince} dateStyle="long" />
Connected via GitHub
</p>
</div>
</CardContent>
</Card>
</div>
<div className="border-t pt-4">
<p className="text-muted-foreground text-sm">
Linked since{' '}
<LocalDate isoDate={userProfile.linkedSince} dateStyle="long" />
</p>
</div>
</section>

{/* Account Data Section */}
<Card>
<CardHeader>
<CardTitle>Account Data</CardTitle>
<CardDescription>Summary of your GitBox data</CardDescription>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 gap-4">
<div className="flex flex-col items-center rounded-lg border p-4">
<LayoutDashboard className="text-muted-foreground mb-2 h-6 w-6" />
<span className="text-2xl font-bold">
{accountData.boardsCount}
</span>
<span className="text-muted-foreground text-sm">Boards</span>
</div>
<div className="flex flex-col items-center rounded-lg border p-4">
<FileText className="text-muted-foreground mb-2 h-6 w-6" />
<span className="text-2xl font-bold">
{accountData.cardsCount}
</span>
<span className="text-muted-foreground text-sm">
Repository Cards
</span>
</div>
<section className="space-y-4">
<div className="space-y-1.5">
<h2 className="text-2xl leading-none font-semibold tracking-tight">
Account Data
</h2>
<p className="text-muted-foreground text-sm">
Summary of your GitBox data
</p>
</div>
<div className="grid grid-cols-2 gap-4">
<div
className="flex flex-col items-start gap-1"
data-testid="account-stat-boards"
>
<LayoutDashboard className="text-muted-foreground h-6 w-6" />
<span className="text-2xl font-bold">
{accountData.boardsCount}
</span>
<span className="text-muted-foreground min-h-[2lh] text-sm leading-5">
Boards
</span>
</div>
</CardContent>
</Card>
<div
className="flex flex-col items-start gap-1"
data-testid="account-stat-cards"
>
<FileText className="text-muted-foreground h-6 w-6" />
<span className="text-2xl font-bold">
{accountData.cardsCount}
</span>
<span className="text-muted-foreground min-h-[2lh] text-sm leading-5">
Repository Cards
</span>
</div>
</div>
</section>

{/* Danger Zone Section */}
<Card className="border-destructive">
<CardHeader>
<CardTitle className="text-destructive">Danger Zone</CardTitle>
<CardDescription>
<section className="border-destructive space-y-4 border-t pt-6">
<div className="space-y-1.5">
<h2 className="text-destructive text-2xl leading-none font-semibold tracking-tight">
Danger Zone
</h2>
<p className="text-muted-foreground text-sm">
Irreversible actions that affect your account
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="border-destructive/20 bg-destructive/5 rounded-lg border p-4">
<p className="text-muted-foreground text-sm">
Deleting your account will permanently remove all your boards,
cards, notes, links, comments, and settings. This action cannot
be undone.
</p>
<p className="text-foreground mt-2 text-sm font-medium">
Your GitHub repositories will NOT be affected.
</p>
</div>
<Button
variant="destructive"
onClick={handleOpenDeleteDialog}
data-testid="delete-account-button"
>
Delete Account
</Button>
</CardContent>
</Card>
</p>
</div>
<div className="border-destructive/20 bg-destructive/5 rounded-lg border p-4">
<p className="text-muted-foreground text-sm">
Deleting your account will permanently remove all your boards,
cards, notes, links, comments, and settings. This action cannot be
undone.
</p>
<p className="text-foreground mt-2 text-sm font-medium">
Your GitHub repositories will NOT be affected.
</p>
</div>
<Button
variant="destructive"
onClick={handleOpenDeleteDialog}
data-testid="delete-account-button"
>
Delete Account
</Button>
</section>

{/* Actions */}
<div className="flex justify-between pt-4">
Expand Down
Loading
Loading