# 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
Identity & Artifact
... label + value cells (Application Name, Slug, Environment, External URL, Current Version, Application JAR) ...
{children} {/* CheckpointsTable + CheckpointDetailDrawer currently render here */}
``` **New structure:** ```tsx
Identity & Artifact
... existing label + value cells ... {checkpointsSlot} {/* NEW: rendered as direct grid children via React.Fragment */}
{children} {/* still used — for the portal-rendered CheckpointDetailDrawer */}
``` **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 `
`, the component returns a Fragment of grid-ready children: ```tsx if (checkpoints.length === 0) { return null; } return ( <> Checkpoints
{open && (
...
{hidden > 0 && !expanded && ( )}
)} ); ``` **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 `` + 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 ` ``` Replace with: ```tsx ``` `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 `` 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 {app && ( <> {selectedDep && } )} ``` After the change: ```tsx : undefined} > {app && selectedDep && } ``` 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.
` currently renders: ```tsx {d.deployedAt && timeAgo(d.deployedAt)}
{d.deployedAt}
{d.deployedAt && timeAgo(d.deployedAt)}
{d.deployedAt && new Date(d.deployedAt).toLocaleString()}