diff --git a/.gitignore b/.gitignore index 95282512b..892fd87e4 100644 --- a/.gitignore +++ b/.gitignore @@ -82,3 +82,6 @@ chat_exports/!veza-stream-server/src/bin/ /blob-report/ /playwright/.cache/ /playwright/.auth/ + +*storybook.log +storybook-static diff --git a/Makefile b/Makefile index 0edea4ce4..c7fe0fb60 100644 --- a/Makefile +++ b/Makefile @@ -95,6 +95,12 @@ help: ## Show this dashboard setup: check-tools install-tools install-deps ## [HIGH] Full project initialization @$(ECHO_CMD) "${BOLD}${GREEN}✅ Setup Complete! Ready to rock with 'make dev'.${NC}" +web-minimal: ## [HIGH] Start Veza Web Minimal Journey (Backend + Frontend + DB) + @./scripts/start_minimal.sh + +stop-minimal: ## [HIGH] Stop Minimal Stack + @./scripts/stop_minimal.sh + stop-all: ## [HIGH] Stop all services (Docker + Local) @$(ECHO_CMD) "${RED}🛑 Stopping all services...${NC}" @docker compose -f $(COMPOSE_FILE) down 2>/dev/null || true @@ -600,7 +606,12 @@ incus-logs: ## [LOW] Show logs from Incus container (usage: make incus-logs SERV # ============================================================================== # TEST & QUALITY # ============================================================================== -.PHONY: test lint fmt status +.PHONY: test test-tmt lint fmt status + +test-tmt: ## [MID] Run Unified TMT Pipeline + @$(ECHO_CMD) "${BLUE}🧪 Running TMT Pipeline...${NC}" + @command -v tmt >/dev/null 2>&1 || { $(ECHO_CMD) "${RED}❌ tmt is missing! Install with 'pip install tmt'${NC}"; exit 1; } + @tmt run test: infra-up ## [MID] Run All Tests (Fastest strategy) @$(ECHO_CMD) "${BLUE}🧪 Running Tests...${NC}" diff --git a/apps/web/.storybook/main.ts b/apps/web/.storybook/main.ts new file mode 100644 index 000000000..5edb8f46b --- /dev/null +++ b/apps/web/.storybook/main.ts @@ -0,0 +1,19 @@ +import type { StorybookConfig } from '@storybook/react-vite'; + +import { dirname, join } from "path" + +function getAbsolutePath(value: string) { + return dirname(require.resolve(join(value, "package.json"))) +} + +const config: StorybookConfig = { + "stories": [ + "../src/**/*.mdx", + "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)" + ], + "addons": [ + getAbsolutePath('@storybook/addon-essentials') + ], + "framework": getAbsolutePath('@storybook/react-vite') +}; +export default config; \ No newline at end of file diff --git a/apps/web/.storybook/preview.ts b/apps/web/.storybook/preview.ts new file mode 100644 index 000000000..5fa9e7456 --- /dev/null +++ b/apps/web/.storybook/preview.ts @@ -0,0 +1,22 @@ +import type { Preview } from '@storybook/react-vite'; +import '../src/index.css'; + +const preview: Preview = { + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + + a11y: { + // 'todo' - show a11y violations in the test UI only + // 'error' - fail CI on a11y violations + // 'off' - skip a11y checks entirely + test: 'todo', + }, + }, +}; + +export default preview; \ No newline at end of file diff --git a/apps/web/.storybook/vitest.setup.ts b/apps/web/.storybook/vitest.setup.ts new file mode 100644 index 000000000..44922d55e --- /dev/null +++ b/apps/web/.storybook/vitest.setup.ts @@ -0,0 +1,7 @@ +import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview"; +import { setProjectAnnotations } from '@storybook/react-vite'; +import * as projectAnnotations from './preview'; + +// This is an important step to apply the right configuration when testing your stories. +// More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations +setProjectAnnotations([a11yAddonAnnotations, projectAnnotations]); \ No newline at end of file diff --git a/apps/web/eslint.config.js b/apps/web/eslint.config.js index d8b52c59a..70775332a 100644 --- a/apps/web/eslint.config.js +++ b/apps/web/eslint.config.js @@ -1,3 +1,6 @@ +// For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format +import storybook from "eslint-plugin-storybook"; + import js from '@eslint/js'; import typescript from '@typescript-eslint/eslint-plugin'; import typescriptParser from '@typescript-eslint/parser'; @@ -6,225 +9,221 @@ import reactHooks from 'eslint-plugin-react-hooks'; import reactRefresh from 'eslint-plugin-react-refresh'; import jsxA11y from 'eslint-plugin-jsx-a11y'; -export default [ - js.configs.recommended, - { - files: ['**/*.{ts,tsx,js,jsx}'], - languageOptions: { - parser: typescriptParser, - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - ecmaVersion: 2022, - sourceType: 'module', - }, - globals: { - // Browser globals - window: 'readonly', - document: 'readonly', - localStorage: 'readonly', - sessionStorage: 'readonly', - console: 'readonly', - setTimeout: 'readonly', - clearTimeout: 'readonly', - setInterval: 'readonly', - clearInterval: 'readonly', - fetch: 'readonly', - WebSocket: 'readonly', - File: 'readonly', - FormData: 'readonly', - CustomEvent: 'readonly', - Event: 'readonly', - CloseEvent: 'readonly', - MessageEvent: 'readonly', - KeyboardEvent: 'readonly', - HTMLElement: 'readonly', - HTMLDivElement: 'readonly', - HTMLInputElement: 'readonly', - HTMLButtonElement: 'readonly', - HTMLAnchorElement: 'readonly', - HTMLParagraphElement: 'readonly', - HTMLHeadingElement: 'readonly', - HTMLTextAreaElement: 'readonly', - HTMLSelectElement: 'readonly', - HTMLImageElement: 'readonly', - HTMLAudioElement: 'readonly', - Element: 'readonly', - Node: 'readonly', - MouseEvent: 'readonly', - Blob: 'readonly', - FileReader: 'readonly', - Image: 'readonly', - global: 'readonly', - NodeJS: 'readonly', - Buffer: 'readonly', - crypto: 'readonly', - performance: 'readonly', - require: 'readonly', - process: 'readonly', - // URL API globals - URL: 'readonly', - URLSearchParams: 'readonly', - // DOM API globals - DOMRect: 'readonly', - DOMRectReadOnly: 'readonly', - Headers: 'readonly', - navigator: 'readonly', - WindowEventMap: 'readonly', - requestAnimationFrame: 'readonly', - cancelAnimationFrame: 'readonly', - Notification: 'readonly', - NotificationOptions: 'readonly', - NotificationPermission: 'readonly', - IntersectionObserver: 'readonly', - IntersectionObserverInit: 'readonly', - MessageChannel: 'readonly', - confirm: 'readonly', - // Service Worker globals - self: 'readonly', - caches: 'readonly', - ServiceWorkerRegistration: 'readonly', - Cache: 'readonly', - CacheStorage: 'readonly', - Response: 'readonly', - Request: 'readonly', - clients: 'readonly', - // React globals - React: 'readonly', - // Test globals - beforeAll: 'readonly', - afterAll: 'readonly', - afterEach: 'readonly', - beforeEach: 'readonly', - describe: 'readonly', - it: 'readonly', - test: 'readonly', - expect: 'readonly', - vi: 'readonly', - vitest: 'readonly', - waitFor: 'readonly', - jest: 'readonly', - AbortController: 'readonly', - AbortSignal: 'readonly', - BroadcastChannel: 'readonly', - DOMException: 'readonly', - atob: 'readonly', - PerformanceNavigationTiming: 'readonly', - PerformanceObserver: 'readonly', - HTMLFormElement: 'readonly', - HTMLTableElement: 'readonly', - HTMLTableSectionElement: 'readonly', - HTMLTableRowElement: 'readonly', - HTMLTableCellElement: 'readonly', - HTMLTableCaptionElement: 'readonly', - HTMLSpanElement: 'readonly', - HTMLCanvasElement: 'readonly', - HTMLLabelElement: 'readonly', - FileList: 'readonly', - MediaQueryListEvent: 'readonly', - IntersectionObserver: 'readonly', - IntersectionObserverEntry: 'readonly', - IntersectionObserverCallback: 'readonly', - ResizeObserver: 'readonly', - ResizeObserverEntry: 'readonly', - HeadersInit: 'readonly', - EventListener: 'readonly', +export default [js.configs.recommended, { + files: ['**/*.{ts,tsx,js,jsx}'], + languageOptions: { + parser: typescriptParser, + parserOptions: { + ecmaFeatures: { + jsx: true, }, + ecmaVersion: 2022, + sourceType: 'module', }, - plugins: { - '@typescript-eslint': typescript, - 'react': react, - 'react-hooks': reactHooks, - 'react-refresh': reactRefresh, - 'jsx-a11y': jsxA11y, - }, - rules: { - // TypeScript - '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-non-null-assertion': 'warn', - - // React - 'react/react-in-jsx-scope': 'off', - 'react/prop-types': 'off', - 'react-hooks/rules-of-hooks': 'error', - 'react-hooks/exhaustive-deps': 'warn', - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true } - ], - - // General - 'no-console': 'off', - 'no-debugger': 'error', - 'prefer-const': 'error', - 'no-var': 'error', - 'object-shorthand': 'error', - 'prefer-template': 'error', - 'no-unused-vars': 'off', // Handled by @typescript-eslint/no-unused-vars - 'no-useless-escape': 'error', - 'no-prototype-builtins': 'warn', - - // Typography: Enforce type scale usage - // Warn on arbitrary text sizes in className strings (e.g., text-[10px], text-[9px]) - // Note: SVG chart text (text-[2px], text-[1.5px]) may need exceptions - review case by case - 'no-restricted-syntax': [ - 'warn', - { - selector: - "Literal[value=/text-\\[\\d+(\\.\\d+)?(px|rem)\\]/], TemplateElement[value.raw=/text-\\[\\d+(\\.\\d+)?(px|rem)\\]/]", - message: - 'Use type scale classes (text-xs, text-sm, text-base, text-lg, text-xl, text-2xl, text-3xl, text-4xl) instead of arbitrary sizes. SVG chart text (text-[2px], text-[1.5px]) may be an exception - add eslint-disable comment if needed.', - }, - // Spacing: Enforce spacing scale usage - // Warn on arbitrary spacing values that don't follow 4px base scale - // Valid scale values: 0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24 - // Arbitrary values like gap-[7px], p-[9px], etc. should use scale values instead - { - selector: - "Literal[value=/(gap-|p-|m-|px-|py-|mx-|my-|space-[xy]-)\\[\\d+(\\.\\d+)?(px|rem)\\]/], TemplateElement[value.raw=/(gap-|p-|m-|px-|py-|mx-|my-|space-[xy]-)\\[\\d+(\\.\\d+)?(px|rem)\\]/]", - message: - 'Use spacing scale classes (gap-0 through gap-24, p-0 through p-24, etc.) instead of arbitrary sizes. Valid scale values follow 4px base: 0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24. For exceptions, add eslint-disable comment.', - }, - // Colors: Prevent Tailwind default colors (use Kodo design system colors instead) - // Warn on default Tailwind color classes: slate, gray, zinc, neutral, stone, red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose - // Allow: kodo-* colors, arbitrary values like text-[#fff], and color utilities without shades - { - selector: - "Literal[value=/(text-|bg-|border-|ring-|outline-|divide-|placeholder-|from-|via-|to-|accent-|caret-|fill-|stroke-)(slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(50|100|200|300|400|500|600|700|800|900|950)/], TemplateElement[value.raw=/(text-|bg-|border-|ring-|outline-|divide-|placeholder-|from-|via-|to-|accent-|caret-|fill-|stroke-)(slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(50|100|200|300|400|500|600|700|800|900|950)/]", - message: - 'Use Kodo design system colors (kodo-cyan, kodo-red, kodo-lime, kodo-steel, etc.) instead of Tailwind default colors. See apps/web/src/styles/COLOR_USAGE.md for color mapping. For exceptions (e.g., test files), add eslint-disable comment.', - }, - // Components: Enforce Button component usage (prevent native button elements) - // Warn on native + ); +} diff --git a/apps/web/src/components/ui/Badge.stories.tsx b/apps/web/src/components/ui/Badge.stories.tsx new file mode 100644 index 000000000..0adf8d185 --- /dev/null +++ b/apps/web/src/components/ui/Badge.stories.tsx @@ -0,0 +1,60 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Badge } from './badge'; +import { Star } from 'lucide-react'; + +const meta = { + title: 'UI/Badge', + component: Badge, + tags: ['autodocs'], + argTypes: { + variant: { + control: 'select', + options: ['cyan', 'magenta', 'lime', 'gold', 'terminal'], + }, + size: { + control: 'select', + options: ['sm', 'md', 'lg'], + }, + dot: { control: 'boolean' }, + count: { control: 'number' }, + label: { control: 'text' }, + }, + args: { + label: 'Badge', + variant: 'cyan', + size: 'md', + } +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; + +export const Variants = { + render: () => ( +
+ + + + + +
+ ), +}; + +export const WithIcon: Story = { + args: { + icon: , + label: 'Premium', + variant: 'gold', + }, +}; + +export const WithCount: Story = { + args: { + label: 'Notifications', + count: 5, + variant: 'magenta', + }, +}; diff --git a/apps/web/src/components/ui/Card.stories.tsx b/apps/web/src/components/ui/Card.stories.tsx new file mode 100644 index 000000000..3f6d5f901 --- /dev/null +++ b/apps/web/src/components/ui/Card.stories.tsx @@ -0,0 +1,84 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from './card'; +import { Button } from './button'; + +const meta = { + title: 'UI/Card', + component: Card, + tags: ['autodocs'], + argTypes: { + variant: { + control: 'select', + options: ['default', 'elevated', 'ghost', 'outline', 'muted', 'glass', 'interactive', 'glow', 'glowMagenta', 'spotlight'], + }, + padding: { + control: 'select', + options: ['none', 'sm', 'default', 'lg'], + }, + }, + args: { + variant: 'default', + padding: 'default', + } +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: (args) => ( + + + Create project + Deploy your new project in one-click. + + +
+

Project Name: Veza App

+

Framework: React

+
+
+ + + + +
+ ), +}; + +export const Interactive: Story = { + args: { + variant: 'interactive', + }, + render: (args) => ( + + + Interactive Card + Hover over me to see the effect. + + +

This card responds to user interaction.

+
+
+ ), +}; + +export const Spotlight: Story = { + args: { + variant: 'spotlight', + spotlight: true, + }, + render: (args) => ( +
+ + + Spotlight Effect + Move your mouse over the card. + + +

The spotlight effect follows your cursor.

+
+
+
+ ), +}; diff --git a/apps/web/src/components/ui/Checkbox.stories.tsx b/apps/web/src/components/ui/Checkbox.stories.tsx new file mode 100644 index 000000000..8dd4f09d8 --- /dev/null +++ b/apps/web/src/components/ui/Checkbox.stories.tsx @@ -0,0 +1,44 @@ +import type { Meta, StoryObj } from '@storybook/react'; +// import { fn } from '@storybook/test'; +import { Checkbox } from './checkbox'; + +const meta = { + title: 'UI/Checkbox', + component: Checkbox, + tags: ['autodocs'], + argTypes: { + label: { control: 'text' }, + disabled: { control: 'boolean' }, + checked: { control: 'boolean' }, + }, + args: { + label: 'Accept terms and conditions', + onCheckedChange: () => { }, // fn() + } +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; + +export const Checked: Story = { + args: { + checked: true, + }, +}; + +export const Disabled: Story = { + args: { + disabled: true, + label: 'Disabled option', + }, +}; + +export const DisabledChecked: Story = { + args: { + disabled: true, + checked: true, + label: 'Disabled and checked', + }, +}; diff --git a/apps/web/src/components/ui/Input.stories.tsx b/apps/web/src/components/ui/Input.stories.tsx new file mode 100644 index 000000000..a5b19c7ff --- /dev/null +++ b/apps/web/src/components/ui/Input.stories.tsx @@ -0,0 +1,53 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Input, SearchInput } from './input'; +import { Mail, Lock } from 'lucide-react'; + +const meta = { + title: 'UI/Input', + component: Input, + tags: ['autodocs'], + argTypes: { + type: { control: 'select', options: ['text', 'password', 'email', 'number'] }, + disabled: { control: 'boolean' }, + label: { control: 'text' }, + }, + args: { + placeholder: 'Enter text...', + } +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; + +export const WithLabel: Story = { + args: { + label: 'Email Address', + placeholder: 'name@example.com', + }, +}; + +export const WithIcon: Story = { + args: { + label: 'Email', + icon: , + placeholder: 'Email', + }, +}; + +export const Password: Story = { + args: { + label: 'Password', + type: 'password', + icon: , + placeholder: 'Password', + }, +}; + +export const Search: Story = { + render: (args) => , + args: { + placeholder: 'Search...', + }, +}; diff --git a/apps/web/src/stories/Button.stories.ts b/apps/web/src/stories/Button.stories.ts new file mode 100644 index 000000000..8d69e29da --- /dev/null +++ b/apps/web/src/stories/Button.stories.ts @@ -0,0 +1,54 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +// import { fn } from 'storybook/test'; + +import { Button } from './Button'; + +// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export +const meta = { + title: 'Example/Button', + component: Button, + parameters: { + // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout + layout: 'centered', + }, + // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs + tags: ['autodocs'], + // More on argTypes: https://storybook.js.org/docs/api/argtypes + argTypes: { + backgroundColor: { control: 'color' }, + }, + // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#story-args + // args: { onClick: fn() }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args +export const Primary: Story = { + args: { + primary: true, + label: 'Button', + }, +}; + +export const Secondary: Story = { + args: { + label: 'Button', + }, +}; + +export const Large: Story = { + args: { + size: 'large', + label: 'Button', + }, +}; + +export const Small: Story = { + args: { + size: 'small', + label: 'Button', + }, +}; diff --git a/apps/web/src/stories/Button.tsx b/apps/web/src/stories/Button.tsx new file mode 100644 index 000000000..f35dafdcb --- /dev/null +++ b/apps/web/src/stories/Button.tsx @@ -0,0 +1,37 @@ +import React from 'react'; + +import './button.css'; + +export interface ButtonProps { + /** Is this the principal call to action on the page? */ + primary?: boolean; + /** What background color to use */ + backgroundColor?: string; + /** How large should the button be? */ + size?: 'small' | 'medium' | 'large'; + /** Button contents */ + label: string; + /** Optional click handler */ + onClick?: () => void; +} + +/** Primary UI component for user interaction */ +export const Button = ({ + primary = false, + size = 'medium', + backgroundColor, + label, + ...props +}: ButtonProps) => { + const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary'; + return ( + + ); +}; diff --git a/apps/web/src/stories/Configure.mdx b/apps/web/src/stories/Configure.mdx new file mode 100644 index 000000000..dca091c16 --- /dev/null +++ b/apps/web/src/stories/Configure.mdx @@ -0,0 +1,364 @@ +import { Meta } from "@storybook/addon-docs/blocks"; + +import Github from "./assets/github.svg"; +import Discord from "./assets/discord.svg"; +import Youtube from "./assets/youtube.svg"; +import Tutorials from "./assets/tutorials.svg"; +import Styling from "./assets/styling.png"; +import Context from "./assets/context.png"; +import Assets from "./assets/assets.png"; +import Docs from "./assets/docs.png"; +import Share from "./assets/share.png"; +import FigmaPlugin from "./assets/figma-plugin.png"; +import Testing from "./assets/testing.png"; +import Accessibility from "./assets/accessibility.png"; +import Theming from "./assets/theming.png"; +import AddonLibrary from "./assets/addon-library.png"; + +export const RightArrow = () => + + + + + +
+
+ # Configure your project + + Because Storybook works separately from your app, you'll need to configure it for your specific stack and setup. Below, explore guides for configuring Storybook with popular frameworks and tools. If you get stuck, learn how you can ask for help from our community. +
+
+
+ A wall of logos representing different styling technologies +

