feat: Add Makefile targets for git pre-commit hook installation and block --no-verify commits.

This commit is contained in:
2026-01-19 00:18:00 +00:00
parent 796f13b358
commit 3aacef9d68
4 changed files with 470 additions and 1 deletions

View File

@@ -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`

View File

@@ -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"

393
docs/sprints/sprint-18.6.md Normal file
View File

@@ -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 (
<div className="flex items-center gap-2 text-sm">
{recordingState === 'recording' && (
<>
<Badge variant="recording" className="gap-1.5">
<RecordingDot />
Recording
</Badge>
<span className="text-muted-foreground"></span>
<span className="font-mono">{formatElapsedTime(elapsedTime)}</span>
</>
)}
<ApiModeIndicator mode={connectionMode} isSimulating={isSimulating} compact />
</div>
);
}
```
### 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 (
<div className="absolute right-2 top-2 opacity-0 group-hover:opacity-100 transition-opacity flex gap-1">
<Button variant="ghost" size="icon-sm" onClick={onCopy} title="Copy text">
<Copy className="h-3.5 w-3.5" />
</Button>
<Button variant="ghost" size="icon-sm" onClick={onPin} title="Pin segment">
<Pin className="h-3.5 w-3.5" />
</Button>
<Button variant="ghost" size="icon-sm" onClick={onAddToNotes} title="Add to notes">
<FileText className="h-3.5 w-3.5" />
</Button>
</div>
);
}
```
### 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 (
<div className="flex gap-1 mb-2">
<Button variant="outline" size="xs" onClick={onInsertTimestamp}>
<Clock className="h-3 w-3 mr-1" />
{formatElapsedTime(elapsedTime)}
</Button>
<Button variant="outline" size="xs" onClick={onAddActionItem}>
<CheckSquare className="h-3 w-3 mr-1" />
Action
</Button>
<Button variant="outline" size="xs" onClick={onAddDecision}>
<Gavel className="h-3 w-3 mr-1" />
Decision
</Button>
</div>
);
}
```
### 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 (
<Button
variant="secondary"
size="sm"
onClick={onClick}
className="fixed bottom-4 left-1/2 -translate-x-1/2 shadow-lg z-50"
>
<ArrowDown className="h-4 w-4 mr-1" />
Jump to live
</Button>
);
}
```
### 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 (
<div className="flex items-center gap-2 p-2 border-b bg-muted/50">
<Input
autoFocus
placeholder="Find in transcript..."
value={query}
onChange={(e) => setQuery(e.target.value)}
className="flex-1 h-8"
/>
{matchCount > 0 && (
<span className="text-xs text-muted-foreground">
{currentMatch + 1} of {matchCount}
</span>
)}
<Button variant="ghost" size="icon-sm" onClick={onClose}>
<X className="h-4 w-4" />
</Button>
</div>
);
}
```
---
## 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

30
scripts/hooks/pre-commit Executable file
View File

@@ -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