From c4c9b6f86a5152b87c9615e95af7c8f11ef7be1f Mon Sep 17 00:00:00 2001 From: OpenClaw Bot Date: Sat, 9 May 2026 19:38:42 +0200 Subject: [PATCH] feat: add functionality detail panels --- src/App.tsx | 78 ++++++++++++++++++++++++++++++++++++++++++--------- src/index.css | 56 ++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 13 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 342bd13..52746b1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -83,6 +83,7 @@ function App() { const [syncStatus, setSyncStatus] = useState<'connecting' | 'synced' | 'pending' | 'syncing' | 'degraded'>('connecting') const [lastSyncedAt, setLastSyncedAt] = useState(null) const [syncAction, setSyncAction] = useState<'idle' | 'refreshing' | 'pushing'>('idle') + const [selectedFunctionalityTitle, setSelectedFunctionalityTitle] = useState('Project Cockpit') const hasHydratedRemote = useRef(false) const initialLocalStateRef = useRef(appState) @@ -622,6 +623,9 @@ function App() { metric: appState.project.name, action: 'Edit summary', 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', @@ -631,6 +635,9 @@ function App() { metric: `${currentFeatureCount} now`, action: 'Open board', 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', @@ -640,6 +647,9 @@ function App() { metric: `${appState.parking_lot.length} parked`, action: 'Review parked', 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', @@ -649,6 +659,9 @@ function App() { metric: `${appState.pulses.length} pulses`, action: 'Open log', 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', @@ -658,6 +671,9 @@ function App() { metric: 'handoff ready', action: 'Export context', 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', @@ -667,8 +683,12 @@ function App() { metric: backendMode === 'appwrite' ? 'Appwrite' : 'cache', action: 'Refresh state', 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 = backendMode === 'appwrite' ? 'Appwrite backend · Unraid server' : backendMode === 'connecting' ? 'Connecting to Appwrite…' : 'Local cache fallback' const syncLabel = @@ -849,24 +869,56 @@ function App() {

{card.description}

{card.signal} - +
+ + +
))} +
+
+
+

Selected functionality

+

{selectedFunctionality.title}

+

{selectedFunctionality.operatorNote}

+
+ {selectedFunctionality.status} +
+
+
+

Proof it exists

+
    + {selectedFunctionality.evidence.map((item) => ( +
  • {item}
  • + ))} +
+
+
+

Current signal

+

{selectedFunctionality.signal}

+

Next useful improvement

+

{selectedFunctionality.next}

+
+
+
+
diff --git a/src/index.css b/src/index.css index 99a875f..b53d2b9 100644 --- a/src/index.css +++ b/src/index.css @@ -726,3 +726,59 @@ pre { 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; + } +}