refactor(sidebar): collapse rail/sections to single-IA, log docs

- Sidebar: kill the drifting railGroups + sections dual definition.
  Single source of truth (workItems / libraryItems / footerItems)
  rendered in both pinned and rail modes; pin/unpin is a width and
  label affordance, not an IA switch. Hairline divider replaces
  section labels. Guides moves to the footer alongside Account.
  Renames: Home -> Dashboard, History -> Sessions, Insights -> Analytics.
- CURRENT-STATE.md: log PR #158 (session impeccable pass + tasklane
  keyboard flow) under "Recently shipped".
- PRODUCT.md: design-context source of truth (users, brand, aesthetic);
  sibling to DESIGN-SYSTEM.md.
- skills-lock.json: lock /impeccable + /documentation-writer skill
  versions so other sessions reproduce the same tooling state.
- Drop stale .impeccable.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-04 22:50:19 -04:00
parent 93fa4eac5c
commit 0f90c0e199
4 changed files with 69 additions and 84 deletions

View File

@@ -12,6 +12,7 @@
## Recently shipped (post-0.1.0.0) ## Recently shipped (post-0.1.0.0)
- **2026-05-01 — PR #158** Session-screen UX impeccable pass + tasklane keyboard flow. Heuristic score 24/40 → 33/40 across five sub-passes (distill, quieter, layout, typeset, polish). Removed duplicate "Suggested checks" chip strip → TaskLane is the single source of truth; added inline `Next steps · N pending` cue on the latest action-bearing AI bubble; consolidated session header to Resolve + Escalate + ⋯ kebab; centered messages column to match composer; dropped all banned decorations (side stripes, gradient surfaces, backdrop blur, accent borderTop) for a single decoration channel per surface; unified 14 text sizes into a 5-step scale. TaskLane keyboard flow: Enter submits + auto-advances, Shift+Enter newline, Esc cancel, focus jumps to Send after the last task. Banner ↔ script-panel are now linked (collapse hides both, any outcome closes both). WhatWeKnow section is collapsible with `sessionStorage` memory + auto-collapse-at-5-facts. Side fix: ParameterizationPreview no longer over-highlights short parameter values (word-boundary check). Two backlog entries logged in `.ai/TODO.md`: ConcludeSessionModal multi-select and `bg-card-hover` Tailwind drift in CommandPalette.
- **2026-05-01 — PR #156** Suggested-fix "Awaiting verification" outcome. Engineers can now park a fix in `applied_pending` (waiting on client power-cycle, AD replication, license sync, etc.) instead of forcing a synchronous worked/didn't/partial verdict. PendingBanner with worked / didn't / update reason / dismiss; nudge "Still checking" records pending with a reason; page-level Resolve auto-patches pending → success before the resolution flow opens; page-level Escalate intercepts pending. Migration `c0f3a4b7e91d` (`pending_reason` column + status CHECK constraint). - **2026-05-01 — PR #156** Suggested-fix "Awaiting verification" outcome. Engineers can now park a fix in `applied_pending` (waiting on client power-cycle, AD replication, license sync, etc.) instead of forcing a synchronous worked/didn't/partial verdict. PendingBanner with worked / didn't / update reason / dismiss; nudge "Still checking" records pending with a reason; page-level Resolve auto-patches pending → success before the resolution flow opens; page-level Escalate intercepts pending. Migration `c0f3a4b7e91d` (`pending_reason` column + status CHECK constraint).
- **2026-04-30 — PR #155** Escalation Mode wedge. Magic-moment handoff-context screen for senior pickup, live SSE escalation arrivals, post-claim time-to-first-action metric (`GET /analytics/flowpilot/escalations`), atomic role-gated claim with conflict resolution, queue self-exclusion, chat ownership extended to claimed sessions. The wedge for the first paying-customer push. - **2026-04-30 — PR #155** Escalation Mode wedge. Magic-moment handoff-context screen for senior pickup, live SSE escalation arrivals, post-claim time-to-first-action metric (`GET /analytics/flowpilot/escalations`), atomic role-gated claim with conflict resolution, queue self-exclusion, chat ownership extended to claimed sessions. The wedge for the first paying-customer push.

View File

