09abf45c13
- Defines height propagation chain for all pages - Establishes PageLayout → QueryBoundary → Content pattern - Documents KsSplitter master-detail implementation - Includes verification checklist for new pages - Rationale: flex children need 'min-height: 0' to respect parent constraints
172 lines
4.5 KiB
Markdown
172 lines
4.5 KiB
Markdown
# ADR: Layout Height Propagation Standard
|
|
|
|
**Status:** ACCEPTED
|
|
**Date:** 2026-08-16
|
|
**Authors:** Claude Code
|
|
|
|
---
|
|
|
|
## Context
|
|
|
|
Multi-level frontend layouts (PageLayout → QueryBoundary → content) require explicit height propagation to ensure:
|
|
1. **Single-screen principle**: First load fits viewport without scroll
|
|
2. **Internal scrolling**: Only nested containers scroll, not the page
|
|
3. **Consistent behavior**: All pages follow the same height rules
|
|
|
|
Previous bugs stemmed from missing `flex: 1; min-height: 0;` constraints at various levels.
|
|
|
|
---
|
|
|
|
## Decision
|
|
|
|
Establish a **required height propagation chain** for all pages:
|
|
|
|
```
|
|
PageLayout (.ks-page__content)
|
|
├─ height: 100%
|
|
├─ min-height: 0
|
|
└─ display: flex
|
|
|
|
↓ (child must propagate height)
|
|
|
|
QueryStateBoundary (.ks-query-boundary)
|
|
├─ flex: 1
|
|
├─ height: 100%
|
|
├─ min-height: 0
|
|
└─ display: flex
|
|
|
|
↓ (child must propagate height)
|
|
|
|
Content Container (grid, splitter, or flex child)
|
|
├─ flex: 1 (if flex child)
|
|
├─ height: 100% (if direct child of flex parent)
|
|
├─ min-height: 0 (always required for flex children)
|
|
└─ overflow: (auto|hidden)
|
|
|
|
↓ (internal scrollable panes)
|
|
|
|
Internal Panes (.items, .detail-panel, etc.)
|
|
├─ flex: 1
|
|
├─ min-height: 0
|
|
└─ overflow-y: auto
|
|
```
|
|
|
|
---
|
|
|
|
## Rationale
|
|
|
|
1. **Flex layout principle**: Flex children must have `min-height: 0` to respect parent constraints
|
|
2. **Height inheritance**: `height: 100%` only works when parent has explicit height
|
|
3. **Single responsibility**: Each layer only enforces its own constraints, children handle overflow
|
|
|
|
---
|
|
|
|
## Implementation
|
|
|
|
### For Grid-Based Pages (.ks-stack)
|
|
|
|
```css
|
|
.ks-stack {
|
|
display: grid;
|
|
gap: var(--ks-space-4);
|
|
flex: 1; /* Required: flex child must expand */
|
|
min-height: 0; /* Required: allow internal scroll */
|
|
height: 100%; /* Required: inherit parent height */
|
|
}
|
|
```
|
|
|
|
### For Master-Detail Pages (KsSplitter)
|
|
|
|
```vue
|
|
<KsSplitter storageKey="unique-key" initialRatio="25">
|
|
<template #left>
|
|
<aside class="request-list">
|
|
<h2>Requests</h2>
|
|
<div class="items"><!-- flex: 1; min-height: 0; overflow-y: auto; --></div>
|
|
</aside>
|
|
</template>
|
|
<template #right>
|
|
<main class="detail-panel">
|
|
<!-- height: 100%; min-height: 0; overflow-y: auto; -->
|
|
</main>
|
|
</template>
|
|
</KsSplitter>
|
|
```
|
|
|
|
**Key**: KsSplitter pane uses `overflow: hidden`; each slot's scrollable child must have `overflow-y: auto; min-height: 0`.
|
|
|
|
### For Flex-Column Containers
|
|
|
|
```css
|
|
.container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex: 1; /* Expand to fill parent */
|
|
min-height: 0; /* Allow internal overflow */
|
|
height: 100%; /* Inherit parent height (optional if flex: 1 works) */
|
|
}
|
|
|
|
.child {
|
|
flex: 1; /* Share space with siblings */
|
|
min-height: 0; /* Don't prevent scrolling */
|
|
overflow-y: auto;/* Internal scroll */
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Standard CSS Classes (Optional Utilities)
|
|
|
|
Defined in `frontend/src/design-system/base.css`:
|
|
|
|
```css
|
|
.ks-flex-column-1 { display: flex; flex-direction: column; flex: 1; min-height: 0; }
|
|
.ks-flex-row-1 { display: flex; flex-direction: row; flex: 1; min-height: 0; }
|
|
.ks-overflow-auto { overflow-y: auto; }
|
|
```
|
|
|
|
**Note**: These are *opt-in* utilities. Prefer explicit CSS in scoped styles for clarity.
|
|
|
|
---
|
|
|
|
## Related Artifacts
|
|
|
|
- **Commits:**
|
|
- 61ca979: `.ks-stack` flex: 1 fix
|
|
- b299939: ApprovalQueue flex layout
|
|
- 05e2791: ApprovalQueue → KsSplitter refactor
|
|
- 4ff5aaf: `.request-detail` height fix
|
|
- a1eccad: Standard CSS class definitions
|
|
|
|
- **Updated Components:**
|
|
- VersionGovernancePage.vue
|
|
- ApprovalQueue.vue (KsSplitter migration)
|
|
- ModelOperationsPage.vue
|
|
|
|
- **Memory:** [[layout_standardization_framework]]
|
|
|
|
---
|
|
|
|
## Verification Checklist
|
|
|
|
For each new page:
|
|
- [ ] First load fits viewport (no page scroll)
|
|
- [ ] Internal panes have `overflow-y: auto; min-height: 0`
|
|
- [ ] Height chain: PageLayout → QueryBoundary → content
|
|
- [ ] Master-detail uses KsSplitter or equivalent flex layout
|
|
- [ ] All flex children have `flex: 1; min-height: 0`
|
|
|
|
---
|
|
|
|
## Future Considerations
|
|
|
|
1. **Breakpoints**: Responsive layouts (mobile) may need `flex-direction: column` at small widths
|
|
2. **Height variants**: Consider separate classes for different flex ratios (1:2, 1:1, etc.)
|
|
3. **Template enforcement**: CI/CD gate to catch missing height constraints
|
|
|
|
---
|
|
|
|
## Questions?
|
|
|
|
See CLAUDE.md §"Frontend: Vue 3 + Vite + KBX Foundation v4" for component architecture.
|