feat: add functionalities overview

This commit is contained in:
OpenClaw Bot
2026-05-09 19:16:30 +02:00
parent 2d2febb7aa
commit 96c26c2406
3 changed files with 292 additions and 2 deletions
+157 -1
View File
@@ -9,6 +9,7 @@ import type { AppState, Feature, FeatureColumn, ParkingLotItem, PulseEvent, Risk
import { arrayToLines, formatDateTime, linesToArray, nowIso, slugify } from './utils/format'
const TABS: Array<{ key: TabKey; label: string }> = [
{ key: 'functionalities', label: 'Functionalities' },
{ key: 'feature-plan', label: 'Feature Plan' },
{ key: 'parking-lot', label: 'Parking Lot' },
{ key: 'pulse-log', label: 'Pulse Log' },
@@ -65,7 +66,7 @@ const downloadText = (filename: string, text: string, contentType = 'text/plain;
function App() {
const [appState, setAppState] = useState<AppState>(() => loadAppState())
const [activeTab, setActiveTab] = useState<TabKey>('feature-plan')
const [activeTab, setActiveTab] = useState<TabKey>('functionalities')
const [statusMessage, setStatusMessage] = useState('Seeded with BuildPulse so you can dogfood it immediately.')
const [selectedFeatureId, setSelectedFeatureId] = useState<string | null>(null)
const [selectedParkingId, setSelectedParkingId] = useState<string | null>(null)
@@ -611,6 +612,63 @@ function App() {
const currentFeatureCount = groupedFeatures.now.length
const recentPulsePreview = [...appState.pulses].sort((a, b) => b.timestamp.localeCompare(a.timestamp)).slice(0, 3)
const completedFeatureCount = groupedFeatures.done.length
const functionalityCards = [
{
title: 'Project Cockpit',
status: 'live',
description: 'Single-project mission, goal, notes, and focus statistics stay visible before the board tries to swallow the room.',
signal: appState.project.current_goal ? 'Goal set' : 'Needs current goal',
metric: appState.project.name,
action: 'Edit summary',
tab: 'functionalities' as TabKey,
},
{
title: 'Feature Plan',
status: currentFeatureCount ? 'active' : 'ready',
description: 'Now / Next / Later / Done columns keep work small, shaped, and movable without becoming Jira in a fake moustache.',
signal: `${appState.features.length} total · ${currentFeatureCount} now · ${completedFeatureCount} done`,
metric: `${currentFeatureCount} now`,
action: 'Open board',
tab: 'feature-plan' as TabKey,
},
{
title: 'Parking Lot',
status: appState.parking_lot.length ? 'active' : 'ready',
description: 'Useful distractions get captured, risk-tagged, and converted into features only when they earn their keep.',
signal: `${appState.parking_lot.length} parked idea${appState.parking_lot.length === 1 ? '' : 's'}`,
metric: `${appState.parking_lot.length} parked`,
action: 'Review parked',
tab: 'parking-lot' as TabKey,
},
{
title: 'Pulse Log',
status: appState.pulses.length ? 'active' : 'ready',
description: 'Intent, decisions, blockers, test results, and outcomes form a future-compatible trail for agents and humans.',
signal: recentPulsePreview[0] ? `Latest: ${recentPulsePreview[0].pulse_type}` : 'No pulses yet',
metric: `${appState.pulses.length} pulses`,
action: 'Open log',
tab: 'pulse-log' as TabKey,
},
{
title: 'AI Handoff + Export',
status: 'live',
description: 'Generate JSON, JSONL, Markdown packages, and focused session prompts so coding agents get context without soup.',
signal: `${Object.keys(markdownPackage).length} markdown files ready`,
metric: 'handoff ready',
action: 'Export context',
tab: 'export' as TabKey,
},
{
title: 'Appwrite Sync',
status: backendMode === 'appwrite' && syncStatus === 'synced' ? 'live' : syncStatus === 'degraded' ? 'degraded' : 'syncing',
description: 'State persists through the Appwrite runtime document, with explicit refresh and force-sync controls for operator recovery.',
signal: backendMode === 'appwrite' ? `Sync status: ${syncStatus}` : 'Local cache fallback active',
metric: backendMode === 'appwrite' ? 'Appwrite' : 'cache',
action: 'Refresh state',
tab: 'functionalities' as TabKey,
},
]
const backendLabel =
backendMode === 'appwrite' ? 'Appwrite backend · Unraid server' : backendMode === 'connecting' ? 'Connecting to Appwrite…' : 'Local cache fallback'
const syncLabel =
@@ -722,6 +780,9 @@ function App() {
</nav>
<div className="quick-actions card">
<button type="button" className="ghost" onClick={() => setActiveTab('functionalities')}>
Show Functionalities
</button>
<button type="button" onClick={() => { setActiveTab('feature-plan'); resetFeatureDraft() }}>
Add Feature
</button>
@@ -736,6 +797,101 @@ function App() {
</button>
</div>
{activeTab === 'functionalities' && (
<section className="view-stack">
<div className="section-heading">
<div>
<h2>Functionalities</h2>
<p>The living map of what BuildPulse actually does right now no brochure fog, no phantom roadmap theatre.</p>
</div>
<div className="functionality-summary">
<span className="pill status-healthy">NPM live</span>
<span className="pill status-healthy">Appwrite backed</span>
<span className="pill">Unraid runtime</span>
</div>
</div>
<section className="card functionality-hero">
<div>
<p className="eyebrow">Capability map</p>
<h3>{appState.project.name} is a pulse-compatible feature cockpit.</h3>
<p>
It keeps the product thread visible: define the mission, shape features, park distractions, log movement, sync state,
and hand clean context to AI agents without turning the app into a bloated command bunker.
</p>
</div>
<div className="functionality-scorecard">
<div>
<span>Live functions</span>
<strong>{functionalityCards.filter((card) => card.status === 'live' || card.status === 'active').length}</strong>
</div>
<div>
<span>Operator recovery</span>
<strong>{backendMode === 'appwrite' ? 'Ready' : 'Cache'}</strong>
</div>
<div>
<span>Context packages</span>
<strong>{Object.keys(markdownPackage).length}</strong>
</div>
</div>
</section>
<div className="functionality-grid">
{functionalityCards.map((card) => (
<article key={card.title} className={`card functionality-card functionality-${card.status}`}>
<div className="item-card-header">
<div>
<p className="eyebrow">{card.status}</p>
<h3>{card.title}</h3>
</div>
<span className="pill">{card.metric}</span>
</div>
<p>{card.description}</p>
<div className="functionality-signal">
<span>{card.signal}</span>
<button
type="button"
className="ghost small"
onClick={() => {
if (card.title === 'Appwrite Sync') {
void refreshFromBackend()
} else {
setActiveTab(card.tab)
}
}}
>
{card.action}
</button>
</div>
</article>
))}
</div>
<section className="card functionality-roadmap">
<div className="section-heading compact">
<div>
<h3>What this is deliberately not yet</h3>
<p>Guardrails matter. The small cockpit wins because it refuses to cosplay as a whole enterprise suite.</p>
</div>
</div>
<div className="roadmap-grid">
<div>
<strong>Not Jira</strong>
<p>No issue jungle, sprint ceremony, or fake certainty factory.</p>
</div>
<div>
<strong>Not an autonomous agent framework</strong>
<p>Agent ingestion can come later; manual pulse truth comes first.</p>
</div>
<div>
<strong>Not multi-project yet</strong>
<p>Single-project discipline keeps v0.1 sharp enough to dogfood.</p>
</div>
</div>
</section>
</section>
)}
{activeTab === 'feature-plan' && (
<section className="view-stack">
<div className="section-heading">