@@ -2,10 +2,10 @@ import { useCallback, useEffect, useRef, useState, type PointerEvent as ReactPoi
import { Link, useLocation } from 'react-router-dom' import { Link, useLocation } from 'react-router-dom'
import type { LucideIcon } from 'lucide-react' import type { LucideIcon } from 'lucide-react'
import { import {
LayoutGrid, Clock, AlertTriangle, GitBranch, Code2, Wand2, LayoutGrid, Clock, AlertTriangle, GitBranch,
ListChecks, Download, BarChart3, ListChecks, BarChart3,
Settings, Pin, PinOff, Settings, Pin, PinOff,
History, FileText, Network, Ticket, FileText, Ticket, BookOpen,
} from 'lucide-react' } from 'lucide-react'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { useUserPreferencesStore } from '@/store/userPreferencesStore' import { useUserPreferencesStore } from '@/store/userPreferencesStore'
@@ -31,11 +31,6 @@ interface NavEntry {
children?: NavSubItem[] children?: NavSubItem[]
} }
interface NavSection {
title: string
items: NavEntry[]
}
/* ── Sidebar component ──────────────────────────────── */ /* ── Sidebar component ──────────────────────────────── */
export function Sidebar() { export function Sidebar() {
@@ -78,36 +73,40 @@ export function Sidebar() {
/* ── Navigation data ──────────────────────────────── */ /* ── Navigation data ──────────────────────────────── */
/* ── Grouped nav: 5 top-level icons (Sentry-style) ── */ /* Single source-of-truth IA. Same items, same order, in both rail
* and pinned modes. Pin/unpin is a width/label affordance, not an
* IA switch. A hairline divider separates the two groups; no labels. */
const railGroups: NavEntry[] = [ const workItems: NavEntry[] = [
{ {
href: '/', icon: LayoutGrid, label: 'Home', shortLabel: 'Home', href: '/', icon: LayoutGrid, label: 'Dashboard', shortLabel: 'Dash',
matchPaths: ['/'], matchPaths: ['/'],
}, },
{
href: '/sessions', icon: History, label: 'History', shortLabel: 'History',
badge: stats?.active_count || undefined,
matchPaths: ['/sessions', '/escalations', '/pilot'],
children: [
{ href: '/sessions', label: 'Session History', count: stats?.active_count || undefined },
{ href: '/escalations', label: 'Escalations', count: stats?.escalation_count || undefined },
],
},
{ {
href: '/tickets', icon: Ticket, label: 'Tickets', shortLabel: 'Tickets', href: '/tickets', icon: Ticket, label: 'Tickets', shortLabel: 'Tickets',
matchPaths: ['/tickets'], matchPaths: ['/tickets'],
}, },
{
href: '/sessions', icon: Clock, label: 'Sessions', shortLabel: 'Sessions',
badge: stats?.active_count || undefined,
matchPaths: ['/sessions'],
},
{
href: '/escalations', icon: AlertTriangle, label: 'Escalations', shortLabel: 'Escal',
badge: stats?.escalation_count || undefined,
matchPaths: ['/escalations'],
},
]
const libraryItems: NavEntry[] = [
{ {
href: '/trees', icon: GitBranch, label: 'Flows', shortLabel: 'Flows', href: '/trees', icon: GitBranch, label: 'Flows', shortLabel: 'Flows',
badge: stats?.tree_counts.total || undefined, badge: stats?.tree_counts.total || undefined,
matchPaths: ['/trees', '/flows', '/my-trees', '/step-library', '/review-queue', '/network-diagrams'], matchPaths: ['/trees', '/flows', '/my-trees', '/step-library', '/network-diagrams'],
children: [ children: [
{ href: '/trees', label: 'Flow Library', count: stats?.tree_counts.total || undefined },
{ href: '/trees?type=procedural', label: 'Projects', count: stats?.tree_counts.procedural || undefined }, { href: '/trees?type=procedural', label: 'Projects', count: stats?.tree_counts.procedural || undefined },
{ href: '/network-diagrams', label: 'Network Maps' },
{ href: '/step-library', label: 'Solutions Library' }, { href: '/step-library', label: 'Solutions Library' },
{ href: '/review-queue', label: 'Review Queue' }, { href: '/network-diagrams', label: 'Network Maps' },
], ],
}, },
{ {
@@ -115,60 +114,25 @@ export function Sidebar() {
badge: pendingDraftCount || undefined, badge: pendingDraftCount || undefined,
matchPaths: ['/scripts', '/script-builder'], matchPaths: ['/scripts', '/script-builder'],
children: [ children: [
{ href: '/scripts', label: 'Script Library', count: pendingDraftCount || undefined },
{ href: '/script-builder', label: 'Script Builder' }, { href: '/script-builder', label: 'Script Builder' },
], ],
}, },
{ {
href: '/analytics', icon: BarChart3, label: 'Insights', shortLabel: 'Data', href: '/review-queue', icon: ListChecks, label: 'Review Queue', shortLabel: 'Review',
matchPaths: ['/review-queue'],
},
{
href: '/analytics', icon: BarChart3, label: 'Analytics', shortLabel: 'Stats',
matchPaths: ['/analytics', '/shares'], matchPaths: ['/analytics', '/shares'],
children: [ children: [
{ href: '/analytics', label: 'Analytics' },
{ href: '/shares', label: 'Exports' }, { href: '/shares', label: 'Exports' },
], ],
}, },
] ]
/* Pinned mode still uses the detailed section layout */
const sections: NavSection[] = [
{
title: 'RESOLVE',
items: [
{ href: '/', icon: LayoutGrid, label: 'Dashboard', shortLabel: 'Dash' },
{ href: '/sessions', icon: Clock, label: 'Session History', shortLabel: 'History', badge: stats?.active_count || undefined, matchPaths: ['/sessions'] },
{ href: '/tickets', icon: Ticket, label: 'Tickets', shortLabel: 'Tickets', matchPaths: ['/tickets'] },
{ href: '/escalations', icon: AlertTriangle, label: 'Escalations', shortLabel: 'Escal', badge: stats?.escalation_count || undefined },
],
},
{
title: 'KNOWLEDGE',
items: [
{
href: '/trees', icon: GitBranch, label: 'Flow Library', shortLabel: 'Flows',
badge: stats?.tree_counts.total || undefined,
matchPaths: ['/trees', '/flows', '/my-trees'],
children: [
{ href: '/trees', label: 'Flow Library' },
{ href: '/trees?type=procedural', label: 'Projects', count: stats?.tree_counts.procedural || undefined },
],
},
{ href: '/network-diagrams', icon: Network, label: 'Network Maps', shortLabel: 'NetMap', matchPaths: ['/network-diagrams'] },
{ href: '/scripts', icon: Code2, label: 'Scripts', shortLabel: 'Scripts' },
{ href: '/script-builder', icon: Wand2, label: 'Script Builder', shortLabel: 'Builder' },
{ href: '/review-queue', icon: ListChecks, label: 'Review Queue', shortLabel: 'Review' },
],
},
{
title: 'INSIGHTS',
items: [
{ href: '/analytics', icon: BarChart3, label: 'Analytics', shortLabel: 'Stats' },
{ href: '/shares', icon: Download, label: 'Exports', shortLabel: 'Export' },
],
},
]
const footerItems: NavEntry[] = [ const footerItems: NavEntry[] = [
{ href: '/account', icon: Settings, label: 'Account', shortLabel: 'Acct' }, { href: '/guides', icon: BookOpen, label: 'Guides', shortLabel: 'Guides', matchPaths: ['/guides'] },
{ href: '/account', icon: Settings, label: 'Account', shortLabel: 'Acct', matchPaths: ['/account'] },
] ]
/* ── Active detection ─────────────────────────────── */ /* ── Active detection ─────────────────────────────── */
@@ -369,9 +333,9 @@ export function Sidebar() {
/* ── Find active flyout group for drawer ── */ /* ── Find active flyout group for drawer ── */
const allRailItems = [...workItems, ...libraryItems, ...footerItems]
const activeFlyoutGroup = flyoutIndex && !sidebarPinned const activeFlyoutGroup = flyoutIndex && !sidebarPinned
? railGroups.find((_, i) => `rail-${i}` === flyoutIndex) || ? allRailItems.find(item => item.href === flyoutIndex) || null
footerItems.find((_, i) => `footer-${i}` === flyoutIndex)
: null : null
/* ── Main render ──────────────────────────────────── */ /* ── Main render ──────────────────────────────────── */
@@ -386,23 +350,20 @@ export function Sidebar() {
> >
{/* Pinned sidebar content */} {/* Pinned sidebar content */}
<div className="px-3 py-2 space-y-0.5"> <div className="px-3 py-2 space-y-0.5">
{sections.map((section, si) => ( {workItems.map(item => renderPinnedItem(item, item.href))}
<div key={section.title}> <div
{si > 0 && ( className="my-3 border-t"
<div className="font-mono text-[0.5625rem] uppercase tracking-[0.12em] text-text-muted px-3 pt-3 pb-1"> style={{ borderColor: 'var(--color-border-default)' }}
{section.title} aria-hidden="true"
</div> />
)} {libraryItems.map(item => renderPinnedItem(item, item.href))}
{section.items.map((item, ii) => renderPinnedItem(item, `${si}-${ii}`))}
</div>
))}
</div> </div>
<div className="flex-1" /> <div className="flex-1" />
{/* Footer */} {/* Footer */}
<div className="px-3 pt-2 pb-4 space-y-0.5" style={{ borderTop: '1px solid var(--color-border-default)' }}> <div className="px-3 pt-2 pb-4 space-y-0.5" style={{ borderTop: '1px solid var(--color-border-default)' }}>
{footerItems.map((item, i) => renderPinnedItem(item, `footer-${i}`))} {footerItems.map(item => renderPinnedItem(item, item.href))}
<button <button
type="button" type="button"
onClick={toggleSidebarPinned} onClick={toggleSidebarPinned}
@@ -417,7 +378,7 @@ export function Sidebar() {
) )
} }
/* Icon Rail (default) — 5 grouped icons, Sentry-style */ /* Icon rail (default, unpinned) — same items as pinned mode, narrower. */
return ( return (
<div <div
className="flex h-full" className="flex h-full"
@@ -432,14 +393,20 @@ export function Sidebar() {
> >
{/* Nav items */} {/* Nav items */}
<div className="flex flex-col items-center w-full px-1 space-y-1.5"> <div className="flex flex-col items-center w-full px-1 space-y-1.5">
{railGroups.map((item, i) => renderRailItem(item, `rail-${i}`))} {workItems.map(item => renderRailItem(item, item.href))}
<div
className="w-8 my-1 border-t self-center"
style={{ borderColor: 'var(--color-border-default)' }}
aria-hidden="true"
/>
{libraryItems.map(item => renderRailItem(item, item.href))}
</div> </div>
<div className="flex-1" /> <div className="flex-1" />
{/* Footer: Account + Pin */} {/* Footer: Guides, Account + Pin */}
<div className="flex flex-col items-center w-full px-1 pb-5 pt-3 space-y-1.5" style={{ borderTop: '1px solid var(--color-border-default)' }}> <div className="flex flex-col items-center w-full px-1 pb-5 pt-3 space-y-1.5" style={{ borderTop: '1px solid var(--color-border-default)' }}>
{footerItems.map((item, i) => renderRailItem(item, `footer-${i}`))} {footerItems.map(item => renderRailItem(item, item.href))}
<button <button
type="button" type="button"
onClick={toggleSidebarPinned} onClick={toggleSidebarPinned}
@@ -471,7 +438,7 @@ export function Sidebar() {
> >
{/* Drawer header */} {/* Drawer header */}
<div className="px-3 mb-3"> <div className="px-3 mb-3">
<h3 className="text-[0.6875rem] font-mono uppercase tracking-[0.12em] text-[#fbbf24]"> <h3 className="text-[0.6875rem] font-mono uppercase tracking-[0.12em] text-text-muted">
{activeFlyoutGroup.label} {activeFlyoutGroup.label}
</h3> </h3>
</div> </div>

17
skills-lock.json Normal file
View File

@@ -0,0 +1,17 @@
{
"version": 1,
"skills": {
"documentation-writer": {
"source": "github/awesome-copilot",
"sourceType": "github",
"skillPath": "skills/documentation-writer/SKILL.md",
"computedHash": "ee53d65b163cd7eb953a930c95841cfe398cc2c0bd24c06508bbaa07c432be35"
},
"impeccable": {
"source": "pbakaus/impeccable",
"sourceType": "github",
"skillPath": ".agents/skills/impeccable/SKILL.md",
"computedHash": "de38608ceb9573c3142306babd9057a240791c4e4d6647f874a4650c996cb37e"
}
}
}