## ADDED Requirements ### Requirement: Theme toggle in sidebar The UI shell SHALL include a theme toggle button in the sidebar. The button SHALL be positioned at the bottom of the sidebar, visually separated from the tool buttons. #### Scenario: Toggle button renders - **WHEN** the application loads - **THEN** a theme toggle button is visible at the bottom of the sidebar #### Scenario: Toggle button displays correct icon - **WHEN** the current theme mode is "system" - **THEN** the button shows a monitor icon (lucide-react `Monitor`) - **WHEN** the current theme mode is "light" - **THEN** the button shows a sun icon (lucide-react `Sun`) - **WHEN** the current theme mode is "dark" - **THEN** the button shows a moon icon (lucide-react `Moon`) #### Scenario: Toggle button has tooltip - **WHEN** user hovers over the theme toggle button - **THEN** a tooltip displays the current mode name (e.g., "Theme: System", "Theme: Light", "Theme: Dark") ### Requirement: Accessibility on interactive elements All interactive elements (buttons, dropdowns, modals) SHALL have `aria-label` attributes describing their function. Toggle buttons SHALL use `aria-pressed`. The keyboard shortcuts modal SHALL have `role="dialog"` and `aria-modal="true"`. #### Scenario: Button has aria-label - **WHEN** a toolbar button renders - **THEN** it has an `aria-label` attribute describing its action (e.g., "Draw span annotation") #### Scenario: Modal has dialog role - **WHEN** the keyboard shortcuts modal opens - **THEN** it has `role="dialog"` and `aria-modal="true"` ### Requirement: Focus trapping in modals Modal dialogs (keyboard shortcuts, confirmation dialogs) SHALL trap focus within the modal while open. Tab and Shift+Tab SHALL cycle through focusable elements within the modal. #### Scenario: Focus trapped in modal - **WHEN** a modal is open and the user presses Tab - **THEN** focus cycles through focusable elements within the modal only ### Requirement: Dark theme on settings pages The annotation-types and span-label-types settings pages SHALL use theme-aware CSS variables instead of hardcoded light colors. They SHALL render correctly in both light and dark themes. #### Scenario: Settings page in dark mode - **WHEN** the theme is set to dark and user navigates to annotation-types page - **THEN** the page renders with dark background and light text (no hardcoded white backgrounds) ### Requirement: next/font for Google Fonts The application SHALL use `next/font/google` to load Google Fonts instead of CSS `@import` in `globals.css`. This prevents render-blocking font loading. #### Scenario: Font loaded via next/font - **WHEN** the application loads - **THEN** Google Fonts are loaded via `next/font/google` (not via CSS `@import url()` in globals.css) ### Requirement: Confidence slider debounce The confidence threshold slider in PredictionPanel SHALL debounce chart re-renders. The slider SHALL update the displayed value immediately but only trigger chart updates after 150ms of inactivity (or on `onPointerUp`). #### Scenario: Dragging slider does not re-render chart per pixel - **WHEN** the user drags the confidence slider - **THEN** the chart re-renders at most once every 150ms (not on every pixel movement) ### Requirement: ChartSelector closes on outside click The custom chart selector dropdown SHALL close when the user clicks outside of it. #### Scenario: Click outside closes dropdown - **WHEN** the chart selector dropdown is open and the user clicks elsewhere on the page - **THEN** the dropdown closes ### Requirement: Dead code removal The following dead code SHALL be removed: - `src/lib/db/migrate.ts` (SQLite migration code) - `get_db_session()` in `services/ml/app/db.py` (unused session leak) - Dead filter code (TODO comment, no-op) in `page.tsx` - Dead `inference*` package reference in `pyproject.toml` #### Scenario: No dead migration code - **WHEN** `src/lib/db/migrate.ts` is searched for - **THEN** the file does not exist #### Scenario: No unused db session function - **WHEN** `services/ml/app/db.py` is inspected - **THEN** `get_db_session()` function is absent ### Requirement: Deprecated Python API replacements The ML service SHALL replace deprecated Python APIs: - `@app.on_event("startup")` replaced with lifespan pattern - `declarative_base()` replaced with `class Base(DeclarativeBase)` - `datetime.utcnow()` replaced with `datetime.now(datetime.UTC)` #### Scenario: No deprecated startup event - **WHEN** `services/ml/app/main.py` is inspected - **THEN** startup logic uses the FastAPI lifespan pattern (not `@app.on_event`) #### Scenario: No deprecated utcnow - **WHEN** `datetime.utcnow()` is searched for in the ML service - **THEN** zero matches are found (all replaced with `datetime.now(datetime.UTC)`) ### Requirement: Unused import cleanup Components SHALL not have unused imports. Specifically, `Toolbox.tsx` SHALL remove unused `TrendingUp` and `ChevronUp` imports. #### Scenario: No unused imports in Toolbox - **WHEN** `Toolbox.tsx` is inspected - **THEN** only imported symbols that are used in the component are present ### Requirement: Tooltip component functional or removed The `ui/tooltip.tsx` component SHALL either be implemented using Radix Tooltip or removed if unused. #### Scenario: Tooltip is functional - **WHEN** the Tooltip component is used - **THEN** it renders an actual tooltip on hover (not a no-op passthrough) ### Requirement: SpanAnnotationList confidence check for zero The confidence display in SpanAnnotationList SHALL use `!= null` instead of a falsy check, so that a confidence value of `0` is correctly displayed. #### Scenario: Confidence zero displayed - **WHEN** a span annotation has confidence value `0` - **THEN** the list displays "0%" (not hidden as if confidence is absent)