Add styling and CSS

+

Like with web applications, there are many ways to include CSS within Storybook. Learn more about setting up styling within Storybook.

+ Learn more +
+
+ An abstraction representing the composition of data for a component +

Provide context and mocking

+

Often when a story doesn't render, it's because your component is expecting a specific environment or context (like a theme provider) to be available.

+ Learn more +
+
+ A representation of typography and image assets +
+

Load assets and resources

+

To link static files (like fonts) to your projects and stories, use the + `staticDirs` configuration option to specify folders to load when + starting Storybook.

+ Learn more +
+
+
+
+
+
+ # Do more with Storybook + + Now that you know the basics, let's explore other parts of Storybook that will improve your experience. This list is just to get you started. You can customise Storybook in many ways to fit your needs. +
+ +
+
+
+ A screenshot showing the autodocs tag being set, pointing a docs page being generated +

Autodocs

+

Auto-generate living, + interactive reference documentation from your components and stories.

+ Learn more +
+
+ A browser window showing a Storybook being published to a chromatic.com URL +

Publish to Chromatic

+

Publish your Storybook to review and collaborate with your entire team.

+ Learn more +
+
+ Windows showing the Storybook plugin in Figma +

Figma Plugin

+

Embed your stories into Figma to cross-reference the design and live + implementation in one place.

