diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 7ecff5e0c..0dd783c4e 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -1,4 +1,4 @@ -name: E2E Test Hive +name: Hive E2E Test on: push: diff --git a/.github/workflows/hive.yml b/.github/workflows/hive.yml index 078424326..f84fa99fc 100644 --- a/.github/workflows/hive.yml +++ b/.github/workflows/hive.yml @@ -1,8 +1,8 @@ -name: Build and Test Hive +name: Hive Build and Test on: push: - branches: [ main ] + branches: [main] pull_request: workflow_dispatch: diff --git a/.github/workflows/ui.yml b/.github/workflows/ui.yml new file mode 100644 index 000000000..5e5aa3b26 --- /dev/null +++ b/.github/workflows/ui.yml @@ -0,0 +1,73 @@ +name: UI Build and Test + +on: + push: + branches: [main, ui] + paths: ["hive-ui/**", ".github/workflows/ui.yml"] + pull_request: + workflow_dispatch: + +jobs: + frontend: + name: Frontend Build and Test + runs-on: ubuntu-24.04 + defaults: + run: + working-directory: hive-ui/frontend + + steps: + - name: Checkout Code + uses: actions/checkout@v6 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + package_json_file: hive-ui/frontend/package.json + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + cache: "pnpm" + cache-dependency-path: hive-ui/frontend/pnpm-lock.yaml + node-version-file: hive-ui/frontend/.nvmrc + + - name: Install frontend dependencies + run: pnpm install --frozen-lockfile + + - name: Run Biome formatter check + run: pnpm check + + - name: Build frontend + run: pnpm build + + webserver: + name: Webserver Build and Test + runs-on: ubuntu-24.04 + defaults: + run: + working-directory: hive-ui/webserver + + steps: + - name: Checkout Code + uses: actions/checkout@v6 + + - name: Setup Go + uses: actions/setup-go@v6 + with: + cache-dependency-path: hive-ui/webserver/go.sum + go-version-file: hive-ui/webserver/go.mod + + - name: Go Vet + run: go vet ./... + + - name: Static Check + run: go tool staticcheck ./... + + - name: Go Vulnerability Check + run: go tool govulncheck ./... + + - name: Gosec Security Scanner + run: go tool gosec -exclude-generated ./... + + - name: Build + run: go build ./... diff --git a/.gitignore b/.gitignore index a957e3a41..af131c4b7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,25 @@ -.vscode +# Go workspace go.work go.work.sum -bin/hive + +# Build outputs +bin/ tmp/ +*.test +*.out + +# Config config/dev.yaml + +# OS files .DS_Store -tests/*.txt +Thumbs.db + +# Environment +.env + +# Test reports +*-report.txt + +# hive-ui +hive-ui/certs/ diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000..5ad58762d --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "biomejs.biome", + "bradlc.vscode-tailwindcss", + "golang.go" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..80dab2d35 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,36 @@ +{ + "files.readonlyInclude": { + "**/routeTree.gen.ts": true + }, + "files.watcherExclude": { + "**/routeTree.gen.ts": true + }, + "search.exclude": { + "**/routeTree.gen.ts": true + }, + "[css][json][jsonc]": { + "editor.defaultFormatter": "biomejs.biome", + "editor.tabSize": 2 + }, + "[javascript][javascriptreact][typescript][typescriptreact]": { + "editor.codeActionsOnSave": { + "source.fixAll.biome": "explicit", + "source.organizeImports.biome": "explicit" + }, + "editor.defaultFormatter": "biomejs.biome", + "editor.tabSize": 2 + }, + "[go]": { + "editor.defaultFormatter": "golang.go", + "editor.tabSize": 4 + }, + "biome.configurationPath": "./hive-ui/frontend/biome.jsonc", + "biome.requireConfiguration": true, + "biome.lsp.bin": "./hive-ui/frontend/node_modules/.bin/biome", + "editor.formatOnSave": true, + "editor.formatOnPaste": true, + "files.insertFinalNewline": true, + "files.trimFinalNewlines": true, + "files.trimTrailingWhitespace": true, + "typescript.tsdk": "./hive-ui/frontend/node_modules/typescript/lib" +} diff --git a/hive-ui/.claude/rules/frontend-standards.md b/hive-ui/.claude/rules/frontend-standards.md new file mode 100644 index 000000000..e368ebb9a --- /dev/null +++ b/hive-ui/.claude/rules/frontend-standards.md @@ -0,0 +1,90 @@ +# Quick Reference + +Most formatting and common issues are automatically fixed by Biome. Run `pnpm fix` before committing to ensure compliance. + +## Core Principles + +Write code that is **accessible, performant, type-safe, and maintainable**. Focus on clarity and explicit intent over brevity. + +### Type Safety & Explicitness + +- Use explicit types for function parameters and return values when they enhance clarity +- Prefer `unknown` over `any` when the type is genuinely unknown +- Use const assertions (`as const`) for immutable values and literal types +- Leverage TypeScript's type narrowing instead of type assertions +- Use meaningful variable names instead of magic numbers - extract constants with descriptive names + +### Modern JavaScript/TypeScript + +- Use arrow functions for callbacks and short functions +- Prefer `for...of` loops over `.forEach()` and indexed `for` loops +- Use optional chaining (`?.`) and nullish coalescing (`??`) for safer property access +- Prefer template literals over string concatenation +- Use destructuring for object and array assignments +- Use `const` by default, `let` only when reassignment is needed, never `var` +- Use interfaces for objects, otherwise use type aliases +- Prefer using function declarations over arrow functions for named functions + +### Async & Promises + +- Always `await` promises in async functions - don't forget to use the return value +- Use `async/await` syntax instead of promise chains for better readability +- Handle errors appropriately in async code with try-catch blocks +- Don't use async functions as Promise executors + +### React & JSX + +- Use function components over class components +- Call hooks at the top level only, never conditionally +- Specify all dependencies in hook dependency arrays correctly +- Use the `key` prop for elements in iterables (prefer unique IDs over array indices) +- Nest children between opening and closing tags instead of passing as props +- Don't define components inside other components +- Use semantic HTML and ARIA attributes for accessibility: + - Provide meaningful alt text for images + - Use proper heading hierarchy + - Add labels for form inputs + - Include keyboard event handlers alongside mouse events + - Use semantic elements (` +} +``` + +**Correct (reads on demand, no subscription):** + +```tsx +function ShareButton({ chatId }: { chatId: string }) { + const handleShare = () => { + const params = new URLSearchParams(window.location.search) + const ref = params.get('ref') + shareChat(chatId, { ref }) + } + + return +} +``` diff --git a/hive-ui/.claude/skills/react-best-practices/rules/rerender-dependencies.md b/hive-ui/.claude/skills/react-best-practices/rules/rerender-dependencies.md new file mode 100644 index 000000000..47a4d9268 --- /dev/null +++ b/hive-ui/.claude/skills/react-best-practices/rules/rerender-dependencies.md @@ -0,0 +1,45 @@ +--- +title: Narrow Effect Dependencies +impact: LOW +impactDescription: minimizes effect re-runs +tags: rerender, useEffect, dependencies, optimization +--- + +## Narrow Effect Dependencies + +Specify primitive dependencies instead of objects to minimize effect re-runs. + +**Incorrect (re-runs on any user field change):** + +```tsx +useEffect(() => { + console.log(user.id) +}, [user]) +``` + +**Correct (re-runs only when id changes):** + +```tsx +useEffect(() => { + console.log(user.id) +}, [user.id]) +``` + +**For derived state, compute outside effect:** + +```tsx +// Incorrect: runs on width=767, 766, 765... +useEffect(() => { + if (width < 768) { + enableMobileMode() + } +}, [width]) + +// Correct: runs only on boolean transition +const isMobile = width < 768 +useEffect(() => { + if (isMobile) { + enableMobileMode() + } +}, [isMobile]) +``` diff --git a/hive-ui/.claude/skills/react-best-practices/rules/rerender-derived-state-no-effect.md b/hive-ui/.claude/skills/react-best-practices/rules/rerender-derived-state-no-effect.md new file mode 100644 index 000000000..3d9fe4050 --- /dev/null +++ b/hive-ui/.claude/skills/react-best-practices/rules/rerender-derived-state-no-effect.md @@ -0,0 +1,40 @@ +--- +title: Calculate Derived State During Rendering +impact: MEDIUM +impactDescription: avoids redundant renders and state drift +tags: rerender, derived-state, useEffect, state +--- + +## Calculate Derived State During Rendering + +If a value can be computed from current props/state, do not store it in state or update it in an effect. Derive it during render to avoid extra renders and state drift. Do not set state in effects solely in response to prop changes; prefer derived values or keyed resets instead. + +**Incorrect (redundant state and effect):** + +```tsx +function Form() { + const [firstName, setFirstName] = useState('First') + const [lastName, setLastName] = useState('Last') + const [fullName, setFullName] = useState('') + + useEffect(() => { + setFullName(firstName + ' ' + lastName) + }, [firstName, lastName]) + + return

{fullName}

+} +``` + +**Correct (derive during render):** + +```tsx +function Form() { + const [firstName, setFirstName] = useState('First') + const [lastName, setLastName] = useState('Last') + const fullName = firstName + ' ' + lastName + + return

{fullName}

+} +``` + +References: [You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect) diff --git a/hive-ui/.claude/skills/react-best-practices/rules/rerender-derived-state.md b/hive-ui/.claude/skills/react-best-practices/rules/rerender-derived-state.md new file mode 100644 index 000000000..e5c899f6c --- /dev/null +++ b/hive-ui/.claude/skills/react-best-practices/rules/rerender-derived-state.md @@ -0,0 +1,29 @@ +--- +title: Subscribe to Derived State +impact: MEDIUM +impactDescription: reduces re-render frequency +tags: rerender, derived-state, media-query, optimization +--- + +## Subscribe to Derived State + +Subscribe to derived boolean state instead of continuous values to reduce re-render frequency. + +**Incorrect (re-renders on every pixel change):** + +```tsx +function Sidebar() { + const width = useWindowWidth() // updates continuously + const isMobile = width < 768 + return