Skip to content

Commit 48267bb

Browse files
author
root
committed
improve docker tabs
1 parent 8a5d453 commit 48267bb

10 files changed

Lines changed: 1011 additions & 333 deletions

File tree

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# Story 4.3 Supplement: Docker Tabs UI Contract
2+
3+
**Epic**: Epic 4 - Docker Operations Layer
4+
**Status**: Implemented
5+
**Parent**: Story 4.3 Docker Workspace Replan
6+
7+
---
8+
9+
## Purpose
10+
11+
This document records finalized UI conventions for all Docker sub-tabs inside the Docker workspace. It is the implementation reference for building and maintaining any Docker tab — existing or new.
12+
13+
Containers-specific decisions are called out explicitly. Everything else applies to all tabs.
14+
15+
---
16+
17+
## Scope
18+
19+
Covers:
20+
- panel shell layout and outer chrome (`DockerPanel`)
21+
- shared section header conventions
22+
- Containers tab: table structure, columns, interaction patterns
23+
- React patterns that apply across all tabs
24+
25+
Does not cover:
26+
- Docker backend routes or API contracts (Epic 4 domain stories)
27+
- monitor telemetry contracts (Story 28.6, 28.7)
28+
29+
---
30+
31+
This document records the finalized UI conventions for the `Containers` tab inside the Docker workspace. It serves as the implementation reference so the same conventions can be applied consistently when extending other Docker sub-tabs.
32+
33+
---
34+
35+
## Panel Shell (`DockerPanel`)
36+
37+
`DockerPanel` owns all outer chrome. Individual tab components are controlled — they render content only, no outer borders, headers, or toolbars of their own.
38+
39+
```
40+
DockerPanel
41+
├── Left nav (tab switching, collapsible)
42+
└── Content panel (rounded-xl border overflow-hidden)
43+
├── Section header (title + per-tab toolbar)
44+
└── <Tab component> (controlled, no chrome)
45+
```
46+
47+
**Rule: `overflow-hidden` on the content panel.** The content panel uses `rounded-xl border`. It must also carry `overflow-hidden` so that child backgrounds do not paint over the rounded corners, which would make the bottom corners appear flat/cut off.
48+
49+
**Rule: controlled tab components.** Any tab component that has toolbar state (filters, pagination, column visibility) must receive that state as props from `DockerPanel`. The tab itself does not own or render the toolbar.
50+
51+
---
52+
53+
## Section Header (all tabs)
54+
55+
Lives in `DockerPanel`, not in individual tab components.
56+
57+
Layout: `flex flex-col gap-2` with inner row `flex-wrap items-center justify-between`. Title left, toolbar right. Wraps naturally in narrow panels — no breakpoint-driven `flex-row` switch.
58+
59+
**Rule**: Do not use `xl:flex-row` or similar breakpoints to switch between stacked and side-by-side. `flex-wrap justify-between` is sufficient.
60+
61+
Tab description text is **not shown** in the section header. The tab name is self-explanatory.
62+
63+
---
64+
65+
## Left Nav (collapsible)
66+
67+
The collapse/expand toggle button uses `right-0` when collapsed and `right-3` when expanded.
68+
69+
**Rule**: At collapsed width (`w-14` = 56px), placing the toggle at `right-3` (12px from edge) causes it to overlap the centered tab icons. Use `right-0` when collapsed.
70+
71+
---
72+
73+
## Containers Table
74+
75+
### Column Order
76+
77+
| # | Column | Always visible |
78+
|---|--------|----------------|
79+
| 1 | Name ||
80+
| 2 | Runtime ||
81+
| 3 | Quick ||
82+
| 4 | Lifecycle | optional |
83+
| 5 | Ports | optional |
84+
| 6+ | CPU / Mem / Net / Compose | optional |
85+
| last | Actions ||
86+
87+
**Rule**: Quick column is fixed at position 3, immediately after Runtime. It must always be visible without horizontal scrolling.
88+
89+
### Name Column
90+
91+
- Content: container name (bold, `text-sm`) above image tag (`font-mono text-[11px] text-muted-foreground`)
92+
- Left edge aligns with the section header title (`pl-4`, 16px from panel edge)
93+
- Clicking the name row opens the inline detail expansion
94+
95+
**Rule**: Name column header and cell content share the same left offset (`pl-4`). Do not use negative margin tricks on the sort button to compensate for component padding — use a native `<button>` element with `px-0` instead.
96+
97+
### Runtime Column
98+
99+
Shows state badge + telemetry freshness badge together.
100+
101+
- Running: `emerald` badge
102+
- Exited: muted badge
103+
- Paused: `amber` badge
104+
- No telemetry / Stale: ghost dashed badge alongside the state badge
105+
106+
### Quick Column (`w-[112px]`)
107+
108+
Three icon-only buttons: Logs (`FileText`), Monitor (`Activity`), Exec (`TerminalSquare`).
109+
110+
- Exec button is disabled when container state is not `running`
111+
- All buttons use `onClick` with `event.stopPropagation()` + `event.preventDefault()`
112+
113+
### Actions Column (`w-[52px]`)
114+
115+
`DropdownMenu` trigger with `MoreVertical` icon.
116+
117+
**Critical rule**: All `DropdownMenuItem` handlers must use `onSelect` (not `onClick`) with `window.setTimeout(() => handler(), 0)` to defer state changes until after Radix menu cleanup. Using `onClick` on menu items causes React error #185 (maximum update depth exceeded) because synchronous state changes during menu close animation create a render loop.
118+
119+
```tsx
120+
<DropdownMenuItem
121+
onSelect={event => {
122+
event.stopPropagation()
123+
window.setTimeout(() => setStatsContainer(c), 0)
124+
}}
125+
>
126+
```
127+
128+
---
129+
130+
## Derived Data — useMemo Rule
131+
132+
Filter chains (`filtered`, `stateFiltered`, `nameFiltered`) must be wrapped in `useMemo`. Plain `.filter()` calls in the render body produce new array references on every render, which causes downstream `useMemo` and `useEffect` hooks to run on every render, eventually triggering React error #185 via the `onSummaryChange` callback.
133+
134+
```tsx
135+
const filtered = useMemo(
136+
() => containers.filter(c => c.Names?.toLowerCase().includes(query)),
137+
[containers, query]
138+
)
139+
```
140+
141+
---
142+
143+
## Sort Button
144+
145+
The `SortHead` component must use a native `<button>` element, not the shadcn `Button` component. The shadcn `size="sm"` prop injects `has-[>svg]:px-2.5` which overrides `px-0` when an SVG child is present, shifting the header text to the right and breaking alignment with cell content.
146+
147+
---
148+
149+
## Row Styling
150+
151+
- Running rows: subtle `bg-emerald-500/[0.015]` tint
152+
- Expanded row: `bg-muted/35`
153+
- Hover: `hover:bg-muted/30`
154+
- State dot / inline badge in the Name cell: **not used** (state is shown in the Runtime column only)