+ Learn more +
+
+ Screenshot of tests passing and failing +

Testing

+

Use stories to test a component in all its variations, no matter how + complex.

+ Learn more +
+
+ Screenshot of accessibility tests passing and failing +

Accessibility

+

Automatically test your components for a11y issues as you develop.

+ Learn more +
+
+ Screenshot of Storybook in light and dark mode +

Theming

+

Theme Storybook's UI to personalize it to your project.

+ Learn more +
+
+
+
+
+
+

Addons

+

Integrate your tools with Storybook to connect workflows.

+ Discover all addons +
+
+ Integrate your tools with Storybook to connect workflows. +
+
+ +
+
+ Github logo + Join our contributors building the future of UI development. + + Star on GitHub +
+
+ Discord logo +
+ Get support and chat with frontend developers. + + Join Discord server +
+
+
+ Youtube logo +
+ Watch tutorials, feature previews and interviews. + + Watch on YouTube +
+
+
+ A book +

Follow guided walkthroughs on for key workflows.

+ + Discover tutorials +
+
+ + diff --git a/apps/web/src/stories/Header.stories.ts b/apps/web/src/stories/Header.stories.ts new file mode 100644 index 000000000..6fefbb86d --- /dev/null +++ b/apps/web/src/stories/Header.stories.ts @@ -0,0 +1,34 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +// import { fn } from 'storybook/test'; + +import { Header } from './Header'; + +const meta = { + title: 'Example/Header', + component: Header, + // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs + tags: ['autodocs'], + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout + layout: 'fullscreen', + }, + args: { + onLogin: () => { }, + onLogout: () => { }, + onCreateAccount: () => { }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const LoggedIn: Story = { + args: { + user: { + name: 'Jane Doe', + }, + }, +}; + +export const LoggedOut: Story = {}; diff --git a/apps/web/src/stories/Header.tsx b/apps/web/src/stories/Header.tsx new file mode 100644 index 000000000..1bf981a42 --- /dev/null +++ b/apps/web/src/stories/Header.tsx @@ -0,0 +1,56 @@ +import React from 'react'; + +import { Button } from './Button'; +import './header.css'; + +type User = { + name: string; +}; + +export interface HeaderProps { + user?: User; + onLogin?: () => void; + onLogout?: () => void; + onCreateAccount?: () => void; +} + +export const Header = ({ user, onLogin, onLogout, onCreateAccount }: HeaderProps) => ( +
+
+
+ + + + + + + +

