Mitai analysis-split Sub-Nav und weniger Blend-UI bei Auspraegungen.
Some checks failed
Test Suite / lint-backend (push) Waiting to run
Test Suite / compose-smoke (push) Waiting to run
Test Suite / k6 /api/health Baseline (push) Blocked by required conditions
Test Suite / playwright-smoke (push) Blocked by required conditions
Deploy Development / deploy (push) Successful in 46s
Test Suite / pytest-backend (push) Has been cancelled

Modus-Subnav als linke Spalte (Plan/Kontrolle/Ausfuehren), Referenz-Auspraegungen aus Formularen entfernt, Hub-Karten auf Steuerung gestrichen.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Lars 2026-07-10 14:26:14 +02:00
parent b0594df5f2
commit 5e2ae9de31
18 changed files with 293 additions and 440 deletions

View File

@ -8,10 +8,7 @@ import {
getInitiativeFields,
listArchetypeFieldDefinitions,
} from '../api/entityFields.js'
import { listSteeringMethods } from '../api/steering.js'
import { listMethodProfiles } from '../api/methodProfiles.js'
import { FieldRenderer } from './FieldRenderer.jsx'
import { InitiativeSteeringMeta } from './InitiativeSteeringMeta.jsx'
export function InitiativeForm({
initial = {},
@ -26,42 +23,6 @@ export function InitiativeForm({
const [fieldDefinitions, setFieldDefinitions] = useState([])
const [dynamicValues, setDynamicValues] = useState({})
const [fieldsLoading, setFieldsLoading] = useState(false)
const [steeringMethods, setSteeringMethods] = useState([])
const [methodProfiles, setMethodProfiles] = useState([])
const [methodProfileKey, setMethodProfileKey] = useState('')
useEffect(() => {
let cancelled = false
listSteeringMethods()
.then((items) => {
if (!cancelled) setSteeringMethods(Array.isArray(items) ? items : [])
})
.catch(() => {
if (!cancelled) setSteeringMethods([])
})
return () => {
cancelled = true
}
}, [])
useEffect(() => {
let cancelled = false
setMethodProfileKey('')
if (!archetypeKey) {
setMethodProfiles([])
return undefined
}
listMethodProfiles(archetypeKey)
.then((items) => {
if (!cancelled) setMethodProfiles(Array.isArray(items) ? items : [])
})
.catch(() => {
if (!cancelled) setMethodProfiles([])
})
return () => {
cancelled = true
}
}, [archetypeKey])
useEffect(() => {
let cancelled = false
@ -130,7 +91,6 @@ export function InitiativeForm({
status: form.status.value,
priority: form.priority.value,
dynamicFields: dynamicValues,
...(methodProfileKey ? { method_profile_key: methodProfileKey } : {}),
})
}
@ -139,6 +99,7 @@ export function InitiativeForm({
}
const formReady = archetypes && !fieldsLoading
const selectedArchetype = (archetypes || []).find((item) => item.key === archetypeKey)
return (
<form className="form workspace-form" onSubmit={handleSubmit}>
@ -160,31 +121,10 @@ export function InitiativeForm({
</option>
))}
</select>
</label>
{methodProfiles.length > 0 && (
<label>
Ausprägung (optional)
<select
name="method_profile_key"
value={methodProfileKey}
onChange={(e) => setMethodProfileKey(e.target.value)}
>
<option value=""> Standard (nur Archetyp) </option>
{methodProfiles.map((item) => (
<option key={item.key} value={item.key}>
{item.label}
</option>
))}
</select>
</label>
{selectedArchetype?.description && (
<span className="muted form-hint">{selectedArchetype.description}</span>
)}
<InitiativeSteeringMeta
archetypeKey={archetypeKey}
initiativeArchetypes={archetypes || []}
steeringMethods={steeringMethods}
methodProfiles={methodProfiles}
selectedMethodProfileKey={methodProfileKey}
/>
</label>
<label>
Kurzbeschreibung (Ziel)
<textarea name="goal" rows={2} defaultValue={initial.goal || ''} />

View File

@ -1,60 +0,0 @@
import { Link } from 'react-router-dom'
import { scopedPath } from '../utils/routes.js'
const AREAS = [
{
to: '/plan/gates',
title: 'Plan',
description: 'Roadmap, Gates und Plan-Ist — methodenneutral pflegen.',
countKey: 'roadmapItems',
},
{
to: '/work/today',
title: 'Ausführung',
description: 'Arbeitspakete, Zuweisungen, Blocker am Objekt.',
countKey: 'actionsOpen',
},
{
to: '/plan/inbox',
title: 'Eingang',
description: 'Backlog triagieren und in Arbeitspakete überführen.',
countKey: 'backlog',
},
{
to: '/control/journey',
title: 'Nachvollziehbarkeit',
description: 'Entscheidungen, Reviews, Nachweise über die Zeit.',
countKey: null,
},
]
export function InitiativeNavCards({ initiativeId, counts = {} }) {
return (
<section className="initiative-nav-cards">
<h2 className="initiative-nav-cards-title">Pflege & Planung</h2>
<div className="initiative-nav-cards-grid">
{AREAS.map((area) => {
const hint =
area.countKey === 'backlog' && counts.backlog
? `${counts.backlog} Backlog`
: area.countKey === 'actionsOpen' && counts.actionsOpen
? `${counts.actionsOpen} offen`
: area.countKey === 'roadmapItems' && counts.roadmapItems
? `${counts.roadmapItems} Plan-Elemente`
: null
return (
<Link
key={area.to}
to={scopedPath(area.to, { initiativeId })}
className="initiative-nav-card card"
>
<h3>{area.title}</h3>
<p className="muted">{area.description}</p>
{hint && <span className="initiative-nav-card-hint">{hint}</span>}
</Link>
)
})}
</div>
</section>
)
}

View File

@ -3,38 +3,21 @@ import {
findArchetype,
methodDescriptionForKey,
methodLabelForKey,
methodProfileDescription,
methodProfileLabel,
} from '../utils/archetypes.js'
/**
* Zeigt Archetyp Methode Ausprägung transparent (Capture & Lagebild).
* Archetyp und abgeleitete Steuerungsmethode (read-only Kontext).
*/
export function InitiativeSteeringMeta({
archetypeKey,
initiativeArchetypes = [],
steeringSnapshot = null,
steeringMethods = [],
methodProfiles = [],
selectedMethodProfileKey = '',
compact = false,
}) {
const archetype = findArchetype(archetypeKey, initiativeArchetypes)
const profileKey =
steeringSnapshot?.method_profile_key || selectedMethodProfileKey || null
const profileLabel =
steeringSnapshot?.method_profile_label ||
methodProfileLabel(profileKey, methodProfiles)
const profileDescription = methodProfileDescription(profileKey, methodProfiles)
const selectedProfile = profileKey
? methodProfiles.find((item) => item.key === profileKey)
: null
const methodKey =
steeringSnapshot?.method_key ||
selectedProfile?.method_key ||
archetype?.default_method_key ||
null
steeringSnapshot?.method_key || archetype?.default_method_key || null
const methodLabel =
steeringSnapshot?.method_label || methodLabelForKey(methodKey, steeringMethods)
const methodDescription = methodDescriptionForKey(methodKey, steeringMethods)
@ -62,19 +45,6 @@ export function InitiativeSteeringMeta({
</span>
</div>
)}
{profileKey && (
<div className="initiative-steering-meta__row">
<span className="initiative-steering-meta__label">Ausprägung</span>
<span className="initiative-steering-meta__value">
<strong>{profileLabel}</strong>
{!compact && profileDescription && (
<span className="initiative-steering-meta__hint muted">
{profileDescription}
</span>
)}
</span>
</div>
)}
{methodKey && (
<div className="initiative-steering-meta__row">
<span className="initiative-steering-meta__label">Steuerungsmethode</span>
@ -85,7 +55,7 @@ export function InitiativeSteeringMeta({
{methodDescription}
</span>
)}
{!compact && !steeringSnapshot && archetype?.default_method_key && !profileKey && (
{!compact && !steeringSnapshot && archetype?.default_method_key && (
<span className="initiative-steering-meta__hint muted">
Wird beim Anlegen automatisch vom Archetyp abgeleitet.
</span>

View File

@ -1,56 +0,0 @@
import { useEffect, useState } from 'react'
import { listMethodProfiles } from '../api/methodProfiles.js'
export function MethodProfileSelect({
archetypeKey,
value = '',
onChange,
disabled = false,
}) {
const [profiles, setProfiles] = useState([])
useEffect(() => {
if (!archetypeKey) {
setProfiles([])
return undefined
}
let cancelled = false
listMethodProfiles(archetypeKey)
.then((items) => {
if (!cancelled) setProfiles(Array.isArray(items) ? items : [])
})
.catch(() => {
if (!cancelled) setProfiles([])
})
return () => {
cancelled = true
}
}, [archetypeKey])
if (profiles.length === 0) {
return (
<p className="muted method-profile-select__empty">
Keine Referenz-Ausprägung für diesen Archetyp hinterlegt.
</p>
)
}
return (
<label className="method-profile-select">
Ausprägung
<select
value={value || ''}
disabled={disabled}
onChange={(e) => onChange?.(e.target.value)}
aria-label="Methoden-Ausprägung"
>
<option value=""> Standard (nur Archetyp) </option>
{profiles.map((item) => (
<option key={item.key} value={item.key}>
{item.label}
</option>
))}
</select>
</label>
)
}

View File

@ -0,0 +1,63 @@
import { NavLink, useLocation } from 'react-router-dom'
import { useProgramScope } from '../context/ProgramScopeContext.jsx'
/**
* Bereichs-Subnav (Mitai Settings/Admin-Shell).
*
* @param {{
* modeLabel: string,
* items: { key: string, to: string, label: string, labelFor?: (ctx: object) => string }[],
* resolveActiveKey?: (pathname: string) => string | null,
* showScope?: boolean,
* scopeMutedLabel?: string,
* }} props
*/
export function ModeAreaNav({
modeLabel,
items,
resolveActiveKey,
showScope = false,
scopeMutedLabel = 'Portfolio',
}) {
const location = useLocation()
const { initiativeId, initiativeTitle, hrefWithScope } = useProgramScope()
const activeKey = resolveActiveKey?.(location.pathname)
return (
<nav className="analysis-split__nav" aria-label={`${modeLabel}-Navigation`}>
<div className="analysis-split__nav-header">
<span className="analysis-split__nav-eyebrow">{modeLabel}</span>
{showScope &&
(initiativeId ? (
<p className="analysis-split__nav-scope" title={initiativeTitle || initiativeId}>
{initiativeTitle || 'Vorhaben'}
</p>
) : (
<p className="analysis-split__nav-scope analysis-split__nav-scope--muted">
{scopeMutedLabel}
</p>
))}
</div>
<ul className="analysis-split__nav-list">
{items.map((item) => {
const label = item.labelFor ? item.labelFor({}) : item.label
const isActive = activeKey === item.key
return (
<li key={item.key}>
<NavLink
to={hrefWithScope(item.to)}
end
className={({ isActive: routerActive }) =>
'analysis-split__nav-link' +
(routerActive || isActive ? ' analysis-split__nav-link--active' : '')
}
>
{label}
</NavLink>
</li>
)
})}
</ul>
</nav>
)
}

View File

@ -0,0 +1,11 @@
/**
* Mitai analysis-split: linke Sub-Nav + Hauptinhalt (Desktop), gestapelt (Mobile).
*/
export function ModeAreaShell({ nav, children }) {
return (
<div className="analysis-split">
{nav}
<div className="analysis-split__main">{children}</div>
</div>
)
}

View File

@ -1,26 +0,0 @@
import { NavLink } from 'react-router-dom'
import { useProgramScope } from '../context/ProgramScopeContext.jsx'
/**
* @param {{ items: { to: string, label: string, end?: boolean }[] }} props
*/
export function ModeSubNav({ items }) {
const { hrefWithScope } = useProgramScope()
return (
<nav className="mode-subnav" aria-label="Modus-Unternavigation">
{items.map((item) => (
<NavLink
key={item.to}
to={hrefWithScope(item.to)}
end={item.end !== false}
className={({ isActive }) =>
'mode-subnav__link' + (isActive ? ' mode-subnav__link--active' : '')
}
>
{item.label}
</NavLink>
))}
</nav>
)
}

View File

@ -1,10 +1,8 @@
import { NavLink, useLocation } from 'react-router-dom'
import { useEffect, useState } from 'react'
import { getInitiative, listInitiativeActions } from '../api/initiatives.js'
import { listInitiativeActions } from '../api/initiatives.js'
import { listInitiativeBacklog } from '../api/backlog.js'
import { useProgramScope } from '../context/ProgramScopeContext.jsx'
import { useEntityArchetypes } from '../hooks/useEntityArchetypes.js'
import { InitiativeSteeringMeta } from './InitiativeSteeringMeta.jsx'
import {
PLAN_OUTLINE_NODES,
resolvePlanOutlineActiveKey,
@ -13,28 +11,8 @@ import {
export function PlanOutlineNav() {
const location = useLocation()
const { initiativeId, initiativeTitle, hrefWithScope } = useProgramScope()
const { initiativeArchetypes } = useEntityArchetypes()
const activeKey = resolvePlanOutlineActiveKey(location.pathname)
const [counts, setCounts] = useState({ inbox: null, work: null })
const [initiativeArchetypeKey, setInitiativeArchetypeKey] = useState('')
useEffect(() => {
if (!initiativeId) {
setInitiativeArchetypeKey('')
return undefined
}
let cancelled = false
getInitiative(initiativeId)
.then((data) => {
if (!cancelled) setInitiativeArchetypeKey(data.archetype_key || '')
})
.catch(() => {
if (!cancelled) setInitiativeArchetypeKey('')
})
return () => {
cancelled = true
}
}, [initiativeId])
useEffect(() => {
if (!initiativeId) {
@ -70,29 +48,21 @@ export function PlanOutlineNav() {
}
return (
<nav className="plan-outline-nav" aria-label="Plan-Outline">
<div className="plan-outline-nav__header">
<span className="plan-outline-nav__eyebrow">Planen</span>
<nav className="analysis-split__nav" aria-label="Plan-Navigation">
<div className="analysis-split__nav-header">
<span className="analysis-split__nav-eyebrow">Planen</span>
{initiativeId ? (
<p className="plan-outline-nav__root" title={initiativeTitle || initiativeId}>
<p className="analysis-split__nav-scope" title={initiativeTitle || initiativeId}>
{initiativeTitle || 'Vorhaben'}
</p>
) : (
<p className="plan-outline-nav__root plan-outline-nav__root--muted">
<p className="analysis-split__nav-scope analysis-split__nav-scope--muted">
Portfolio Vorhaben wählen
</p>
)}
</div>
{initiativeId && initiativeArchetypeKey && (
<InitiativeSteeringMeta
archetypeKey={initiativeArchetypeKey}
initiativeArchetypes={initiativeArchetypes}
compact
/>
)}
<ul className="plan-outline-nav__list">
<ul className="analysis-split__nav-list">
{PLAN_OUTLINE_NODES.map((node) => {
const needsInitiative = node.requiresInitiative && !initiativeId
const isDisabled = node.disabled || needsInitiative
@ -102,8 +72,8 @@ export function PlanOutlineNav() {
<li key={node.key}>
<span
className={
'plan-outline-nav__link plan-outline-nav__link--disabled' +
(activeKey === node.key ? ' plan-outline-nav__link--active' : '')
'analysis-split__nav-link analysis-split__nav-link--disabled' +
(activeKey === node.key ? ' analysis-split__nav-link--active' : '')
}
aria-disabled="true"
title={needsInitiative ? 'Vorhaben im Scope wählen' : node.hint}
@ -120,8 +90,8 @@ export function PlanOutlineNav() {
to={hrefWithScope(node.to)}
end
className={({ isActive }) =>
'plan-outline-nav__link' +
(isActive ? ' plan-outline-nav__link--active' : '')
'analysis-split__nav-link' +
(isActive ? ' analysis-split__nav-link--active' : '')
}
>
{nodeLabel(node)}

View File

@ -54,8 +54,6 @@ export function SteeringSnapshotPanel({
method_label,
archetype_key,
archetype_label,
method_profile_key,
method_profile_label,
steering_guidance,
attention_items = [],
signals = [],
@ -117,13 +115,6 @@ export function SteeringSnapshotPanel({
{(archetype_label || archetype_key) && (
<p className="steering-archetype-row muted">
Archetyp: <strong>{archetype_label || archetype_key}</strong>
{method_profile_label || method_profile_key ? (
<>
{' '}
· Ausprägung:{' '}
<strong>{method_profile_label || method_profile_key}</strong>
</>
) : null}
</p>
)}
{(method_label || canManageMethod) && (

View File

@ -0,0 +1,24 @@
/**
* Kontrolle-Subnav (Mitai: settingsNav / analysis-split).
* Pfade müssen mit Routes in App.jsx übereinstimmen.
*
* @typedef {{ key: string, to: string, label: string }} ModeNavItem
*/
/** @type {ModeNavItem[]} */
export const CONTROL_NAV_ITEMS = [
{ key: 'status', to: '/control/status', label: 'Steuerung' },
{ key: 'journey', to: '/control/journey', label: 'Nachvollziehbarkeit' },
]
/**
* @param {string} pathname
* @returns {string | null}
*/
export function resolveControlNavActiveKey(pathname) {
const path = pathname.replace(/\/$/, '') || pathname
const match = CONTROL_NAV_ITEMS.find(
(item) => path === item.to || path.startsWith(`${item.to}/`),
)
return match?.key ?? null
}

View File

@ -0,0 +1,18 @@
import { describe, expect, it } from 'vitest'
import { resolveControlNavActiveKey } from '../config/controlNav.js'
import { resolveWorkNavActiveKey } from '../config/workNav.js'
describe('controlNav', () => {
it('resolves active control sub-route', () => {
expect(resolveControlNavActiveKey('/control/status')).toBe('status')
expect(resolveControlNavActiveKey('/control/journey')).toBe('journey')
expect(resolveControlNavActiveKey('/control')).toBe(null)
})
})
describe('workNav', () => {
it('resolves active work sub-route', () => {
expect(resolveWorkNavActiveKey('/work/today')).toBe('today')
expect(resolveWorkNavActiveKey('/work/mine')).toBe('mine')
})
})

View File

@ -0,0 +1,24 @@
/**
* Ausführen-Subnav (Mitai: settingsNav / analysis-split).
* Pfade müssen mit Routes in App.jsx übereinstimmen.
*
* @typedef {{ key: string, to: string, label: string }} ModeNavItem
*/
/** @type {ModeNavItem[]} */
export const WORK_NAV_ITEMS = [
{ key: 'today', to: '/work/today', label: 'Heute' },
{ key: 'mine', to: '/work/mine', label: 'Meine Queue' },
]
/**
* @param {string} pathname
* @returns {string | null}
*/
export function resolveWorkNavActiveKey(pathname) {
const path = pathname.replace(/\/$/, '') || pathname
const match = WORK_NAV_ITEMS.find(
(item) => path === item.to || path.startsWith(`${item.to}/`),
)
return match?.key ?? null
}

View File

@ -3,7 +3,6 @@ import { scopedPath } from '../../utils/routes.js'
import { useInitiativeOperations } from '../../context/InitiativeOperationsContext.jsx'
import { SteeringSnapshotPanel } from '../../components/SteeringSnapshotPanel.jsx'
import { NextActionWidget } from '../../widgets/NextActionWidget.jsx'
import { InitiativeNavCards } from '../../components/InitiativeNavCards.jsx'
export function InitiativeOverviewPage() {
const {
@ -16,9 +15,6 @@ export function InitiativeOverviewPage() {
methodBusy,
capabilities,
handleMethodChange,
backlogItems,
roadmapItems,
milestones,
} = useInitiativeOperations()
const counts = steeringSnapshot?.counts || {}
@ -66,15 +62,6 @@ export function InitiativeOverviewPage() {
</Link>
</section>
)}
<InitiativeNavCards
initiativeId={initiativeId}
counts={{
backlog: backlogItems.length,
roadmapItems: roadmapItems.length,
actionsOpen: counts.actions_open,
}}
/>
</>
)
}

View File

@ -1,18 +1,29 @@
import { Navigate, Outlet } from 'react-router-dom'
import { ModeAreaShell } from '../../components/ModeAreaShell.jsx'
import { ModeAreaNav } from '../../components/ModeAreaNav.jsx'
import { ModeShell } from '../../components/ModeShell.jsx'
import { ModeSubNav } from '../../components/ModeSubNav.jsx'
const CONTROL_NAV = [
{ to: '/control/status', label: 'Steuerung', end: true },
{ to: '/control/journey', label: 'Journey', end: true },
]
import {
CONTROL_NAV_ITEMS,
resolveControlNavActiveKey,
} from '../../config/controlNav.js'
export function ControlLayout() {
return (
<div className="app-page">
<ModeShell modeKey="control" />
<ModeSubNav items={CONTROL_NAV} />
<ModeAreaShell
nav={
<ModeAreaNav
modeLabel="Kontrolle"
items={CONTROL_NAV_ITEMS}
resolveActiveKey={resolveControlNavActiveKey}
showScope
scopeMutedLabel="Vorhaben wählen"
/>
}
>
<Outlet />
</ModeAreaShell>
</div>
)
}

View File

@ -1,17 +1,15 @@
import { Outlet } from 'react-router-dom'
import { ModeAreaShell } from '../../components/ModeAreaShell.jsx'
import { ModeShell } from '../../components/ModeShell.jsx'
import { PlanOutlineNav } from '../../components/PlanOutlineNav.jsx'
export function PlanLayout() {
return (
<div className="app-page plan-outline-shell">
<div className="app-page">
<ModeShell modeKey="plan" />
<div className="plan-outline-shell__body">
<PlanOutlineNav />
<div className="plan-outline-shell__content">
<ModeAreaShell nav={<PlanOutlineNav />}>
<Outlet />
</div>
</div>
</ModeAreaShell>
</div>
)
}

View File

@ -7,7 +7,6 @@ import { LoadingState } from '../../components/LoadingState.jsx'
import { Modal } from '../../components/Modal.jsx'
import { InitiativeForm } from '../../components/InitiativeForm.jsx'
import { InitiativeSteeringMeta } from '../../components/InitiativeSteeringMeta.jsx'
import { MethodProfileSelect } from '../../components/MethodProfileSelect.jsx'
import { PriorityBadge } from '../../components/PriorityBadge.jsx'
import { useInitiativeOperations } from '../../context/InitiativeOperationsContext.jsx'
import { useProgramScope } from '../../context/ProgramScopeContext.jsx'
@ -88,8 +87,6 @@ function PlanProfileInner() {
formBusy,
steeringSnapshot,
steeringMethods,
methodBusy,
handleMethodProfileChange,
actors,
projects,
} = ops
@ -143,16 +140,6 @@ function PlanProfileInner() {
steeringSnapshot={steeringSnapshot}
steeringMethods={steeringMethods}
/>
{canManage && (
<div className="plan-profile__steering-edit">
<MethodProfileSelect
archetypeKey={initiative.archetype_key}
value={steeringSnapshot?.method_profile_key || ''}
disabled={methodBusy}
onChange={handleMethodProfileChange}
/>
</div>
)}
<div className="plan-profile__field">
<h3 className="plan-profile__label">Verantwortlich (Owner)</h3>
<p>{ownerName}</p>

View File

@ -1,18 +1,24 @@
import { Navigate, Outlet } from 'react-router-dom'
import { ModeAreaShell } from '../../components/ModeAreaShell.jsx'
import { ModeAreaNav } from '../../components/ModeAreaNav.jsx'
import { ModeShell } from '../../components/ModeShell.jsx'
import { ModeSubNav } from '../../components/ModeSubNav.jsx'
const WORK_NAV = [
{ to: '/work/today', label: 'Heute', end: true },
{ to: '/work/mine', label: 'Meine Queue', end: true },
]
import { WORK_NAV_ITEMS, resolveWorkNavActiveKey } from '../../config/workNav.js'
export function WorkLayout() {
return (
<div className="app-page">
<ModeShell modeKey="work" />
<ModeSubNav items={WORK_NAV} />
<ModeAreaShell
nav={
<ModeAreaNav
modeLabel="Ausführen"
items={WORK_NAV_ITEMS}
resolveActiveKey={resolveWorkNavActiveKey}
/>
}
>
<Outlet />
</ModeAreaShell>
</div>
)
}

View File

@ -223,28 +223,110 @@
white-space: nowrap;
}
.mode-subnav {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 16px;
/* Mitai analysis-split: Bereichs-Shell mit linker Sub-Nav (Desktop) */
.analysis-split {
display: grid;
grid-template-columns: minmax(200px, 240px) minmax(0, 1fr);
gap: 20px;
align-items: start;
}
.mode-subnav__link {
padding: 8px 14px;
border-radius: 999px;
text-decoration: none;
font-size: 13px;
font-weight: 600;
color: var(--jk-text-muted);
.analysis-split__main {
min-width: 0;
}
.analysis-split__nav {
position: sticky;
top: 12px;
padding: 14px;
border-radius: 12px;
border: 1px solid var(--jk-border);
background: var(--jk-surface);
}
.mode-subnav__link--active {
color: var(--jk-primary);
border-color: var(--jk-primary);
.analysis-split__nav-header {
margin-bottom: 12px;
padding-bottom: 10px;
border-bottom: 1px solid var(--jk-border);
}
.analysis-split__nav-eyebrow {
display: block;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--jk-text-muted);
}
.analysis-split__nav-scope {
margin: 6px 0 0;
font-size: 15px;
font-weight: 700;
line-height: 1.3;
overflow: hidden;
text-overflow: ellipsis;
}
.analysis-split__nav-scope--muted {
font-weight: 600;
color: var(--jk-text-muted);
}
.analysis-split__nav-list {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 4px;
}
.analysis-split__nav-link {
display: block;
padding: 8px 10px;
border-radius: 8px;
text-decoration: none;
font-size: 13px;
font-weight: 600;
color: var(--jk-text-muted);
}
.analysis-split__nav-link:hover {
background: var(--jk-primary-soft);
color: var(--jk-text);
}
.analysis-split__nav-link--active {
color: var(--jk-primary);
background: var(--jk-primary-soft);
}
.analysis-split__nav-link--disabled {
opacity: 0.55;
cursor: not-allowed;
}
@media (max-width: 900px) {
.analysis-split {
grid-template-columns: 1fr;
}
.analysis-split__nav-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.analysis-split__nav-link {
padding: 8px 14px;
border: 1px solid var(--jk-border);
border-radius: 999px;
background: var(--jk-surface);
}
.analysis-split__nav-link--active {
border-color: var(--jk-primary);
}
}
.scope-picker__list {
@ -292,93 +374,6 @@
margin-bottom: 12px;
}
.plan-outline-shell__body {
display: grid;
grid-template-columns: minmax(200px, 240px) minmax(0, 1fr);
gap: 20px;
align-items: start;
}
@media (max-width: 900px) {
.plan-outline-shell__body {
grid-template-columns: 1fr;
}
}
.plan-outline-nav {
position: sticky;
top: 12px;
padding: 14px;
border-radius: 12px;
border: 1px solid var(--jk-border);
background: var(--jk-surface);
}
.plan-outline-nav__header {
margin-bottom: 12px;
padding-bottom: 10px;
border-bottom: 1px solid var(--jk-border);
}
.plan-outline-nav__eyebrow {
display: block;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--jk-text-muted);
}
.plan-outline-nav__root {
margin: 6px 0 0;
font-size: 15px;
font-weight: 700;
line-height: 1.3;
overflow: hidden;
text-overflow: ellipsis;
}
.plan-outline-nav__root--muted {
font-weight: 600;
color: var(--jk-text-muted);
}
.plan-outline-nav__list {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 4px;
}
.plan-outline-nav__link {
display: block;
padding: 8px 10px;
border-radius: 8px;
text-decoration: none;
font-size: 13px;
font-weight: 600;
color: var(--jk-text-muted);
}
.plan-outline-nav__link:hover {
background: var(--jk-primary-soft);
color: var(--jk-text);
}
.plan-outline-nav__link--active {
color: var(--jk-primary);
background: var(--jk-primary-soft);
}
.plan-outline-nav__link--disabled {
opacity: 0.55;
cursor: not-allowed;
}
.plan-outline-shell__content {
min-width: 0;
}
.plan-profile__header {
display: flex;