candle-annotator/openspec/specs/ui-shell/spec.md
Marko Djordjevic 448b67199f Sync user-accounts delta specs to main specs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 18:50:14 +01:00

8.9 KiB

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)

MODIFIED Requirements (user-accounts)

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 at /app
  • 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")

ADDED Requirements (user-accounts)

Requirement: App workspace route at /app

The main workspace page SHALL be served at /app (route src/app/app/page.tsx). The existing src/app/page.tsx content (chart, annotations, toolbox, panels) SHALL move to this new route. The /app route SHALL be protected by the auth proxy.

Scenario: Workspace at /app

  • WHEN an authenticated user navigates to /app
  • THEN the full workspace renders (chart, toolbox, panels — same as current /)

Scenario: Unauthenticated redirect

  • WHEN an unauthenticated user navigates to /app
  • THEN they are redirected to /login

Requirement: App layout with user menu

The /app layout SHALL include a top navigation bar with the CandleAnnotator logo (linking to /app), and a user menu on the right side. The user menu SHALL show the user's name/email and provide links to Settings and Sign Out.

Scenario: User menu displays identity

  • WHEN an authenticated user views the app
  • THEN the top nav shows the user's name or email with an avatar/initial

Scenario: User menu dropdown

  • WHEN a user clicks the user menu
  • THEN a dropdown shows: "Settings" (links to /app/settings) and "Sign Out" (calls signOut())

Scenario: Sign out

  • WHEN a user clicks "Sign Out" in the user menu
  • THEN the session is destroyed and the user is redirected to /login

The sidebar SHALL include a settings gear icon button that navigates to /app/settings.

Scenario: Settings button renders

  • WHEN the app workspace loads
  • THEN a settings icon button is visible in the sidebar (near the theme toggle)

Scenario: Navigate to settings

  • WHEN a user clicks the settings icon
  • THEN they are navigated to /app/settings

Requirement: Public page layout

Public pages (/, /login, /register) SHALL use a separate layout (src/app/(public)/layout.tsx) without the workspace sidebar, toolbox, or user menu. They SHALL share the same root layout (fonts, ThemeProvider).

Scenario: Public layout structure

  • WHEN a user views a public page
  • THEN the page renders without sidebar, toolbox, or user menu
  • AND the root layout fonts and theme provider are still active