# Checkpoints in the Identity grid + locale time + remove History — design

**Date:** 2026-04-23
**Scope:** three targeted UX changes on the unified app deployment page, follow-up to `2026-04-23-deployment-page-polish-design.md`.
**Status:** Draft — pending user review.

## 1. Motivation

The previous polish shipped a collapsible `CheckpointsTable` as a standalone section below the Identity & Artifact block. That made the visual hierarchy noisy — Checkpoints became a third section between Identity and the config tabs, competing for attention. The proper home for "how many past deployments exist and what were they" is *inside* the Identity panel, as one more row in its config grid.

Three changes:

1. Move the checkpoints section into the Identity & Artifact config grid as an in-grid row.
2. Format the Deployed-column sub-line to the user's locale (replaces the raw ISO string).
3. Remove the redundant `HistoryDisclosure` from the Deployment tab — the checkpoints table covers the same information and the per-deployment log drill-down now lives in the drawer.

## 2. Design

### 2.1 Checkpoints row in the Identity config grid

**Current structure** (`IdentitySection.tsx`):

```tsx
<div className={styles.section}>
  <SectionHeader>Identity & Artifact</SectionHeader>
  <div className={styles.configGrid}>
    ... label + value cells (Application Name, Slug, Environment, External URL, Current Version, Application JAR) ...
  </div>
  {children}   {/* CheckpointsTable + CheckpointDetailDrawer currently render here */}
</div>
```

**New structure:**

```tsx
<div className={styles.section}>
  <SectionHeader>Identity & Artifact</SectionHeader>
  <div className={styles.configGrid}>
    ... existing label + value cells ...
    {checkpointsSlot}   {/* NEW: rendered as direct grid children via React.Fragment */}
  </div>
  {children}   {/* still used — for the portal-rendered CheckpointDetailDrawer */}
</div>
```

**Slot contract.** `IdentitySection` gains a new prop:

```ts
interface IdentitySectionProps {
  // ... existing props ...
  checkpointsSlot?: ReactNode;
  children?: ReactNode;
}
```

`checkpointsSlot` is expected to be a React.Fragment whose children are grid-direct cells (spans / divs). React fragments are transparent to CSS grid, so the inner elements become direct children of `configGrid` and flow into grid cells like the existing rows.

**`CheckpointsTable` rewrite.** Instead of wrapping itself in `<div className={styles.checkpointsSection}>`, the component returns a Fragment of grid-ready children:

```tsx
if (checkpoints.length === 0) {
  return null;
}

return (
  <>
    <span className={styles.configLabel}>Checkpoints</span>
    <div className={styles.checkpointsTriggerCell}>
      <button
        type="button"
        className={styles.checkpointsTrigger}
        onClick={() => setOpen((v) => !v)}
        aria-expanded={open}
      >
        <span className={styles.checkpointsChevron}>{open ? '\u25BE' : '\u25B8'}</span>
        {open ? 'Collapse' : 'Expand'} ({checkpoints.length})
      </button>
    </div>
    {open && (
      <div className={styles.checkpointsTableFullRow}>
        <table>...</table>
        {hidden > 0 && !expanded && (
          <button type="button" className={styles.showOlderBtn} onClick={...}>
            Show older (N) — archived, postmortem only
          </button>
        )}
      </div>
    )}
  </>
);
```

**Why this layout.**
- The trigger button sits in the value column (180px label + 1fr value). When closed, the row reads `Checkpoints    ▸ Expand (5)`.
- When opened, a third grid child appears: a div that spans both columns (`grid-column: 1 / -1`) containing the `<table>` + optional "Show older" button. This gives the 7-column table the full grid width so columns don't crush.
- The trigger remains in the value cell of the label row above — collapse/expand stays attached to its label.

**CSS changes** (`AppDeploymentPage.module.css`):

*Add:*

```css
.checkpointsTriggerCell {
  display: flex;
  align-items: center;
}

.checkpointsTrigger {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: none;
  border: none;
  padding: 0;
  color: var(--text-primary);
  cursor: pointer;
  font: inherit;
  text-align: left;
}

.checkpointsTrigger:hover {
  color: var(--amber);
}

.checkpointsTableFullRow {
  grid-column: 1 / -1;
  margin-top: 4px;
}
```

*Remove (no longer referenced):*
- `.checkpointsSection`
- `.checkpointsHeader` + `.checkpointsHeader:hover`
- `.checkpointsCount`

*Keep:* `.checkpointsChevron` (still used by the trigger for the arrow). `.checkpointsTable`, `.jarCell`, `.jarName`, `.jarStrike`, `.archivedHint`, `.isoSubline`, `.muted`, `.strategyPill`, `.outcomePill`, `.outcome-*`, `.chevron`, `.showOlderBtn`, `.checkpointArchived` — all still referenced by the table body.

*Also remove* (cleanup — unrelated dead weight from the retired `Checkpoints.tsx` row-list view, safe to delete because no TSX references remain):
- `.checkpointsRow`
- `.disclosureToggle`
- `.checkpointList`
- `.checkpointRow`
- `.checkpointMeta`
- Standalone `.checkpointArchived { color: var(--warning); font-size: 12px; }` (the table-row variant `.checkpointsTable tr.checkpointArchived { opacity: 0.55; }` stays)
- `.historyRow` (see §2.3)

### 2.2 Deployed-column locale sub-line

