feat: add ui shell sections

This commit is contained in:
dev-agent
2026-04-28 16:15:24 +00:00
parent 99878bae4c
commit 5aac6d69c5
5 changed files with 364 additions and 70 deletions

View File

@@ -27,11 +27,6 @@ npm run lint
```
## Current scope
- Minimal frontend shell only.
- No live API wiring in the baseline commit.
- `environment summary` stays mock-only until a worker-visible route exists in the current backend runtime.
## Next development slice
1. Add app shell sections for `timeline`, `audit feed`, and `environment summary`.
2. Introduce typed DTO/fixture layer from the approved first-slice contracts.
3. Keep `environment summary` behind mock data until the live route is available.
- App shell slice for `timeline`, `audit feed`, and `environment summary`.
- Local typed fixtures only. No live API wiring.
- `environment summary` stays explicitly mock-only until a worker-visible route exists in the current backend runtime.

View File

@@ -1,7 +1,7 @@
:root {
color: #e5eef7;
background: #0b1220;
font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
color: #ebf1ea;
background: #101611;
font-family: 'IBM Plex Sans', 'Segoe UI', sans-serif;
}
body {
@@ -9,8 +9,9 @@ body {
min-width: 320px;
min-height: 100vh;
background:
radial-gradient(circle at top, rgba(56, 189, 248, 0.18), transparent 35%),
linear-gradient(180deg, #020617 0%, #0f172a 100%);
radial-gradient(circle at top left, rgba(201, 255, 148, 0.14), transparent 32%),
radial-gradient(circle at right, rgba(102, 163, 255, 0.1), transparent 28%),
linear-gradient(180deg, #0b100c 0%, #161d17 100%);
}
#root {
@@ -18,23 +19,38 @@ body {
}
.app-shell {
max-width: 1120px;
max-width: 1180px;
margin: 0 auto;
padding: 48px 24px 64px;
padding: 40px 20px 56px;
}
.hero-card,
.next-slice-card,
.status-grid article {
background: rgba(15, 23, 42, 0.8);
border: 1px solid rgba(148, 163, 184, 0.18);
border-radius: 24px;
box-shadow: 0 20px 45px rgba(2, 6, 23, 0.35);
.overview-strip article,
.panel,
.summary-card,
.timeline-item,
.audit-item {
background: rgba(18, 25, 20, 0.84);
border: 1px solid rgba(207, 234, 212, 0.1);
box-shadow: 0 18px 40px rgba(5, 8, 6, 0.3);
}
.hero-card,
.next-slice-card {
.panel,
.overview-strip article,
.summary-card,
.timeline-item,
.audit-item {
border-radius: 22px;
}
.hero-card {
display: grid;
grid-template-columns: minmax(0, 1.1fr) minmax(280px, 0.9fr);
gap: 24px;
align-items: end;
padding: 28px;
margin-bottom: 20px;
}
.eyebrow {
@@ -42,52 +58,177 @@ body {
text-transform: uppercase;
letter-spacing: 0.12em;
font-size: 0.8rem;
color: #38bdf8;
color: #b8f36b;
}
h1,
h2,
p,
ul {
h3,
p {
margin-top: 0;
}
h1 {
font-size: clamp(2.4rem, 5vw, 4rem);
margin-bottom: 16px;
font-size: clamp(2.4rem, 4vw, 3.5rem);
margin-bottom: 0;
line-height: 0.95;
letter-spacing: -0.04em;
}
h2 {
margin-bottom: 0;
font-size: 1.2rem;
}
h3 {
margin-bottom: 8px;
font-size: 1rem;
}
.lede {
font-size: 1.1rem;
margin-bottom: 0;
align-self: stretch;
font-size: 1rem;
line-height: 1.7;
max-width: 68ch;
color: #cbd5e1;
color: #c8d4c8;
}
.status-grid {
.overview-strip {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 20px;
margin: 28px 0;
gap: 16px;
margin-bottom: 20px;
}
.status-grid article {
.overview-strip article {
padding: 18px 20px;
}
.kicker,
.section-label,
.item-meta {
display: inline-block;
margin: 0 0 8px;
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.1em;
color: #93a393;
}
.overview-strip strong {
display: block;
font-size: 1rem;
color: #f5f8f4;
}
.content-grid {
display: grid;
grid-template-columns: repeat(12, minmax(0, 1fr));
gap: 20px;
}
.panel {
grid-column: span 4;
padding: 22px;
}
.status-grid h2,
.next-slice-card h2 {
margin-bottom: 12px;
font-size: 1.1rem;
.panel-wide {
grid-column: span 8;
}
.status-grid p,
.next-slice-card li {
color: #cbd5e1;
line-height: 1.6;
.panel-heading {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
margin-bottom: 18px;
}
.next-slice-card ul {
padding-left: 20px;
.panel-badge {
border-radius: 999px;
padding: 7px 12px;
font-size: 0.8rem;
color: #d8f4af;
background: rgba(184, 243, 107, 0.12);
border: 1px solid rgba(184, 243, 107, 0.18);
}
.panel-badge.muted {
color: #c6d0c6;
background: rgba(198, 208, 198, 0.08);
border-color: rgba(198, 208, 198, 0.12);
}
.timeline-list,
.audit-list,
.summary-grid {
display: grid;
gap: 14px;
}
.timeline-list {
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
.timeline-item,
.audit-item,
.summary-card {
padding: 18px;
}
.timeline-item h3,
.audit-item h3,
.summary-card h3 {
color: #f5f8f4;
}
.timeline-item p,
.audit-item p,
.summary-card p {
margin-bottom: 0;
line-height: 1.55;
color: #c8d4c8;
}
.timeline-stable {
border-color: rgba(157, 255, 172, 0.16);
}
.timeline-active {
border-color: rgba(255, 211, 122, 0.18);
}
.timeline-attention {
border-color: rgba(255, 143, 143, 0.18);
}
.audit-target {
color: #b8f36b;
}
.summary-grid {
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}
@media (max-width: 900px) {
.hero-card,
.content-grid {
grid-template-columns: 1fr;
}
.panel,
.panel-wide {
grid-column: auto;
}
}
@media (max-width: 640px) {
.app-shell {
padding-inline: 16px;
}
.hero-card,
.panel {
padding: 20px;
}
}

View File

@@ -1,45 +1,101 @@
import './App.css'
const nextSlice = [
'Timeline block wired to approved DTO fixtures',
'Audit feed block wired to approved DTO fixtures',
'Environment summary placeholder kept mock-only',
]
import {
auditEntries,
environmentSummaryItems,
timelineEntries,
} from './fixtures/dashboard'
function App() {
return (
<main className="app-shell">
<section className="hero-card">
<header className="hero-card">
<div>
<p className="eyebrow">Mission Control / devopspanel</p>
<h1>DevOps orchestration admin panel</h1>
<h1>Operations shell</h1>
</div>
<p className="lede">
Baseline frontend scaffold for the first implementation slice. This commit
intentionally sets the stack and shell contract before live integrations.
Local mock slice for shell structure only. Timeline, audit feed, and
environment summary are rendered from typed fixtures with no live API wiring.
</p>
</section>
</header>
<section className="status-grid">
<section className="overview-strip" aria-label="App slice overview">
<article>
<h2>Baseline stack</h2>
<p>React + TypeScript + Vite</p>
<span className="kicker">Stack</span>
<strong>React + TypeScript + Vite</strong>
</article>
<article>
<h2>Current state</h2>
<p>Scaffold only, no runtime data wiring yet</p>
<span className="kicker">Data mode</span>
<strong>Local typed fixtures</strong>
</article>
<article>
<h2>Guardrail</h2>
<p>Environment summary remains mock-only until live worker-visible route exists</p>
<span className="kicker">Guardrail</span>
<strong>Environment summary is mock-only</strong>
</article>
</section>
<section className="next-slice-card">
<h2>Next development slice</h2>
<ul>
{nextSlice.map((item) => (
<li key={item}>{item}</li>
<section className="content-grid">
<article className="panel panel-wide">
<div className="panel-heading">
<div>
<p className="section-label">Timeline</p>
<h2>Planned operator moments</h2>
</div>
<span className="panel-badge">{timelineEntries.length} items</span>
</div>
<div className="timeline-list">
{timelineEntries.map((entry) => (
<div key={entry.id} className={`timeline-item timeline-${entry.tone}`}>
<p className="item-meta">{entry.windowLabel}</p>
<h3>{entry.title}</h3>
<p>{entry.detail}</p>
</div>
))}
</ul>
</div>
</article>
<article className="panel">
<div className="panel-heading">
<div>
<p className="section-label">Audit Feed</p>
<h2>Recent shell events</h2>
</div>
</div>
<div className="audit-list">
{auditEntries.map((entry) => (
<div key={entry.id} className="audit-item">
<p className="item-meta">{entry.occurredAt}</p>
<h3>
{entry.actor} {entry.action}
</h3>
<p className="audit-target">{entry.target}</p>
<p>{entry.summary}</p>
</div>
))}
</div>
</article>
<article className="panel panel-wide">
<div className="panel-heading">
<div>
<p className="section-label">Environment Summary</p>
<h2>Mock-only environment snapshot</h2>
</div>
<span className="panel-badge muted">mock only</span>
</div>
<div className="summary-grid">
{environmentSummaryItems.map((item) => (
<div key={item.id} className="summary-card">
<p className="item-meta">{item.label}</p>
<h3>{item.value}</h3>
<p>{item.note}</p>
</div>
))}
</div>
</article>
</section>
</main>
)

77
src/fixtures/dashboard.ts Normal file
View File

@@ -0,0 +1,77 @@
import type {
AuditEntry,
EnvironmentSummaryItem,
TimelineEntry,
} from '../types/dashboard'
export const timelineEntries: TimelineEntry[] = [
{
id: 'tl-1',
title: 'Morning maintenance window',
windowLabel: '08:30-09:00 UTC',
detail: 'Patch validation and worker drift review staged for operator sign-off.',
tone: 'stable',
},
{
id: 'tl-2',
title: 'Release rehearsal',
windowLabel: '11:15-12:00 UTC',
detail: 'Dry-run checklist for deployment sequencing and rollback notes.',
tone: 'active',
},
{
id: 'tl-3',
title: 'Credential rotation follow-up',
windowLabel: '15:00-15:30 UTC',
detail: 'Pending verification items remain isolated to the mock shell.',
tone: 'attention',
},
]
export const auditEntries: AuditEntry[] = [
{
id: 'af-1',
actor: 'ops.release',
action: 'approved',
target: 'frontend-shell-slice',
occurredAt: '10:42 UTC',
summary: 'Scope held to local fixtures and presentational shell changes only.',
},
{
id: 'af-2',
actor: 'runtime.guardrail',
action: 'flagged',
target: 'environment-summary',
occurredAt: '09:18 UTC',
summary: 'Section copy must stay explicitly mock-only until backend visibility exists.',
},
{
id: 'af-3',
actor: 'design.review',
action: 'recorded',
target: 'layout-baseline',
occurredAt: '08:05 UTC',
summary: 'Three-panel shell accepted as the next UI slice for iteration.',
},
]
export const environmentSummaryItems: EnvironmentSummaryItem[] = [
{
id: 'env-1',
label: 'Mock worker coverage',
value: '12 workers',
note: 'Mock dataset only. Not sourced from runtime inventory.',
},
{
id: 'env-2',
label: 'Mock alert posture',
value: '2 advisory notices',
note: 'Mock status text for layout validation only.',
},
{
id: 'env-3',
label: 'Mock change queue',
value: '5 queued actions',
note: 'Mock placeholder until live API contracts are introduced.',
},
]

25
src/types/dashboard.ts Normal file
View File

@@ -0,0 +1,25 @@
export type TimelineTone = 'stable' | 'active' | 'attention'
export interface TimelineEntry {
id: string
title: string
windowLabel: string
detail: string
tone: TimelineTone
}
export interface AuditEntry {
id: string
actor: string
action: string
target: string
occurredAt: string
summary: string
}
export interface EnvironmentSummaryItem {
id: string
label: string
value: string
note: string
}