feat: add functionality detail panels
This commit is contained in:
+65
-13
@@ -83,6 +83,7 @@ function App() {
|
|||||||
const [syncStatus, setSyncStatus] = useState<'connecting' | 'synced' | 'pending' | 'syncing' | 'degraded'>('connecting')
|
const [syncStatus, setSyncStatus] = useState<'connecting' | 'synced' | 'pending' | 'syncing' | 'degraded'>('connecting')
|
||||||
const [lastSyncedAt, setLastSyncedAt] = useState<string | null>(null)
|
const [lastSyncedAt, setLastSyncedAt] = useState<string | null>(null)
|
||||||
const [syncAction, setSyncAction] = useState<'idle' | 'refreshing' | 'pushing'>('idle')
|
const [syncAction, setSyncAction] = useState<'idle' | 'refreshing' | 'pushing'>('idle')
|
||||||
|
const [selectedFunctionalityTitle, setSelectedFunctionalityTitle] = useState('Project Cockpit')
|
||||||
const hasHydratedRemote = useRef(false)
|
const hasHydratedRemote = useRef(false)
|
||||||
const initialLocalStateRef = useRef(appState)
|
const initialLocalStateRef = useRef(appState)
|
||||||
|
|
||||||
@@ -622,6 +623,9 @@ function App() {
|
|||||||
metric: appState.project.name,
|
metric: appState.project.name,
|
||||||
action: 'Edit summary',
|
action: 'Edit summary',
|
||||||
tab: 'functionalities' as TabKey,
|
tab: 'functionalities' as TabKey,
|
||||||
|
operatorNote: 'Use this when the project starts drifting and the cockpit needs a clean north star again.',
|
||||||
|
evidence: ['Project summary fields are editable inline.', 'Hero stats reflect live feature, parking, and pulse counts.', 'Current goal is always visible in the page header.'],
|
||||||
|
next: 'Add an inline “goal changed” pulse when the current goal is edited.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Feature Plan',
|
title: 'Feature Plan',
|
||||||
@@ -631,6 +635,9 @@ function App() {
|
|||||||
metric: `${currentFeatureCount} now`,
|
metric: `${currentFeatureCount} now`,
|
||||||
action: 'Open board',
|
action: 'Open board',
|
||||||
tab: 'feature-plan' as TabKey,
|
tab: 'feature-plan' as TabKey,
|
||||||
|
operatorNote: 'Use this to decide what is actively being built and what should stay out of the way.',
|
||||||
|
evidence: ['Four columns: Now, Next, Later, Done.', 'Cards show priority, status, acceptance criteria count, and linked pulse activity.', 'Selected features expose focus notes, criteria, recent pulses, handoff, and pulse actions.'],
|
||||||
|
next: 'Add a readiness checklist that highlights missing acceptance criteria before work starts.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Parking Lot',
|
title: 'Parking Lot',
|
||||||
@@ -640,6 +647,9 @@ function App() {
|
|||||||
metric: `${appState.parking_lot.length} parked`,
|
metric: `${appState.parking_lot.length} parked`,
|
||||||
action: 'Review parked',
|
action: 'Review parked',
|
||||||
tab: 'parking-lot' as TabKey,
|
tab: 'parking-lot' as TabKey,
|
||||||
|
operatorNote: 'Use this when an idea is useful but too distracting to deserve active build attention yet.',
|
||||||
|
evidence: ['Parked ideas carry risk level, reason parked, and possible future placement.', 'A parked idea can be converted into a real feature.', 'Parking keeps future options visible without polluting Now.'],
|
||||||
|
next: 'Add a “promote candidate” signal for parked items that keep reappearing in pulses.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Pulse Log',
|
title: 'Pulse Log',
|
||||||
@@ -649,6 +659,9 @@ function App() {
|
|||||||
metric: `${appState.pulses.length} pulses`,
|
metric: `${appState.pulses.length} pulses`,
|
||||||
action: 'Open log',
|
action: 'Open log',
|
||||||
tab: 'pulse-log' as TabKey,
|
tab: 'pulse-log' as TabKey,
|
||||||
|
operatorNote: 'Use this as the honest activity ledger: intent, action, decision, blocker, result.',
|
||||||
|
evidence: ['Pulses can link to features.', 'Filters support pulse type, feature, source, and agent.', 'Recent pulse previews surface movement on the Feature Plan.'],
|
||||||
|
next: 'Add one-click TEST_RESULT and DECISION templates from the functionality detail panel.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'AI Handoff + Export',
|
title: 'AI Handoff + Export',
|
||||||
@@ -658,6 +671,9 @@ function App() {
|
|||||||
metric: 'handoff ready',
|
metric: 'handoff ready',
|
||||||
action: 'Export context',
|
action: 'Export context',
|
||||||
tab: 'export' as TabKey,
|
tab: 'export' as TabKey,
|
||||||
|
operatorNote: 'Use this when another agent or coding session needs clean context without archaeology.',
|
||||||
|
evidence: ['JSON export preserves full app state.', 'JSONL export carries pulse history.', 'Markdown package includes agent-facing project, feature, parking, pulse, and context files.'],
|
||||||
|
next: 'Add a “copy focused handoff” button directly on each capability detail.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Appwrite Sync',
|
title: 'Appwrite Sync',
|
||||||
@@ -667,8 +683,12 @@ function App() {
|
|||||||
metric: backendMode === 'appwrite' ? 'Appwrite' : 'cache',
|
metric: backendMode === 'appwrite' ? 'Appwrite' : 'cache',
|
||||||
action: 'Refresh state',
|
action: 'Refresh state',
|
||||||
tab: 'functionalities' as TabKey,
|
tab: 'functionalities' as TabKey,
|
||||||
|
operatorNote: 'Use this when browser state and backend truth need to be reconciled deliberately.',
|
||||||
|
evidence: ['Public health endpoint reports backend=appwrite.', 'Refresh from backend pulls the Appwrite document into local state.', 'Force sync now pushes the current cockpit state back to Appwrite.'],
|
||||||
|
next: 'Expose last successful pull/push direction as sync provenance.',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
const selectedFunctionality = functionalityCards.find((card) => card.title === selectedFunctionalityTitle) ?? functionalityCards[0]
|
||||||
const backendLabel =
|
const backendLabel =
|
||||||
backendMode === 'appwrite' ? 'Appwrite backend · Unraid server' : backendMode === 'connecting' ? 'Connecting to Appwrite…' : 'Local cache fallback'
|
backendMode === 'appwrite' ? 'Appwrite backend · Unraid server' : backendMode === 'connecting' ? 'Connecting to Appwrite…' : 'Local cache fallback'
|
||||||
const syncLabel =
|
const syncLabel =
|
||||||
@@ -849,24 +869,56 @@ function App() {
|
|||||||
<p>{card.description}</p>
|
<p>{card.description}</p>
|
||||||
<div className="functionality-signal">
|
<div className="functionality-signal">
|
||||||
<span>{card.signal}</span>
|
<span>{card.signal}</span>
|
||||||
<button
|
<div className="button-inline-row">
|
||||||
type="button"
|
<button type="button" className="ghost small" onClick={() => setSelectedFunctionalityTitle(card.title)}>
|
||||||
className="ghost small"
|
Inspect
|
||||||
onClick={() => {
|
</button>
|
||||||
if (card.title === 'Appwrite Sync') {
|
<button
|
||||||
void refreshFromBackend()
|
type="button"
|
||||||
} else {
|
className="ghost small"
|
||||||
setActiveTab(card.tab)
|
onClick={() => {
|
||||||
}
|
if (card.title === 'Appwrite Sync') {
|
||||||
}}
|
void refreshFromBackend()
|
||||||
>
|
} else {
|
||||||
{card.action}
|
setActiveTab(card.tab)
|
||||||
</button>
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{card.action}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<section className="card functionality-detail">
|
||||||
|
<div className="section-heading compact">
|
||||||
|
<div>
|
||||||
|
<p className="eyebrow">Selected functionality</p>
|
||||||
|
<h3>{selectedFunctionality.title}</h3>
|
||||||
|
<p>{selectedFunctionality.operatorNote}</p>
|
||||||
|
</div>
|
||||||
|
<span className={`pill functionality-${selectedFunctionality.status}`}>{selectedFunctionality.status}</span>
|
||||||
|
</div>
|
||||||
|
<div className="functionality-detail-grid">
|
||||||
|
<article>
|
||||||
|
<h4>Proof it exists</h4>
|
||||||
|
<ul>
|
||||||
|
{selectedFunctionality.evidence.map((item) => (
|
||||||
|
<li key={item}>{item}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
<article>
|
||||||
|
<h4>Current signal</h4>
|
||||||
|
<p>{selectedFunctionality.signal}</p>
|
||||||
|
<h4>Next useful improvement</h4>
|
||||||
|
<p>{selectedFunctionality.next}</p>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section className="card functionality-roadmap">
|
<section className="card functionality-roadmap">
|
||||||
<div className="section-heading compact">
|
<div className="section-heading compact">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -726,3 +726,59 @@ pre {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.functionality-detail {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.functionality-detail::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: radial-gradient(circle at top right, rgba(96, 165, 250, 0.1), transparent 34%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.functionality-detail-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||||
|
gap: 1rem;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.functionality-detail-grid article {
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.14);
|
||||||
|
border-radius: 18px;
|
||||||
|
background: rgba(30, 41, 59, 0.58);
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.functionality-detail-grid h4 {
|
||||||
|
margin: 0 0 0.55rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.functionality-detail-grid h4:not(:first-child) {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.functionality-detail-grid p,
|
||||||
|
.functionality-detail-grid ul {
|
||||||
|
margin: 0;
|
||||||
|
color: #c9d4ea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.functionality-detail-grid ul {
|
||||||
|
padding-left: 1.15rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.functionality-detail-grid li + li {
|
||||||
|
margin-top: 0.45rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.functionality-detail-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user