In `CheckpointsTable.tsx`, the Deployed `<td>` currently renders:

```tsx
<td>
  {d.deployedAt && timeAgo(d.deployedAt)}
  <div className={styles.isoSubline}>{d.deployedAt}</div>
</td>
```

Replace with:

```tsx
<td>
  {d.deployedAt && timeAgo(d.deployedAt)}
  <div className={styles.isoSubline}>
    {d.deployedAt && new Date(d.deployedAt).toLocaleString()}
  </div>
</td>
```

`new Date(iso).toLocaleString()` uses the browser's resolved locale via the Intl API. No locale plumbing, no new util.

Primary "5h ago" display stays unchanged.

### 2.3 Remove the History disclosure from the Deployment tab

`HistoryDisclosure.tsx` renders a collapsible `DataTable` + nested `StartupLogPanel`. It duplicates information now surfaced via `CheckpointsTable` + `CheckpointDetailDrawer` (which has its own LogsPanel).

**Changes:**

- Delete `ui/src/pages/AppsTab/AppDeploymentPage/DeploymentTab/HistoryDisclosure.tsx`.
- `ui/src/pages/AppsTab/AppDeploymentPage/DeploymentTab/DeploymentTab.tsx` — remove the import and the `<HistoryDisclosure ... />` render at the bottom of the tab.
- `ui/src/pages/AppsTab/AppDeploymentPage/AppDeploymentPage.module.css` — drop the `.historyRow` rule (covered in §2.1's CSS cleanup list).

## 3. Page wiring

`ui/src/pages/AppsTab/AppDeploymentPage/index.tsx` currently passes the table + drawer together as `children` to `IdentitySection`:

```tsx
<IdentitySection ...>
  {app && (
    <>
      <CheckpointsTable ... />
      {selectedDep && <CheckpointDetailDrawer ... />}
    </>
  )}
</IdentitySection>
```

After the change:

```tsx
<IdentitySection
  ...
  checkpointsSlot={app ? <CheckpointsTable ... /> : undefined}
>
  {app && selectedDep && <CheckpointDetailDrawer ... />}
</IdentitySection>
```

The drawer continues to pass through as `children` because `SideDrawer` uses `createPortal` — it can live at any DOM depth, but conceptually sits outside the Identity grid so it doesn't become a stray grid cell.

## 4. Files touched

| Path | Change |
|------|--------|
| `ui/src/pages/AppsTab/AppDeploymentPage/IdentitySection.tsx` | Add `checkpointsSlot?: ReactNode`; render inside `configGrid` after JAR row |
| `ui/src/pages/AppsTab/AppDeploymentPage/CheckpointsTable.tsx` | Return React.Fragment of grid-ready children; replace header wrapper with `checkpointsTrigger` button; locale sub-line in Deployed cell |
| `ui/src/pages/AppsTab/AppDeploymentPage/CheckpointsTable.test.tsx` | Update `expand()` helper to target `/expand|collapse/i`; add test asserting locale sub-line differs from raw ISO |
| `ui/src/pages/AppsTab/AppDeploymentPage/AppDeploymentPage.module.css` | Add `.checkpointsTriggerCell`, `.checkpointsTrigger`, `.checkpointsTableFullRow`; remove obsolete classes listed in §2.1 |
| `ui/src/pages/AppsTab/AppDeploymentPage/index.tsx` | Split `checkpointsSlot` out of `children`; drawer stays in `children` |
| `ui/src/pages/AppsTab/AppDeploymentPage/DeploymentTab/DeploymentTab.tsx` | Remove `HistoryDisclosure` import + render |
| `ui/src/pages/AppsTab/AppDeploymentPage/DeploymentTab/HistoryDisclosure.tsx` | **Delete** |

## 5. Testing

**Unit (vitest + RTL):**

- Update `CheckpointsTable.test.tsx`:
  - `expand()` helper targets `screen.getByRole('button', { name: /expand|collapse/i })`.
  - The "defaults to collapsed" test asserts the trigger button exists and reads `Expand (1)`; rows hidden.
  - The "clicking header expands" test clicks the button (now labeled `Expand`); after click, button label is `Collapse`; rows visible.
  - One new test: render the table with `deployedAt: '2026-04-23T10:35:00Z'`, expand, grab the `.isoSubline` element, assert its text contains neither the raw ISO `T` nor `Z`, i.e. it was parsed into a localized form. (Avoids asserting the exact string — CI locales vary.)

**Manual smoke:**

- Page loads → `Checkpoints | ▸ Expand (N)` as a grid row under Application JAR. Collapsed by default.
- Click trigger → text swaps to `▾ Collapse (N)`; table appears below, spanning full grid width.
- Deployed column sub-line shows a local-format date/time (e.g. `4/23/2026, 12:35:00 PM` in `en-US`).
- Deployment tab no longer shows `▶ History (N)` below `Startup Logs`.
- `CheckpointDetailDrawer` still opens on row click (unaffected).
- Empty state: app with no checkpoints shows no Checkpoints row at all.

## 6. Non-goals

- No changes to `CheckpointDetailDrawer` layout or behavior.
- No changes to `timeAgo` (other components still use it).
- No new locale-formatting helpers; `toLocaleString()` inline at the one callsite.
- Not touching primary Deployed column display (keeps "5h ago").
- No changes to the `CheckpointsTable` columns themselves.

## 7. Open questions

None — all resolved during brainstorming.
