feat: discipline buildpulse today commands
This commit is contained in:
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "buildpulse",
|
||||
"private": true,
|
||||
"version": "0.4.2",
|
||||
"version": "0.4.3",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"api": "node --env-file=../.env server/index.mjs",
|
||||
|
||||
+120
-25
@@ -148,6 +148,7 @@ function App() {
|
||||
const [showManualFeatureEditor, setShowManualFeatureEditor] = useState(false)
|
||||
const [showManualParkingEditor, setShowManualParkingEditor] = useState(false)
|
||||
const [showManualPulseEditor, setShowManualPulseEditor] = useState(false)
|
||||
const [todayCommandDrawerOpen, setTodayCommandDrawerOpen] = useState(false)
|
||||
const hasHydratedRemote = useRef(false)
|
||||
const initialLocalStateRef = useRef(appState)
|
||||
const triageStatusRef = useRef<HTMLDivElement | null>(null)
|
||||
@@ -1531,7 +1532,7 @@ function App() {
|
||||
<div className="app-shell">
|
||||
<header className="mobile-shell-header card">
|
||||
<div>
|
||||
<p className="eyebrow">BuildPulse v0.4.2</p>
|
||||
<p className="eyebrow">BuildPulse v0.4.3</p>
|
||||
<h1>BuildPulse</h1>
|
||||
<p className="hero-goal compact-goal">
|
||||
<strong>Current goal:</strong> {appState.project.current_goal || 'Classify new ideas before they become work.'}
|
||||
@@ -2429,16 +2430,123 @@ function App() {
|
||||
<h2>Capture → Brief → Build</h2>
|
||||
<p>One decision at a time. Capture the idea, pick the next move, hand it to an agent.</p>
|
||||
</div>
|
||||
<div className="button-inline-row">
|
||||
<button type="button" className="primary-triage-button" onClick={() => openTriage()}>
|
||||
+ Add Idea
|
||||
</button>
|
||||
<button type="button" className="ghost" onClick={() => setShowManualFeatureEditor((current) => !current)}>
|
||||
Manual
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="today-command-bar" aria-label="Today primary workflow">
|
||||
<button type="button" className="primary-triage-button today-primary-command" onClick={() => openTriage()}>
|
||||
Capture new idea
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="ghost today-drawer-toggle"
|
||||
aria-expanded={todayCommandDrawerOpen}
|
||||
aria-controls="today-command-drawer"
|
||||
onClick={() => setTodayCommandDrawerOpen((current) => !current)}
|
||||
>
|
||||
{todayCommandDrawerOpen ? 'Hide commands' : 'Commands'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{todayCommandDrawerOpen && (
|
||||
<div id="today-command-drawer" className="today-command-drawer" aria-label="Secondary Today commands">
|
||||
<div>
|
||||
<p className="eyebrow">Build</p>
|
||||
<button
|
||||
type="button"
|
||||
className="drawer-command primary"
|
||||
disabled={!focusFeature}
|
||||
onClick={() => {
|
||||
if (!focusFeature) return
|
||||
setTodayCommandDrawerOpen(false)
|
||||
openHandoffPreview(focusFeature.id, 'OpenClaw')
|
||||
}}
|
||||
>
|
||||
Start AI session
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="drawer-command"
|
||||
disabled={!focusFeature}
|
||||
onClick={() => {
|
||||
if (!focusFeature) return
|
||||
setTodayCommandDrawerOpen(false)
|
||||
openFeatureResultCapture(focusFeature.id)
|
||||
}}
|
||||
>
|
||||
Record result
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="drawer-command"
|
||||
disabled={!focusFeature}
|
||||
onClick={() => {
|
||||
if (!focusFeature) return
|
||||
setTodayCommandDrawerOpen(false)
|
||||
beginFeatureEdit(focusFeature)
|
||||
}}
|
||||
>
|
||||
Focus details
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<p className="eyebrow">Sort</p>
|
||||
<button
|
||||
type="button"
|
||||
className="drawer-command"
|
||||
onClick={() => {
|
||||
setTodayCommandDrawerOpen(false)
|
||||
setShowManualFeatureEditor((current) => !current)
|
||||
}}
|
||||
>
|
||||
Manual feature
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="drawer-command"
|
||||
onClick={() => {
|
||||
setTodayCommandDrawerOpen(false)
|
||||
setActiveTab('parking-lot')
|
||||
}}
|
||||
>
|
||||
Parked ideas
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="drawer-command"
|
||||
onClick={() => {
|
||||
setTodayCommandDrawerOpen(false)
|
||||
setActiveTab('pulse-log')
|
||||
}}
|
||||
>
|
||||
Pulse history
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<p className="eyebrow">Ship</p>
|
||||
<button
|
||||
type="button"
|
||||
className="drawer-command"
|
||||
onClick={() => {
|
||||
setTodayCommandDrawerOpen(false)
|
||||
setActiveTab('roadmap')
|
||||
}}
|
||||
>
|
||||
Release view
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="drawer-command"
|
||||
onClick={() => {
|
||||
setTodayCommandDrawerOpen(false)
|
||||
setActiveTab('export')
|
||||
}}
|
||||
>
|
||||
Session handoff
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="today-grid">
|
||||
<article className="focus-panel today-focus-card">
|
||||
<p className="eyebrow">Focus</p>
|
||||
@@ -2451,17 +2559,7 @@ function App() {
|
||||
<span className="pill">{focusFeature.status}</span>
|
||||
<span className="pill">{columnLabels[focusFeature.column]}</span>
|
||||
</div>
|
||||
<div className="button-inline-row sticky-action-row">
|
||||
<button type="button" className="primary small" onClick={() => openHandoffPreview(focusFeature.id, 'OpenClaw')}>
|
||||
Start AI Session
|
||||
</button>
|
||||
<button type="button" className="ghost small" onClick={() => openFeatureResultCapture(focusFeature.id)}>
|
||||
Record Result
|
||||
</button>
|
||||
<button type="button" className="ghost small" onClick={() => beginFeatureEdit(focusFeature)}>
|
||||
Details
|
||||
</button>
|
||||
</div>
|
||||
<p className="today-card-note">Use Commands for build, result, and detail actions.</p>
|
||||
</>
|
||||
) : (
|
||||
<p>No focus feature yet. Add an idea and let triage decide if it deserves to become work.</p>
|
||||
@@ -2477,17 +2575,14 @@ function App() {
|
||||
<small>{selectedReleaseBlockers.length ? `${selectedReleaseBlockers.length} required item(s) still open` : 'No required blockers detected'}</small>
|
||||
<small>{selectedRelease?.forbidden_feature_titles.length ? `Forbidden right now: ${selectedRelease.forbidden_feature_titles.slice(0, 3).join(', ')}` : 'No forbidden-work list yet'}</small>
|
||||
</div>
|
||||
<button type="button" className="ghost small" onClick={() => setActiveTab('roadmap')}>Release</button>
|
||||
<p className="today-card-note">Release controls live in Commands.</p>
|
||||
</article>
|
||||
|
||||
<article className="focus-panel today-decision-card">
|
||||
<p className="eyebrow">Needs decision</p>
|
||||
<h3>{duplicateParkingGroups.length ? `${duplicateParkingGroups.length} duplicate idea group${duplicateParkingGroups.length === 1 ? '' : 's'}` : 'No obvious duplicate pile-ups'}</h3>
|
||||
<p>{appState.parking_lot.length} parked · {appState.pulses.length} pulses</p>
|
||||
<div className="button-inline-row">
|
||||
<button type="button" className="ghost small" onClick={() => setActiveTab('parking-lot')}>Ideas</button>
|
||||
<button type="button" className="ghost small" onClick={() => setActiveTab('pulse-log')}>History</button>
|
||||
</div>
|
||||
<p className="today-card-note">Decision controls live in Commands.</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -2722,3 +2722,81 @@ select {
|
||||
line-height: 1.12;
|
||||
}
|
||||
}
|
||||
|
||||
/* v0.4.3 — interaction discipline: one obvious Today path, secondary commands in a drawer. */
|
||||
.today-command-bar {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 0.7rem;
|
||||
align-items: stretch;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.today-primary-command {
|
||||
min-height: 3rem;
|
||||
justify-content: center;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.today-drawer-toggle {
|
||||
min-width: 9rem;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.today-command-drawer {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 0.75rem;
|
||||
margin: -0.2rem 0 1rem;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid rgba(125, 211, 252, 0.18);
|
||||
border-radius: 12px;
|
||||
background: rgba(3, 7, 18, 0.72);
|
||||
}
|
||||
|
||||
.drawer-command {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
margin-top: 0.45rem;
|
||||
border-color: rgba(148, 163, 184, 0.16);
|
||||
background: rgba(148, 163, 184, 0.06);
|
||||
}
|
||||
|
||||
.drawer-command.primary {
|
||||
justify-content: center;
|
||||
border-color: rgba(125, 211, 252, 0.5);
|
||||
background: linear-gradient(135deg, rgba(14, 165, 233, 0.92), rgba(59, 130, 246, 0.92));
|
||||
color: #f8fbff;
|
||||
}
|
||||
|
||||
.drawer-command:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.42;
|
||||
}
|
||||
|
||||
.today-card-note {
|
||||
margin-top: 0.75rem;
|
||||
color: #8fa0bb;
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.today-command-bar {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0.45rem;
|
||||
margin: 0.75rem 0 0.55rem;
|
||||
}
|
||||
|
||||
.today-drawer-toggle {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.today-command-drawer {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0.55rem;
|
||||
margin: 0 0 0.45rem;
|
||||
padding: 0.6rem;
|
||||
border-radius: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user