diff --git a/.claude/hookify.block-no-verify.local.md b/.claude/hookify.block-no-verify.local.md new file mode 100644 index 0000000..8ba7b33 --- /dev/null +++ b/.claude/hookify.block-no-verify.local.md @@ -0,0 +1,25 @@ +--- +name: block-no-verify +enabled: true +event: bash +pattern: git\s+commit\s+.*--no-verify|git\s+commit\s+--no-verify +action: block +--- + +**BLOCKED: git commit --no-verify** + +You attempted to bypass pre-commit hooks with `--no-verify`. + +**Why this is blocked:** +- Pre-commit hooks run `make quality` to ensure code standards +- Bypassing them allows broken code to be committed +- The user explicitly requested this protection + +**What to do instead:** +1. Run `make quality` to see what's failing +2. Fix the issues +3. Commit normally: `git commit -m "your message"` + +**If you truly need to bypass (rare cases):** +- Ask the user for explicit permission first +- They can temporarily disable this rule in `.claude/hookify.block-no-verify.local.md` diff --git a/Makefile b/Makefile index 16a0f6c..1c76936 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ .PHONY: all quality quality-ts quality-rs quality-py lint type-check test-quality \ lint-rs clippy fmt fmt-rs fmt-check check help e2e e2e-ui e2e-grpc \ - ensure-py ensure-ts ensure-rs ensure-hygiene + ensure-py ensure-ts ensure-rs ensure-hygiene install-hooks uninstall-hooks # Default target all: quality @@ -215,6 +215,23 @@ e2e-grpc: ensure-rs @echo "Requires: gRPC server running on :50051" cd client/src-tauri && NOTEFLOW_INTEGRATION=1 cargo test --test grpc_integration -- --ignored --nocapture +#------------------------------------------------------------------------------- +# Git Hooks +#------------------------------------------------------------------------------- + +## Install git hooks (pre-commit) +install-hooks: + @echo "=== Installing Git Hooks ===" + @chmod +x scripts/hooks/pre-commit + @ln -sf ../../scripts/hooks/pre-commit .git/hooks/pre-commit + @echo "✓ Pre-commit hook installed" + +## Uninstall git hooks +uninstall-hooks: + @echo "=== Uninstalling Git Hooks ===" + @rm -f .git/hooks/pre-commit + @echo "✓ Pre-commit hook removed" + #------------------------------------------------------------------------------- # Help #------------------------------------------------------------------------------- @@ -264,6 +281,10 @@ help: @echo " e2e-ui Run Playwright e2e tests with UI mode" @echo " e2e-grpc Run Rust gRPC integration tests (real backend wiring)" @echo "" + @echo "Git Hooks:" + @echo " install-hooks Install pre-commit hook" + @echo " uninstall-hooks Remove pre-commit hook" + @echo "" @echo "Dependencies:" @echo " ensure-py Ensure Python tooling is installed" @echo " ensure-ts Ensure client dependencies are installed" diff --git a/docs/sprints/sprint-18.6.md b/docs/sprints/sprint-18.6.md new file mode 100644 index 0000000..09205f1 --- /dev/null +++ b/docs/sprints/sprint-18.6.md @@ -0,0 +1,393 @@ +# Sprint 18.6: Live Meeting UI Refinement + +> **Size**: L | **Owner**: Frontend Team | **Prerequisites**: None +> **Phase**: 1 - UI/UX Polish +> **References**: [Meeting Explorer](https://github.com/aladagemre/meeting-explorer), [Transcript Seeker](https://github.com/Meeting-BaaS/transcript-seeker) + +--- + +## Open Issues & Prerequisites + +> **Review Date**: 2026-01-18 — Initial specification + +### Blocking Issues + +| ID | Issue | Status | Resolution | +|----|-------|--------|------------| +| **B1** | **No blocking issues identified** | N/A | N/A | + +### Design Gaps to Address + +| ID | Gap | Resolution | +|----|-----|------------| +| G1 | Sidebar recording state not synced with Recording page | Implement shared recording context via `Outlet` context | +| G2 | In-transcript search UX not defined | Follow Slack-style "Jump to live" + filter pattern | +| G3 | Notes quick-action buttons not specified | Add "Insert timestamp", "Add action item", "Add decision" | + +### Prerequisite Verification + +| Prerequisite | Status | Notes | +|--------------|--------|-------| +| Resizable panels working | **DONE** | `react-resizable-panels` in use | +| Speaker preferences system | **DONE** | `preferences.getSpeakerName()` exists | +| ApiModeIndicator component | **DONE** | Sprint GAP-007 completed | + +--- + +## Validation Status (2026-01-18) + +### PARTIALLY IMPLEMENTED + +| Component | Status | Notes | +|-----------|--------|-------| +| Status badge consolidation | Partial | `ApiModeIndicator` exists but Recording/Timer/Simulated badges are separate | +| Notes panel resizing | Partial | Resizable but collapsed state is too narrow | +| Speaker renaming | Partial | Backend exists, inline UI not implemented | +| Transcript interactions | Partial | Timestamps shown, no click-to-seek or segment actions | + +### NOT IMPLEMENTED + +| Component | Status | Notes | +|-----------|--------|-------| +| In-transcript search | Not implemented | Global search only in `top-bar.tsx` | +| Notes quick actions | Not implemented | Only manual timestamp insertion exists | +| Jump to live indicator | Not implemented | Auto-scroll exists but no manual trigger | +| Stats panel warnings | Not implemented | Raw values only, no actionable alerts | + +--- + +## Objective + +Refine the live meeting recording UI to reduce cognitive load, improve discoverability, and align with established "transcript viewer + notes" patterns from Meeting Explorer and Transcript Seeker. The goal is to create a calmer, more "enterprise-grade" interface where the recording controls, status indicators, transcript navigation, and notes capture work together cohesively. + +--- + +## Key Decisions + +| Decision | Choice | Rationale | +|----------|--------|-----------| +| **Primary recording control location** | Header only (right side) | Single canonical location reduces "which button?" confusion | +| **Sidebar button behavior** | Navigate to recording page | Acts as shortcut, not duplicate control | +| **Status badge strategy** | Unified status row in header | Consolidate Recording/Timer/Mode into one compact display | +| **Notes panel default width** | 25% minimum (was allowing too narrow) | Ensure placeholder text doesn't wrap vertically | +| **Transcript segment interaction** | Hover actions + click-to-pin | Builds toward transcript-as-navigation pattern | +| **In-transcript search** | Command palette style (Cmd+F) | Separates from global search, familiar pattern | + +--- + +## What Already Exists + +| Asset | Location | Implication | +|-------|----------|-------------| +| `ApiModeIndicator` | `client/src/components/api-mode-indicator.tsx` | Reuse pattern for unified status display | +| `SpeakerBadge` with preferences | `client/src/components/speaker-badge.tsx:26-28` | Extend for inline rename on click | +| `TimestampedNotesEditor` | `client/src/components/timestamped-notes-editor.tsx` | Add quick-action buttons above editor | +| `usePanelPreferences` hook | `client/src/hooks/use-panel-preferences.ts` | Already handles panel sizing persistence | +| `TranscriptSegmentCard` with motion | `client/src/components/recording/transcript-segment-card.tsx` | Add hover actions overlay | +| Outlet context pattern | `client/src/components/app-layout.tsx:30` | Use for shared recording state | +| `formatElapsedTime` utility | `client/src/lib/format.ts` | Reuse for unified timer display | + +--- + +## Scope + +| Task | Effort | Notes | +|------|--------|-------| +| **Phase 1: Control Consolidation** | | | +| 1.1 Unify recording controls to header only | S | Remove duplicate from sidebar, keep navigation | +| 1.2 Create `UnifiedStatusRow` component | M | Combine Recording badge + Timer + Mode indicator | +| 1.3 Update `AppSidebar` to show recording state, not control | S | Button becomes "Go to Recording" when active | +| **Phase 2: Panel Improvements** | | | +| 2.1 Set minimum width constraint on Notes panel | S | Prevent text wrapping issue | +| 2.2 Add tabbed drawer option for Notes/Stats | M | Optional: `Tabs` component in right rail | +| 2.3 Make Stats panel collapsible by default | S | Lead with signal, hide raw telemetry | +| **Phase 3: Notes Workflow Enhancement** | | | +| 3.1 Add quick-action buttons to `NotesPanel` header | M | Insert timestamp, Add action item, Add decision | +| 3.2 Add "Send to notes" hover action on transcript segments | M | Click segment → append to notes | +| 3.3 Improve autosave indicator copy | S | "Auto-save: On" + "Saved 24s ago" format | +| **Phase 4: Transcript Navigation** | | | +| 4.1 Add hover actions to `TranscriptSegmentCard` | M | Copy, Pin, Add to Notes buttons | +| 4.2 Implement "Jump to live" indicator | S | Appears when scrolled up, click returns to bottom | +| 4.3 Add in-transcript search (Cmd+F) | L | Filter/highlight within current meeting | +| **Phase 5: Speaker & Stats Polish** | | | +| 5.1 Add inline speaker rename on badge click | M | Modal or inline input | +| 5.2 Convert raw stats to actionable indicators | M | "Low input", "Clipping", "2 speakers detected" | +| 5.3 Move confidence % to tooltip, show warning only | S | Reduce visual noise | + +**Total Effort**: L (estimated 3-4 days) + +--- + +## UI Components + +### UnifiedStatusRow + +```tsx +// client/src/components/recording/unified-status-row.tsx + +export interface UnifiedStatusRowProps { + recordingState: RecordingState; + elapsedTime: number; + connectionMode: ConnectionMode; + isSimulating: boolean; +} + +export function UnifiedStatusRow({ + recordingState, + elapsedTime, + connectionMode, + isSimulating, +}: UnifiedStatusRowProps) { + return ( +
+ {recordingState === 'recording' && ( + <> + + + Recording + + + {formatElapsedTime(elapsedTime)} + + )} + +
+ ); +} +``` + +### TranscriptSegmentActions (Hover Overlay) + +```tsx +// client/src/components/recording/transcript-segment-actions.tsx + +export interface TranscriptSegmentActionsProps { + segment: FinalSegment; + onCopy: () => void; + onPin: () => void; + onAddToNotes: () => void; +} + +export function TranscriptSegmentActions({ + segment, + onCopy, + onPin, + onAddToNotes, +}: TranscriptSegmentActionsProps) { + return ( +
+ + + +
+ ); +} +``` + +### NotesQuickActions + +```tsx +// client/src/components/recording/notes-quick-actions.tsx + +export interface NotesQuickActionsProps { + onInsertTimestamp: () => void; + onAddActionItem: () => void; + onAddDecision: () => void; + elapsedTime: number; +} + +export function NotesQuickActions({ + onInsertTimestamp, + onAddActionItem, + onAddDecision, + elapsedTime, +}: NotesQuickActionsProps) { + return ( +
+ + + +
+ ); +} +``` + +### JumpToLiveIndicator + +```tsx +// client/src/components/recording/jump-to-live.tsx + +export interface JumpToLiveIndicatorProps { + isVisible: boolean; + onClick: () => void; +} + +export function JumpToLiveIndicator({ isVisible, onClick }: JumpToLiveIndicatorProps) { + if (!isVisible) return null; + + return ( + + ); +} +``` + +### InTranscriptSearch + +```tsx +// client/src/components/recording/in-transcript-search.tsx + +export interface InTranscriptSearchProps { + segments: FinalSegment[]; + onHighlight: (segmentIds: string[]) => void; + onClose: () => void; +} + +export function InTranscriptSearch({ + segments, + onHighlight, + onClose, +}: InTranscriptSearchProps) { + const [query, setQuery] = useState(''); + const [matchCount, setMatchCount] = useState(0); + const [currentMatch, setCurrentMatch] = useState(0); + + // Filter and highlight logic + useEffect(() => { + if (!query) { + onHighlight([]); + setMatchCount(0); + return; + } + const matches = segments.filter((s) => + s.text.toLowerCase().includes(query.toLowerCase()) + ); + onHighlight(matches.map((m) => m.id)); + setMatchCount(matches.length); + }, [query, segments, onHighlight]); + + return ( +
+ setQuery(e.target.value)} + className="flex-1 h-8" + /> + {matchCount > 0 && ( + + {currentMatch + 1} of {matchCount} + + )} + +
+ ); +} +``` + +--- + +## Deliverables + +### Client Components + +- [ ] `client/src/components/recording/unified-status-row.tsx` — Consolidated recording status display +- [ ] `client/src/components/recording/transcript-segment-actions.tsx` — Hover action overlay +- [ ] `client/src/components/recording/notes-quick-actions.tsx` — Quick note capture buttons +- [ ] `client/src/components/recording/jump-to-live.tsx` — Scroll-to-live indicator +- [ ] `client/src/components/recording/in-transcript-search.tsx` — In-meeting search bar +- [ ] `client/src/components/recording/speaker-rename-dialog.tsx` — Inline speaker rename modal + +### Component Updates + +- [ ] `client/src/components/recording/recording-header.tsx` — Use `UnifiedStatusRow`, remove duplicate badges +- [ ] `client/src/components/recording/notes-panel.tsx` — Add `NotesQuickActions`, set min-width +- [ ] `client/src/components/recording/stats-panel.tsx` — Make collapsible, add warning indicators +- [ ] `client/src/components/recording/transcript-segment-card.tsx` — Add hover actions, group class +- [ ] `client/src/components/speaker-badge.tsx` — Add onClick for rename +- [ ] `client/src/components/app-sidebar.tsx` — Change recording button to navigation +- [ ] `client/src/pages/Recording.tsx` — Integrate new components, add keyboard shortcuts + +### Hooks + +- [ ] `client/src/hooks/use-transcript-search.ts` — Search state and matching logic +- [ ] `client/src/hooks/use-keyboard-shortcuts.ts` — Cmd+F binding for in-transcript search + +--- + +## Test Strategy + +### Fixtures to extend or create + +- `client/src/test/mocks/segments.ts`: Add test segments with various speaker IDs +- `client/src/test/mocks/recording-state.ts`: Add recording state mocks + +### Core test cases + +- **UnifiedStatusRow**: Renders all states (idle, recording, paused, stopping) +- **TranscriptSegmentActions**: Hover reveals actions, click handlers fire +- **NotesQuickActions**: Buttons insert correct content +- **JumpToLiveIndicator**: Shows when scrolled up, hides at bottom +- **InTranscriptSearch**: Filters segments, highlights matches, Escape closes +- **SpeakerBadge**: Click opens rename, saves to preferences +- **Integration**: Full recording flow with new components + +--- + +## Quality Gates + +- [ ] `npm run test` passes for all new components +- [ ] `npm run type-check` passes with no new errors +- [ ] `npm run lint` passes with no new warnings +- [ ] All new components have test coverage +- [ ] No `any` types or `// @ts-ignore` comments +- [ ] Accessibility: All buttons have aria-labels/titles +- [ ] Responsive: Panels work at 1024px+ viewport widths + +--- + +## Migration Notes + +### Breaking Changes + +None — all changes are additive or modify internal component structure. + +### Deprecations + +- `ApiModeIndicator` in header alongside recording badge → replaced by `UnifiedStatusRow` +- Sidebar "Start Recording" button → becomes "Go to Recording" during active session + +--- + +## Post-Sprint + +- [ ] Add transcript segment click-to-seek (requires audio playback integration) +- [ ] Add speaker voice sample playback for identification +- [ ] Implement multi-meeting transcript search +- [ ] Add transcript export with timestamps +- [ ] Consider tabbed notes/entities/stats drawer pattern diff --git a/scripts/hooks/pre-commit b/scripts/hooks/pre-commit new file mode 100755 index 0000000..b954de7 --- /dev/null +++ b/scripts/hooks/pre-commit @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# Pre-commit hook for NoteFlow +# Runs quality checks before allowing commits +# +# Install: make install-hooks +# Bypass: git commit --no-verify + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo -e "${YELLOW}Running pre-commit quality checks...${NC}" + +# Get the repo root (in case hook is run from subdirectory) +REPO_ROOT="$(git rev-parse --show-toplevel)" +cd "$REPO_ROOT" + +# Run make quality +if make quality; then + echo -e "${GREEN}✓ All quality checks passed${NC}" + exit 0 +else + echo -e "${RED}✗ Quality checks failed${NC}" + echo -e "${YELLOW}Fix the issues above or use 'git commit --no-verify' to bypass${NC}" + exit 1 +fi