Acme

+
+
+ {user ? ( + <> + + Welcome, {user.name}! + +
+
+
+); diff --git a/apps/web/src/stories/Page.stories.ts b/apps/web/src/stories/Page.stories.ts new file mode 100644 index 000000000..256895f0a --- /dev/null +++ b/apps/web/src/stories/Page.stories.ts @@ -0,0 +1,33 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +// import { expect, userEvent, within } from 'storybook/test'; + +import { Page } from './Page'; + +const meta = { + title: 'Example/Page', + component: Page, + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout + layout: 'fullscreen', + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const LoggedOut: Story = {}; + +// More on component testing: https://storybook.js.org/docs/writing-tests/interaction-testing +export const LoggedIn: Story = { + // play: async ({ canvasElement }) => { + // const canvas = within(canvasElement); + // const loginButton = canvas.getByRole('button', { name: /Log in/i }); + // await expect(loginButton).toBeInTheDocument(); + // await userEvent.click(loginButton); + // await expect(loginButton).not.toBeInTheDocument(); + // + // const logoutButton = canvas.getByRole('button', { name: /Log out/i }); + // await expect(logoutButton).toBeInTheDocument(); + // }, +}; diff --git a/apps/web/src/stories/Page.tsx b/apps/web/src/stories/Page.tsx new file mode 100644 index 000000000..e11748301 --- /dev/null +++ b/apps/web/src/stories/Page.tsx @@ -0,0 +1,73 @@ +import React from 'react'; + +import { Header } from './Header'; +import './page.css'; + +type User = { + name: string; +}; + +export const Page: React.FC = () => { + const [user, setUser] = React.useState(); + + return ( +
+
setUser({ name: 'Jane Doe' })} + onLogout={() => setUser(undefined)} + onCreateAccount={() => setUser({ name: 'Jane Doe' })} + /> + +
+

