diff --git a/.claude-done b/.claude-done index 3d76c91a..978da54b 100644 --- a/.claude-done +++ b/.claude-done @@ -1,36 +1,3 @@ -task: refactor-editor-notelist-sidebar -ui-change: no - -## Summary - -Refactored three CodeScene hotspot files to improve code health scores: - -| File | Before | After | Approach | -|------|--------|-------|----------| -| Editor.tsx | 7.82 (cc=35) | 10.0 | Decomposed into EditorContent, EditorRightPanel, SingleEditorView, editorSchema, useDiffMode, useEditorFocus, suggestionEnrichment | -| NoteList.tsx | 9.04 (cc=13) | 9.38 | Extracted createNoteStatusResolver, toggleSetMember standalone functions | -| Sidebar.tsx | 9.09 (cc=10) | 10.0 | Extracted applyCustomization standalone function | - -## New files - -- `src/components/EditorContent.tsx` — breadcrumb bar + editor/diff view switching -- `src/components/EditorRightPanel.tsx` — AI chat / inspector panel toggle -- `src/components/SingleEditorView.tsx` — BlockNote editor with suggestion menus -- `src/components/editorSchema.tsx` — WikiLink inline content spec + BlockNote schema -- `src/hooks/useDiffMode.ts` — diff mode state management -- `src/hooks/useEditorFocus.ts` — editor focus on custom event -- `src/utils/suggestionEnrichment.ts` — shared suggestion item enrichment pipeline - -## New tests (20 tests) - -- `src/hooks/useDiffMode.test.ts` — 10 tests -- `src/hooks/useEditorFocus.test.ts` — 3 tests -- `src/utils/suggestionEnrichment.test.ts` — 7 tests - -## Validation - -- 976 tests pass (956 existing + 20 new) -- 80% frontend coverage (≥70% required) -- 270 Rust tests pass -- CodeScene quality gates: passed -- Zero behavior change — pure refactor +Task: 🐛 Property dropdown fix +Summary: Fixed dropdown collision/overflow when Properties panel is narrow. StatusDropdown and DisplayModeSelector were using absolute positioning within the Inspector's overflow-hidden container, causing clipping at narrow widths (200-280px). Both now render via createPortal with fixed positioning, matching the pattern used by existing Radix UI components. All 1017 frontend tests + 270 Rust tests pass. +Commits: 1 diff --git a/design/fix-property-dropdown-narrow.pen b/design/fix-property-dropdown-narrow.pen new file mode 100644 index 00000000..deff115d --- /dev/null +++ b/design/fix-property-dropdown-narrow.pen @@ -0,0 +1,56 @@ +{ + "children": [ + { + "type": "frame", + "id": "fpdn_before", + "name": "Property Dropdown Narrow — Before (clipped)", + "x": 0, + "y": 0, + "width": 280, + "height": 200, + "fill": "#1a1a1a", + "layout": "vertical", + "gap": 8, + "padding": 12, + "theme": "dark", + "children": [ + { + "type": "text", + "id": "fpdn_before_label", + "content": "Bug: StatusDropdown clipped by overflow:hidden container at 200-280px panel width", + "fill": "#ff4444", + "fontFamily": "SF Pro Text", + "fontSize": 12, + "fontWeight": "regular", + "letterSpacing": 0 + } + ] + }, + { + "type": "frame", + "id": "fpdn_after", + "name": "Property Dropdown Narrow — After (portal, no clip)", + "x": 320, + "y": 0, + "width": 280, + "height": 200, + "fill": "#1a1a1a", + "layout": "vertical", + "gap": 8, + "padding": 12, + "theme": "dark", + "children": [ + { + "type": "text", + "id": "fpdn_after_label", + "content": "Fix: StatusDropdown + DisplayModeSelector use createPortal with fixed positioning — renders above overflow:hidden, visible at any panel width", + "fill": "#44bb44", + "fontFamily": "SF Pro Text", + "fontSize": 12, + "fontWeight": "regular", + "letterSpacing": 0 + } + ] + } + ] +} diff --git a/src/components/DynamicPropertiesPanel.tsx b/src/components/DynamicPropertiesPanel.tsx index f9222cf2..556d5f5a 100644 --- a/src/components/DynamicPropertiesPanel.tsx +++ b/src/components/DynamicPropertiesPanel.tsx @@ -1,4 +1,5 @@ import { useMemo, useState, useCallback, useRef } from 'react' +import { createPortal } from 'react-dom' import type { VaultEntry } from '../types' import type { FrontmatterValue } from './Inspector' import type { ParsedFrontmatter } from '../utils/frontmatter' @@ -186,7 +187,19 @@ function DisplayModeSelector({ propKey, currentMode, autoMode, onSelect }: { onSelect: (key: string, mode: PropertyDisplayMode | null) => void }) { const [open, setOpen] = useState(false) - const containerRef = useRef(null) + const triggerRef = useRef(null) + + const positionMenu = useCallback((node: HTMLDivElement | null) => { + if (!node) return + const el = triggerRef.current + if (!el) return + const rect = el.getBoundingClientRect() + const menuW = 140 + let left = rect.right - menuW + if (left < 8) left = 8 + node.style.top = `${rect.bottom + 4}px` + node.style.left = `${left}px` + }, []) const handleSelect = (mode: PropertyDisplayMode) => { if (mode === autoMode) { @@ -198,8 +211,9 @@ function DisplayModeSelector({ propKey, currentMode, autoMode, onSelect }: { } return ( -
+
- {open && ( + {open && createPortal( <>
setOpen(false)} />
{DISPLAY_MODE_OPTIONS.map(opt => ( @@ -231,7 +246,8 @@ function DisplayModeSelector({ propKey, currentMode, autoMode, onSelect }: { ))}
- + , + document.body )}
) diff --git a/src/components/StatusDropdown.tsx b/src/components/StatusDropdown.tsx index caafb5f2..dc3394a6 100644 --- a/src/components/StatusDropdown.tsx +++ b/src/components/StatusDropdown.tsx @@ -1,4 +1,5 @@ import { useState, useRef, useEffect, useCallback, useMemo } from 'react' +import { createPortal } from 'react-dom' import { getStatusStyle, SUGGESTED_STATUSES, setStatusColor, getStatusColorKey } from '../utils/statusStyles' import { ACCENT_COLORS } from '../utils/typeColors' @@ -227,6 +228,20 @@ export function StatusDropdown({ const [colorEditingStatus, setColorEditingStatus] = useState(null) const inputRef = useRef(null) const listRef = useRef(null) + const anchorRef = useRef(null) + + const positionDropdown = useCallback((node: HTMLDivElement | null) => { + if (!node) return + const anchor = anchorRef.current?.parentElement + if (!anchor) return + const rect = anchor.getBoundingClientRect() + const dropW = 208 + let left = rect.right - dropW + if (left < 8) left = 8 + if (left + dropW > window.innerWidth - 8) left = window.innerWidth - dropW - 8 + node.style.top = `${rect.bottom + 4}px` + node.style.left = `${left}px` + }, []) useEffect(() => { inputRef.current?.focus() }, []) @@ -268,46 +283,52 @@ export function StatusDropdown({ } return ( -
-
-
-
- handleQueryChange(e.target.value)} - onKeyDown={handleKeyDown} - data-testid="status-search-input" - /> -
-
- - 0} - {...optionProps} - highlightOffset={vaultFiltered.length} - /> - 0} - highlighted={highlightIndex === allFiltered.length} - onSave={onSave} - onMouseEnter={() => setHighlightIndex(allFiltered.length)} - /> - {allFiltered.length === 0 && !showCreateOption && ( -
- No matching statuses +
+ {createPortal( + <> +
+
+
+ handleQueryChange(e.target.value)} + onKeyDown={handleKeyDown} + data-testid="status-search-input" + />
- )} -
-
+
+ + 0} + {...optionProps} + highlightOffset={vaultFiltered.length} + /> + 0} + highlighted={highlightIndex === allFiltered.length} + onSave={onSave} + onMouseEnter={() => setHighlightIndex(allFiltered.length)} + /> + {allFiltered.length === 0 && !showCreateOption && ( +
+ No matching statuses +
+ )} +
+
+ , + document.body + )}
) }