web/dist/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
77
<title>dashboard</title>
8-
<script type="module" crossorigin src="/assets/index-AKhrE1Zk.js"></script>
8+
<script type="module" crossorigin src="/assets/index-Cou65mpo.js"></script>
99
<link rel="modulepreload" crossorigin href="/assets/monaco-vendor-w6PwaWGP.js">
1010
<link rel="modulepreload" crossorigin href="/assets/react-vendor-AUwL6HVX.js">
1111
<link rel="modulepreload" crossorigin href="/assets/tanstack-vendor-DExzPqVU.js">
1212
<link rel="modulepreload" crossorigin href="/assets/i18n-vendor-BJ67fyYj.js">
1313
<link rel="modulepreload" crossorigin href="/assets/pb-vendor-CPBlzd7X.js">
1414
<link rel="modulepreload" crossorigin href="/assets/xterm-vendor-DNSqvmB5.js">
1515
<link rel="stylesheet" crossorigin href="/assets/xterm-vendor-DFuMZ0ql.css">
16-
<link rel="stylesheet" crossorigin href="/assets/index-DkfGmPxD.css">
16+
<link rel="stylesheet" crossorigin href="/assets/index-1PebkHR_.css">
1717
</head>
1818
<body>
1919
<div id="root"></div>

0 commit comments

Comments
 (0)