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' import { arrayToLines, formatDateTime, linesToArray, nowIso, slugify } from './utils/format'
const TABS: Array<{ key: TabKey; label: string }> = [ const TABS: Array<{ key: TabKey; label: string }> = [
{ key: 'functionalities', label: 'Functionalities' },
{ key: 'feature-plan', label: 'Feature Plan' }, { key: 'feature-plan', label: 'Feature Plan' },
{ key: 'parking-lot', label: 'Parking Lot' }, { key: 'parking-lot', label: 'Parking Lot' },
{ key: 'pulse-log', label: 'Pulse Log' }, { key: 'pulse-log', label: 'Pulse Log' },
@@ -65,7 +66,7 @@ const downloadText = (filename: string, text: string, contentType = 'text/plain;
function App() { function App() {
const [appState, setAppState] = useState<AppState>(() => loadAppState()) 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 [statusMessage, setStatusMessage] = useState('Seeded with BuildPulse so you can dogfood it immediately.')
const [selectedFeatureId, setSelectedFeatureId] = useState<string | null>(null) const [selectedFeatureId, setSelectedFeatureId] = useState<string | null>(null)
const [selectedParkingId, setSelectedParkingId] = useState<string | null>(null) const [selectedParkingId, setSelectedParkingId] = useState<string | null>(null)
@@ -611,6 +612,63 @@ function App() {
const currentFeatureCount = groupedFeatures.now.length const currentFeatureCount = groupedFeatures.now.length
const recentPulsePreview = [...appState.pulses].sort((a, b) => b.timestamp.localeCompare(a.timestamp)).slice(0, 3) 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 = 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 =
@@ -722,6 +780,9 @@ function App() {
</nav> </nav>
<div className="quick-actions card"> <div className="quick-actions card">
<button type="button" className="ghost" onClick={() => setActiveTab('functionalities')}>
Show Functionalities
</button>
<button type="button" onClick={() => { setActiveTab('feature-plan'); resetFeatureDraft() }}> <button type="button" onClick={() => { setActiveTab('feature-plan'); resetFeatureDraft() }}>
Add Feature Add Feature
</button> </button>
@@ -736,6 +797,101 @@ function App() {
</button> </button>
</div> </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' && ( {activeTab === 'feature-plan' && (
<section className="view-stack"> <section className="view-stack">
<div className="section-heading"> <div className="section-heading">
+134
View File
@@ -592,3 +592,137 @@ pre {
align-items: stretch; align-items: stretch;
} }
} }
.functionality-summary,
.functionality-signal {
display: flex;
align-items: center;
gap: 0.6rem;
flex-wrap: wrap;
}
.functionality-hero {
display: grid;
grid-template-columns: minmax(0, 1.4fr) minmax(260px, 0.6fr);
gap: 1rem;
overflow: hidden;
position: relative;
}
.functionality-hero::after {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(135deg, rgba(45, 212, 191, 0.08), rgba(129, 140, 248, 0.08), transparent 70%);
pointer-events: none;
}
.functionality-hero > *,
.functionality-card > *,
.functionality-roadmap > * {
position: relative;
z-index: 1;
}
.functionality-hero h3,
.functionality-card h3,
.functionality-roadmap h3 {
margin: 0;
}
.functionality-hero p,
.functionality-card p,
.functionality-roadmap p {
color: #c9d4ea;
}
.functionality-scorecard,
.roadmap-grid {
display: grid;
gap: 0.75rem;
}
.functionality-scorecard div,
.roadmap-grid div {
border: 1px solid rgba(148, 163, 184, 0.14);
border-radius: 18px;
background: rgba(30, 41, 59, 0.58);
padding: 1rem;
}
.functionality-scorecard span {
display: block;
color: #9fb4d9;
font-size: 0.82rem;
}
.functionality-scorecard strong {
display: block;
margin-top: 0.25rem;
font-size: 1.45rem;
}
.functionality-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 1rem;
}
.functionality-card {
display: flex;
flex-direction: column;
gap: 0.85rem;
min-height: 240px;
overflow: hidden;
position: relative;
}
.functionality-card::before {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
opacity: 0.7;
pointer-events: none;
}
.functionality-live::before,
.functionality-active::before {
background: linear-gradient(180deg, rgba(52, 211, 153, 0.09), transparent 55%);
}
.functionality-ready::before,
.functionality-syncing::before {
background: linear-gradient(180deg, rgba(96, 165, 250, 0.09), transparent 55%);
}
.functionality-degraded::before {
background: linear-gradient(180deg, rgba(248, 113, 113, 0.12), transparent 55%);
}
.functionality-signal {
margin-top: auto;
justify-content: space-between;
color: #bfdbfe;
font-size: 0.9rem;
}
.roadmap-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
@media (max-width: 1080px) {
.functionality-grid,
.functionality-hero,
.roadmap-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 720px) {
.functionality-summary,
.functionality-signal {
align-items: stretch;
flex-direction: column;
}
}
+1 -1
View File
@@ -99,4 +99,4 @@ export interface AppState {
settings: Settings settings: Settings
} }
export type TabKey = 'feature-plan' | 'parking-lot' | 'pulse-log' | 'export' export type TabKey = 'functionalities' | 'feature-plan' | 'parking-lot' | 'pulse-log' | 'export'