Pages in Storybook

+

+ We recommend building UIs with a{' '} + + component-driven + {' '} + process starting with atomic components and ending with pages. +

+

+ Render pages with mock data. This makes it easy to build and review page states without + needing to navigate to them in your app. Here are some handy patterns for managing page + data in Storybook: +

+
    +
  • + Use a higher-level connected component. Storybook helps you compose such data from the + "args" of child component stories +
  • +
  • + Assemble data in the page component from your services. You can mock these services out + using Storybook. +
  • +
+

+ Get a guided tutorial on component-driven development at{' '} + + Storybook tutorials + + . Read more in the{' '} + + docs + + . +

+
+ Tip Adjust the width of the canvas with the{' '} + + + + + + Viewports addon in the toolbar +
+
+
+ ); +}; diff --git a/apps/web/src/stories/assets/accessibility.png b/apps/web/src/stories/assets/accessibility.png new file mode 100644 index 000000000..6ffe6feab Binary files /dev/null and b/apps/web/src/stories/assets/accessibility.png differ diff --git a/apps/web/src/stories/assets/accessibility.svg b/apps/web/src/stories/assets/accessibility.svg new file mode 100644 index 000000000..107e93f83 --- /dev/null +++ b/apps/web/src/stories/assets/accessibility.svg @@ -0,0 +1 @@ +Accessibility \ No newline at end of file diff --git a/apps/web/src/stories/assets/addon-library.png b/apps/web/src/stories/assets/addon-library.png new file mode 100644 index 000000000..95deb38a8 Binary files /dev/null and b/apps/web/src/stories/assets/addon-library.png differ diff --git a/apps/web/src/stories/assets/assets.png b/apps/web/src/stories/assets/assets.png new file mode 100644 index 000000000..cfba6817a Binary files /dev/null and b/apps/web/src/stories/assets/assets.png differ diff --git a/apps/web/src/stories/assets/avif-test-image.avif b/apps/web/src/stories/assets/avif-test-image.avif new file mode 100644 index 000000000..530709bc1 Binary files /dev/null and b/apps/web/src/stories/assets/avif-test-image.avif differ diff --git a/apps/web/src/stories/assets/context.png b/apps/web/src/stories/assets/context.png new file mode 100644 index 000000000..e5cd249a2 Binary files /dev/null and b/apps/web/src/stories/assets/context.png differ diff --git a/apps/web/src/stories/assets/discord.svg b/apps/web/src/stories/assets/discord.svg new file mode 100644 index 000000000..d638958b6 --- /dev/null +++ b/apps/web/src/stories/assets/discord.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/stories/assets/docs.png b/apps/web/src/stories/assets/docs.png new file mode 100644 index 000000000..a749629df Binary files /dev/null and b/apps/web/src/stories/assets/docs.png differ diff --git a/apps/web/src/stories/assets/figma-plugin.png b/apps/web/src/stories/assets/figma-plugin.png new file mode 100644 index 000000000..8f79b08cd Binary files /dev/null and b/apps/web/src/stories/assets/figma-plugin.png differ diff --git a/apps/web/src/stories/assets/github.svg b/apps/web/src/stories/assets/github.svg new file mode 100644 index 000000000..dc513528c --- /dev/null +++ b/apps/web/src/stories/assets/github.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/stories/assets/share.png b/apps/web/src/stories/assets/share.png new file mode 100644 index 000000000..8097a3707 Binary files /dev/null and b/apps/web/src/stories/assets/share.png differ diff --git a/apps/web/src/stories/assets/styling.png b/apps/web/src/stories/assets/styling.png new file mode 100644 index 000000000..d341e8263 Binary files /dev/null and b/apps/web/src/stories/assets/styling.png differ diff --git a/apps/web/src/stories/assets/testing.png b/apps/web/src/stories/assets/testing.png new file mode 100644 index 000000000..d4ac39a0c Binary files /dev/null and b/apps/web/src/stories/assets/testing.png differ diff --git a/apps/web/src/stories/assets/theming.png b/apps/web/src/stories/assets/theming.png new file mode 100644 index 000000000..1535eb9b8 Binary files /dev/null and b/apps/web/src/stories/assets/theming.png differ diff --git a/apps/web/src/stories/assets/tutorials.svg b/apps/web/src/stories/assets/tutorials.svg new file mode 100644 index 000000000..b492a9c66 --- /dev/null +++ b/apps/web/src/stories/assets/tutorials.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/stories/assets/youtube.svg b/apps/web/src/stories/assets/youtube.svg new file mode 100644 index 000000000..a7515d7e9 --- /dev/null +++ b/apps/web/src/stories/assets/youtube.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/stories/button.css b/apps/web/src/stories/button.css new file mode 100644 index 000000000..4e3620b0d --- /dev/null +++ b/apps/web/src/stories/button.css @@ -0,0 +1,30 @@ +.storybook-button { + display: inline-block; + cursor: pointer; + border: 0; + border-radius: 3em; + font-weight: 700; + line-height: 1; + font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; +} +.storybook-button--primary { + background-color: #555ab9; + color: white; +} +.storybook-button--secondary { + box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset; + background-color: transparent; + color: #333; +} +.storybook-button--small { + padding: 10px 16px; + font-size: 12px; +} +.storybook-button--medium { + padding: 11px 20px; + font-size: 14px; +} +.storybook-button--large { + padding: 12px 24px; + font-size: 16px; +} diff --git a/apps/web/src/stories/header.css b/apps/web/src/stories/header.css new file mode 100644 index 000000000..5efd46c26 --- /dev/null +++ b/apps/web/src/stories/header.css @@ -0,0 +1,32 @@ +.storybook-header { + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + padding: 15px 20px; + font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +.storybook-header svg { + display: inline-block; + vertical-align: top; +} + +.storybook-header h1 { + display: inline-block; + vertical-align: top; + margin: 6px 0 6px 10px; + font-weight: 700; + font-size: 20px; + line-height: 1; +} + +.storybook-header button + button { + margin-left: 10px; +} + +.storybook-header .welcome { + margin-right: 10px; + color: #333; + font-size: 14px; +} diff --git a/apps/web/src/stories/page.css b/apps/web/src/stories/page.css new file mode 100644 index 000000000..77c81d2d5 --- /dev/null +++ b/apps/web/src/stories/page.css @@ -0,0 +1,68 @@ +.storybook-page { + margin: 0 auto; + padding: 48px 20px; + max-width: 600px; + color: #333; + font-size: 14px; + line-height: 24px; + font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +.storybook-page h2 { + display: inline-block; + vertical-align: top; + margin: 0 0 4px; + font-weight: 700; + font-size: 32px; + line-height: 1; +} + +.storybook-page p { + margin: 1em 0; +} + +.storybook-page a { + color: inherit; +} + +.storybook-page ul { + margin: 1em 0; + padding-left: 30px; +} + +.storybook-page li { + margin-bottom: 8px; +} + +.storybook-page .tip { + display: inline-block; + vertical-align: top; + margin-right: 10px; + border-radius: 1em; + background: #e7fdd8; + padding: 4px 12px; + color: #357a14; + font-weight: 700; + font-size: 11px; + line-height: 12px; +} + +.storybook-page .tip-wrapper { + margin-top: 40px; + margin-bottom: 40px; + font-size: 13px; + line-height: 20px; +} + +.storybook-page .tip-wrapper svg { + display: inline-block; + vertical-align: top; + margin-top: 3px; + margin-right: 4px; + width: 12px; + height: 12px; +} + +.storybook-page .tip-wrapper svg path { + fill: #1ea7fd; +} diff --git a/apps/web/vitest.config.ts b/apps/web/vitest.config.ts index 17f9e968c..e2413b81b 100644 --- a/apps/web/vitest.config.ts +++ b/apps/web/vitest.config.ts @@ -1,7 +1,14 @@ import { defineConfig } from 'vitest/config'; import react from '@vitejs/plugin-react'; import path from 'path'; +import { fileURLToPath } from 'node:url'; +import { storybookTest } from '@storybook/addon-vitest/vitest-plugin'; +const dirname = + typeof __dirname !== 'undefined' + ? __dirname + : path.dirname(fileURLToPath(import.meta.url)); +// More info at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon export default defineConfig({ plugins: [react()], test: { @@ -30,6 +37,128 @@ export default defineConfig({ }, }, }, + projects: [ + { + extends: true, + plugins: [ + // The plugin will run tests for the stories defined in your Storybook config + // See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest + storybookTest({ + configDir: path.join(dirname, '.storybook'), + }), + ], + test: { + name: 'storybook', + browser: { + enabled: true, + headless: true, + provider: 'playwright', + instances: [ + { + browser: 'chromium', + }, + ], + }, + setupFiles: ['.storybook/vitest.setup.ts'], + }, + }, + { + extends: true, + plugins: [ + // The plugin will run tests for the stories defined in your Storybook config + // See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest + storybookTest({ + configDir: path.join(dirname, '.storybook'), + }), + ], + test: { + name: 'storybook', + browser: { + enabled: true, + headless: true, + provider: 'playwright', + instances: [ + { + browser: 'chromium', + }, + ], + }, + setupFiles: ['.storybook/vitest.setup.ts'], + }, + }, + { + extends: true, + plugins: [ + // The plugin will run tests for the stories defined in your Storybook config + // See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest + storybookTest({ + configDir: path.join(dirname, '.storybook'), + }), + ], + test: { + name: 'storybook', + browser: { + enabled: true, + headless: true, + provider: 'playwright', + instances: [ + { + browser: 'chromium', + }, + ], + }, + setupFiles: ['.storybook/vitest.setup.ts'], + }, + }, + { + extends: true, + plugins: [ + // The plugin will run tests for the stories defined in your Storybook config + // See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest + storybookTest({ + configDir: path.join(dirname, '.storybook'), + }), + ], + test: { + name: 'storybook', + browser: { + enabled: true, + headless: true, + provider: 'playwright', + instances: [ + { + browser: 'chromium', + }, + ], + }, + setupFiles: ['.storybook/vitest.setup.ts'], + }, + }, + { + extends: true, + plugins: [ + // The plugin will run tests for the stories defined in your Storybook config + // See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest + storybookTest({ + configDir: path.join(dirname, '.storybook'), + }), + ], + test: { + name: 'storybook', + browser: { + enabled: true, + headless: true, + provider: 'playwright', + instances: [ + { + browser: 'chromium', + }, + ], + }, + setupFiles: ['.storybook/vitest.setup.ts'], + }, + }, + ], }, resolve: { alias: { diff --git a/apps/web/vitest.shims.d.ts b/apps/web/vitest.shims.d.ts new file mode 100644 index 000000000..f923d47d4 --- /dev/null +++ b/apps/web/vitest.shims.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/docs/BOOT_MODE_STATUS.md b/docs/BOOT_MODE_STATUS.md new file mode 100644 index 000000000..79eefb6a7 --- /dev/null +++ b/docs/BOOT_MODE_STATUS.md @@ -0,0 +1,37 @@ +# Veza Web - Mode Boot (Dégradé) + +> **État** : 🟠 FONCTIONNEL PARTIEL +> **Mode** : `VEZA_MODE=boot` + +Ce document liste les fonctionnalités explicitement désactivées ou simulées pour permettre le démarrage de l'application Web sans l'infrastructure complète. + +## ⛔ Services Désactivés +Les services suivants ne sont **PAS** lancés. Toute tentative d'interaction échouera ou sera ignorée. + +| Composant | Status | Conséquence Utilisateur | +|-----------|--------|-------------------------| +| **RabbitMQ** | ❌ OFF | Pas de notifications temps réel, pas de tâches asynchrones. | +| **ClamAV** | ❌ OFF | Les uploads de fichiers ne sont PAS scannés pour les virus. | +| **S3 Storage** | ❌ OFF | Les fichiers sont stockés localement dans `uploads/` (pas de cloud). | +| **Chat Server** | ❌ OFF | La messagerie instantanée ne fonctionne pas (chargement infini ou erreur). | +| **Stream Server** | ❌ OFF | Le streaming audio/vidéo est indisponible. | +| **Workers** | ❌ OFF | Les emails (inscription, reset password) ne sont pas envoyés (logs uniquement). | + +## ⚠️ Fonctionnalités Simulées / Mockées +Certaines parties du code ont été modifiées pour "faire semblant" de marcher afin d'éviter les crashs. + +- **Uploads** : Acceptés aveuglément sans validation antivirus. +- **Emails** : Le service d'envoi d'email loggue le contenu dans la console au lieu d'utiliser SMTP. + +## ✅ Parcours Utilisateur Actif +Le "Happy Path" minimal suivant est garanti fonctionnel : + +1. **Inscription / Connexion** (JWT) +2. **Affichage Dashboard** +3. **Création de Playlist** (Persistance en base Postgres) +4. **Modification de Profil** (Basique) + +## 🛠️ Comment lancer ce mode +```bash +./scripts/start_boot.sh +``` diff --git a/docs/BUDGETS.md b/docs/BUDGETS.md new file mode 100644 index 000000000..cfabfdecb --- /dev/null +++ b/docs/BUDGETS.md @@ -0,0 +1,38 @@ +# VEZA RESOURCE BUDGETS (THE CONTRACT) + +Ce fichier définit les limites **physiques** acceptables pour le projet. +Les tests automatisés (`tmt/tests/...`) utilisent ces valeurs comme conditions d'échec. + +> **Règle d'or** : Tout changement dans ce fichier exige un **commit dédié** et une **justification écrite** expliquant pourquoi l'augmentation est inévitable et bénéfique pour l'utilisateur final (pas pour le développeur). + +## 1. Frontend (Web) + +| Métrique | Limite (Max) | Justification | Script de Contrôle | +| :--- | :--- | :--- | :--- | +| **Bundle Size** (Total) | **5000 KB** (5MB) | Téléchargement < 10s en 3G (4Mbps). | `tests/frontend/bundle_size.sh` | +| **Main Entry JS** | **800 KB** | Parsing/Execution rapide sur CPU mobile bas de gamme. | `tests/frontend/bundle_size.sh` | +| **Temps de Build** | **120 s** | Feedback loop rapide. Empêche la complexité du toolchain. | `tests/frontend/build_perf.sh` | + +## 2. Backend (API Go) + +| Métrique | Limite (Max) | Justification | Script de Contrôle | +| :--- | :--- | :--- | :--- | +| **Temps de Démarrage** | **2 s** | Cold start rapide, redémarrage auto transparent. | `tests/backend/startup_time.sh` | +| **Mémoire RSS (Idle)** | **100 MB** | Doit cohabiter avec DB + Services sur 1Go RAM total. | `tests/backend/memory_budget.sh` | +| **Binaires .test** | **0** | Aucun binaire de test ne doit polluer le repo. | `.gitignore` | + +## 3. Services (Rust) + +| Métrique | Limite (Max) | Justification | Script de Contrôle | +| :--- | :--- | :--- | :--- | +| **Warnings** | **0** | Zéro tolérance au bruit. | `tests/services/rust_clippy.sh` | +| **Compilation** | *Suivi* | Pas de limite dure encore, mais surveillé. | - | + +## 4. Protocole d'Augmentation + +Si vous devez augmenter un budget : + +1. Ouvrez une **Issue** titrée : `[BUDGET] Request to increase `. +2. Prouvez que vous avez d'abord tenté d'optimiser. +3. Expliquez l'impact sur un utilisateur avec un Celeron N4000 et 4GB RAM. +4. Obtenez l'approbation d'un mainteneur "Garde des Sceaux". diff --git a/docs/FRUGALITY.md b/docs/FRUGALITY.md new file mode 100644 index 000000000..79bb8b0ac --- /dev/null +++ b/docs/FRUGALITY.md @@ -0,0 +1,48 @@ +# VEZA FRUGALITY MANIFESTO + +> **"Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away."** — Antoine de Saint-Exupéry + +This document is the **Supreme Law** of the Veza codebase. +It supersedes all other documentation, feature requests, or developer preferences. + +## 1. The Prohibitions (Non-Negotiable) + +These are not guidelines. These are **interdictions**. + +* 🔴 **Un test lent n’est pas un test vital.** + * Si un test prend plus de quelques secondes, il doit être déplacé dans `legacy` ou supprimé. + * Le pipeline Vital doit être instantané. + +* 🔴 **Un warning Rust ou Go est un défaut de conception.** + * Il n'y a pas de "petit warning". Le code bruyant cache les vrais problèmes. + * Tout warning bloque le merge. + +* 🔴 **Une dépendance frontend non justifiée est un bug.** + * Ajouter une librairie de 50KB pour éviter d'écrire 10 lignes de CSS est une faute professionnelle. + * Tout ajout à `package.json` doit être défendu par écrit. + +* 🔴 **Un dépassement de budget n’est pas “temporaire”.** + * On n'augmente pas la RAM "juste pour le MVP". + * Si ça ne rentre pas, on simplifie la feature, on ne demande pas plus de ressources. + +## 2. Invariants Matériels + +Veza est conçu pour le **tiers-monde numérique**, pas pour la Silicon Valley. + +* **CPU**: 1 Core. Pas de multithreading gratuit. +* **RAM**: 1 Go pour l'ensemble du stack (Backend + DB + Services). +* **GPU**: 0. Le logiciel doit fonctionner en rendu software pur (`LIBGL_ALWAYS_SOFTWARE=1`). +* **Réseau**: Doit survivre à 3G H+ et aux coupures intermittentes. + +## 3. La Règle du Frontend + +**Aucun nouveau test frontend n'est VITAL par défaut.** + +* Le frontend est volatile. Le tester excessivement crée de la rigidité, pas de la robustesse. +* Un test frontend n'entre dans `plans/vital.fmf` que s'il prouve qu'il protège un invariant critique (ex: "L'app démarre", "Le chat s'affiche"). +* Tout le reste va dans `plans/legacy.fmf` ou n'existe pas. + +## 4. Modification de la Loi + +Ce document peut être amendé, mais seulement après un **débat explicite**. +On ne change pas la constitution en douce dans une PR de feature. diff --git a/docs/GLOBAL_PROJECT_STATE_2026.md b/docs/GLOBAL_PROJECT_STATE_2026.md new file mode 100644 index 000000000..d4a814bad --- /dev/null +++ b/docs/GLOBAL_PROJECT_STATE_2026.md @@ -0,0 +1,72 @@ +# 🏥 Audit Global de l'État du Projet Veza (Février 2026) + +> **Verdict Rapide** : Le patient est en vie (il respire), mais il est sous assistance respiratoire ("Boot Mode"). L'architecture est solide mais encombrée par une dette technique visible (TODOs) et une documentation pléthorique qui frôle la saturation cognitive. + +--- + +## 1. État de Fonctionnement (Runtime) + +### ✅ Ce qui marche (Le Coeur) +* **Infrastructure Vitale** : Postgres (5432) et Redis (6379) sont stables. +* **Backend API** : Le serveur Go tourne sur le port `8080`. + * Route `/health` : OK (200). + * Connexion DB : OK. + * Migrations : Appliquées (001 à 931). +* **Frontend** : Vite sert l'application sur le port `5173`. + * Build : Rapide (Hot Module Replacement actif). + * Stack : React 18, TypeScript, Tailwind 4. + +### ⛔ Ce qui est "Mort" (Volontairement) +Le mode `VEZA_MODE=boot` a amputé le système de ses membres complexes : +* **RabbitMQ** : Éteint. Conséquence : Pas d'événements asynchrones. +* **ClamAV** : Éteint. Conséquence : Sécurité des uploads = 0. +* **Chat & Stream Servers** : Éteints. Conséquence : Les fonctionnalités "Core" (musique, chat) sont des coquilles vides. +* **S3** : Éteint. Conséquence : Stockage local uniquement. + +--- + +## 2. Analyse du Codebase + +### 🧠 Backend (`veza-backend-api`) +* **Architecture** : Structure Go standard (`cmd/`, `internal/`) mais dense. +* **Dette Technique** : 🚨 **210+ TODOs/FIXMEs** détectés. + * Beacoup de "Refactor après stabilisation". + * Des pans entiers de logique sont annotés comme "Legacy" ou "À migrer". +* **Points Forts** : + * Gestion de config robuste (`godotenv`, validation stricte). + * Middleware structuré (Rate Limiting, CORS, Auth). + * Instrumentation présente (Prometheus, Sentry). + +### 🎨 Frontend (`apps/web`) +* **Modernité** : Stack très à jour (Vite 7, Tailwind 4, Zuid/Zustand). +* **Complexité** : Le `package.json` révèle une "usine à gaz" de tests : + * 4 frameworks de tests différents : `vitest`, `playwright`, `backstopjs`, `pa11y`. + * Cela indique une volonté de qualité extrême, mais potentiellement dure à maintenir. +* **Migration** : Traces de migration d'interface (`KODO_MIGRATION`). Le Design System semble être en transition. + +--- + +## 3. Documentation & Process +* **Surcharge Informationnelle** : Le dossier `docs/` contient **54 fichiers**. + * Il y a des audits de tout (migration, UI, backend, tests, transactions...). + * ⚠️ **Risque** : Difficile de distinguer la "Vérité Terrain" de "l'Archive Historique". +* **Tests (TMT)** : Une infrastructure de test sophistiquée (`tmt`) est en place, couvrant unitaires, intégration et E2E. C'est un actif majeur pour la stabilité future. + +--- + +## 4. Recommandations "Chirurgicales" + +### Immédiat (Next 24h) +1. **Nettoyer la Vue** : Archiver les vieux audits dans `docs/archive/` pour ne garder que les specs actives. +2. **Tester le "Happy Path"** : Puisque l'app tourne, vérifier manuellement l'inscription et la création de playlist pour valider le "Boot Mode". + +### Moyen Terme (Next Sprint) +1. **Réanimer un Membre** : Réactiver **RabbitMQ** en priorité pour rétablir l'asynchronisme. +2. **Triage des TODOs** : 200 TODOs, c'est trop. Il faut en supprimer 50% (obsolètes) et transformer les 50% restants en tickets Jira/Github. +3. **Stubber les Services Manquants** : Au lieu de désactiver le Chat, créer un "Mock Server" simple qui répond 200 OK pour que l'UI ne tourne pas dans le vide. + +--- + +## Conclusion +Veza n'est pas un "MVP bricolé". C'est un système industriel complexe qui a été mis en pause. Le "Boot Mode" a permis de redémarrer le cœur. +La prochaine étape n'est pas de "coder des features", mais de **reconnecter les organes vitaux un par un**. diff --git a/docs/MINIMAL_WEB.md b/docs/MINIMAL_WEB.md new file mode 100644 index 000000000..c9f2ed9aa --- /dev/null +++ b/docs/MINIMAL_WEB.md @@ -0,0 +1,42 @@ +# Veza Minimal Web Stack + +> **Goal**: Run a functional end-to-end version of Veza with the absolute minimum resources. + +## 🚀 How to Run + +```bash +# 1. Start everything (DB, Redis, Backend, Frontend) +make web-minimal + +# 2. View in Browser +# http://localhost:5173 +``` + +## 🛠️ Components +- **Frontend**: Vite Dev Server (Port 5173). Bundled minimal. +- **Backend**: Go API (Port 8080). +- **Database**: Postgres + Redis (Docker). +- **Ignored**: Chat Server, Stream Server, Workers, S3, MinIO. + +## ✅ Verified User Journey +We have verified that the following core loop works: +1. **Registration** (New User) +2. **Login** (JWT Token access) +3. **Create Playlist** (Write to DB) +4. **List Playlists** (Read from DB) + +You can verify this yourself by running: +```bash +./scripts/verify_minimal_journey.sh +``` + +## ⚠️ Known Limitations +- **Profile Endpoint**: The `/api/v1/profile` or `/auth/me` endpoint returns 404 in some contexts. The frontend likely relies on the User object returned during Login. +- **Chat**: Disabled. Chat page will likely fail or show loading. +- **Uploads**: Disabled/Mocked. S3 is not running. +- **Critical JS**: We are still missing `