diff --git a/README.md b/README.md index 6ae16c44..74312e3e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # BandScope -BandScope is a public GitHub project for a local-first desktop app that turns a song into a practical rehearsal view: likely harmony by section and by instrument or vocal role, section roadmap, tempo and groove cues, separated stems, playable ranges, simplification hints, transposition or capo guidance, overlap cues, visible confidence, and rehearsal priorities without DAW complexity. +BandScope is a public GitHub project for a local-first desktop app that turns a song into a practical rehearsal view: likely harmony by section and by instrument or vocal role, section roadmap, tempo and groove cues, rough stem previews, playable ranges, simplification hints, transposition or capo guidance, overlap cues, visible confidence, and rehearsal priorities without DAW complexity. It does not promise notation-grade full arrangement transcription or DAW-style production editing. diff --git a/apps/desktop/.gitignore b/apps/desktop/.gitignore new file mode 100644 index 00000000..20687473 --- /dev/null +++ b/apps/desktop/.gitignore @@ -0,0 +1 @@ +storybook-static diff --git a/apps/desktop/.storybook/main.ts b/apps/desktop/.storybook/main.ts new file mode 100644 index 00000000..fa5c0b52 --- /dev/null +++ b/apps/desktop/.storybook/main.ts @@ -0,0 +1,23 @@ +import type { StorybookConfig } from "@storybook/react-vite" +import tailwindcss from "@tailwindcss/vite" +import path from "node:path" +import { fileURLToPath } from "node:url" + +const dir = path.dirname(fileURLToPath(import.meta.url)) + +const config: StorybookConfig = { + stories: ["../src/**/*.stories.@(ts|tsx)"], + addons: [], + framework: { name: "@storybook/react-vite", options: {} }, + viteFinal: async (viteConfig) => { + viteConfig.plugins = [...(viteConfig.plugins ?? []), tailwindcss()] + viteConfig.resolve = viteConfig.resolve ?? {} + viteConfig.resolve.alias = { + ...(viteConfig.resolve.alias ?? {}), + "@": path.resolve(dir, "../src"), + } + return viteConfig + }, +} + +export default config diff --git a/apps/desktop/.storybook/preview.ts b/apps/desktop/.storybook/preview.ts new file mode 100644 index 00000000..81f8c701 --- /dev/null +++ b/apps/desktop/.storybook/preview.ts @@ -0,0 +1,14 @@ +import type { Preview } from "@storybook/react-vite" + +import "../src/index.css" + +const preview: Preview = { + parameters: { + layout: "centered", + controls: { + matchers: { color: /(background|color)$/i, date: /Date$/i }, + }, + }, +} + +export default preview diff --git a/apps/desktop/package.json b/apps/desktop/package.json index e8b42e09..3a3f2011 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -6,6 +6,8 @@ "scripts": { "dev": "vite", "build": "vite build", + "storybook": "storybook dev -p 6006", + "build-storybook": "storybook build", "lint": "eslint \"src/**/*.{ts,tsx}\" vite.config.ts", "typecheck": "tsc --noEmit", "test": "node -e \"require('node:fs').mkdirSync('coverage/.tmp', { recursive: true })\" && vitest run --coverage" @@ -20,10 +22,12 @@ "lucide-react": "^1.20.0", "react": "^19.2.4", "react-dom": "^19.2.7", + "sonner": "^2.0.7", "tailwind-merge": "^3.6.0", "tw-animate-css": "^1.4.0" }, "devDependencies": { + "@storybook/react-vite": "^10.4.6", "@tailwindcss/vite": "^4.3.1", "@tauri-apps/cli": "^2.11.2", "@testing-library/jest-dom": "^6.6.3", @@ -35,6 +39,7 @@ "@vitest/coverage-v8": "^4.1.5", "eslint": "^10.5.0", "jsdom": "^29.1.1", + "storybook": "^10.4.6", "tailwindcss": "^4.2.4", "typescript": "^6.0.3", "typescript-eslint": "^8.60.1", diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx index 24f5fb09..96f25ae2 100644 --- a/apps/desktop/src/App.tsx +++ b/apps/desktop/src/App.tsx @@ -46,6 +46,7 @@ import { EmptyState, ErrorState, LoadingState } from "./features/workspace/Works import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Progress } from "@/components/ui/progress"; +import { Toaster } from "@/components/ui/sonner"; const ANALYSIS_POLL_INTERVAL_MS = 250; const MAX_ERROR_DETAIL_LENGTH = 220; @@ -736,6 +737,8 @@ export function App() { + + ); } diff --git a/apps/desktop/src/components/ui/accordion.tsx b/apps/desktop/src/components/ui/accordion.tsx new file mode 100644 index 00000000..752e33a0 --- /dev/null +++ b/apps/desktop/src/components/ui/accordion.tsx @@ -0,0 +1,76 @@ +"use client" + +import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion" +import { ChevronDown } from "lucide-react" + +import { cn } from "@/lib/utils" + +/** Render a vertically stacked set of collapsible sections. */ +function Accordion({ className, ...props }: AccordionPrimitive.Root.Props) { + return ( + + ) +} + +/** Render one collapsible accordion item. */ +function AccordionItem({ className, ...props }: AccordionPrimitive.Item.Props) { + return ( + + ) +} + +/** Render the clickable header that toggles an item open or closed. */ +function AccordionTrigger({ + className, + children, + ...props +}: AccordionPrimitive.Trigger.Props) { + return ( + + svg]:rotate-180", + className + )} + {...props} + > + {children} + + + + ) +} + +/** Render the collapsible content panel of an item. */ +function AccordionContent({ + className, + children, + ...props +}: AccordionPrimitive.Panel.Props) { + return ( + +
{children}
+
+ ) +} + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/apps/desktop/src/components/ui/breadcrumb.tsx b/apps/desktop/src/components/ui/breadcrumb.tsx new file mode 100644 index 00000000..76061293 --- /dev/null +++ b/apps/desktop/src/components/ui/breadcrumb.tsx @@ -0,0 +1,107 @@ +import * as React from "react" +import { ChevronRight, MoreHorizontal } from "lucide-react" + +import { cn } from "@/lib/utils" + +/** Render the breadcrumb navigation landmark. */ +function Breadcrumb(props: React.ComponentProps<"nav">) { + return