# Deployment Page Polish — Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Deliver five targeted UX improvements on the unified app deployment page: JAR-upload progress inside the primary button, sort + refresh on startup logs, a collapsible checkpoints table (default collapsed), a DS-styled replica dropdown, and reversed drawer tab order (Config first). **Architecture:** All changes are UI-only, confined to `ui/src/pages/AppsTab/AppDeploymentPage/*`, `ui/src/components/StartupLogPanel.*`, `ui/src/api/queries/admin/apps.ts` (`useUploadJar` switches to `XMLHttpRequest`), and `ui/src/api/queries/logs.ts` (`useStartupLogs` accepts a sort parameter). No backend or schema changes. **Tech Stack:** React 18, TypeScript, Vitest + React Testing Library, `@cameleer/design-system` components (Button, Select, Tabs, SectionHeader), `lucide-react` (`RefreshCw`), TanStack Query. **Spec:** `docs/superpowers/specs/2026-04-23-deployment-page-polish-design.md` --- ## Files touched (summary) | Path | Change | |------|--------| | `ui/src/api/queries/admin/apps.ts` | `useUploadJar` rewrites the mutation function to use `XMLHttpRequest` and exposes an `onProgress` callback | | `ui/src/api/queries/logs.ts` | `useStartupLogs` gains a 5th `sort` parameter | | `ui/src/pages/AppsTab/AppDeploymentPage/PrimaryActionButton.tsx` | Adds `'uploading'` mode with a progress-overlay visual | | `ui/src/pages/AppsTab/AppDeploymentPage/AppDeploymentPage.module.css` | Adds `.uploadBtnWrap`, `.uploadBtnFill`, `.checkpointsHeader` styles | | `ui/src/pages/AppsTab/AppDeploymentPage/index.tsx` | Wires `uploadPct` state and feeds it into both upload call sites + the primary button | | `ui/src/components/StartupLogPanel.tsx` | New header layout (sort ↑/↓ + refresh icon), local sort state, scroll-ref | | `ui/src/components/StartupLogPanel.module.css` | Extends with `.headerRight`, `.scrollWrap`, `.ghostIconBtn` | | `ui/src/pages/AppsTab/AppDeploymentPage/CheckpointsTable.tsx` | Collapsible header + `useState(false)` default | | `ui/src/pages/AppsTab/AppDeploymentPage/CheckpointsTable.test.tsx` | Tests updated for collapsed-by-default behavior; stale empty-state assertion removed | | `ui/src/pages/AppsTab/AppDeploymentPage/CheckpointDetailDrawer/LogsPanel.tsx` | Native `` clashes with the rest of the drawer's DS styling. **Files:** - Modify: `ui/src/pages/AppsTab/AppDeploymentPage/CheckpointDetailDrawer/LogsPanel.tsx` - [ ] **Step 7.1: Replace native select with DS `Select`** Open `ui/src/pages/AppsTab/AppDeploymentPage/CheckpointDetailDrawer/LogsPanel.tsx`. Replace the whole file contents with: ```tsx import { useMemo, useState } from 'react'; import { Select } from '@cameleer/design-system'; import { useInfiniteApplicationLogs } from '../../../../api/queries/logs'; import type { Deployment } from '../../../../api/queries/admin/apps'; import { instanceIdsFor } from './instance-id'; import styles from './CheckpointDetailDrawer.module.css'; interface Props { deployment: Deployment; appSlug: string; envSlug: string; } export function LogsPanel({ deployment, appSlug, envSlug }: Props) { const allInstanceIds = useMemo( () => instanceIdsFor(deployment, envSlug, appSlug), // eslint-disable-next-line react-hooks/exhaustive-deps [deployment.id, deployment.replicaStates, envSlug, appSlug] ); const [replicaFilter, setReplicaFilter] = useState<'all' | number>('all'); const filteredInstanceIds = replicaFilter === 'all' ? allInstanceIds : allInstanceIds.filter((_, i) => i === replicaFilter); const logs = useInfiniteApplicationLogs({ application: appSlug, instanceIds: filteredInstanceIds, isAtTop: true, }); const replicaOptions = [ { value: 'all', label: `all (${deployment.replicaStates.length})` }, ...deployment.replicaStates.map((_, i) => ({ value: String(i), label: String(i) })), ]; return (