Contributing
Contributing to Knitly
Section titled “Contributing to Knitly”Thanks for your interest in Knitly! This guide covers everything you need to know to get started.
Quick Start
Section titled “Quick Start”- Read the README for project overview
- Set up development environment
- Pick an issue from GitHub Issues
- Create a branch and start coding
Development Environment
Section titled “Development Environment”Requirements
Section titled “Requirements”- Bun (Node.js runtime)
- Git
- ffmpeg (optional, for video processing)
# Clone repositorygit clone https://github.com/knitly-app/knitly.gitcd knitly
# Install dependenciesbun installRunning Development
Section titled “Running Development”# Start development serversbun run dev
# Frontend: http://localhost:5173# API: http://localhost:3000Seed Data
Section titled “Seed Data”# Create admin userbun --cwd server run seed
# Create admin + test databun --cwd server run seed:devOverride admin credentials:
ADMIN_EMAIL=you@example.com ADMIN_PASSWORD=yourpass bun --cwd server run seedProject Structure
Section titled “Project Structure”knitly/├── frontend/ # Preact SPA│ ├── src/│ │ ├── api/ # API client and query keys│ │ ├── routes/ # Router routes│ │ ├── components/# UI components│ │ ├── hooks/ # Custom hooks│ │ ├── stores/ # State management│ │ └── test/ # Tests│ └── dist/ # Built frontend├── server/ # Hono API server│ ├── src/│ │ ├── routes/ # API routes│ │ ├── middleware/# Request middleware│ │ ├── lib/ # Database and utilities│ │ └── __tests__/ # API tests│ └── uploads/ # File storage└── custom/ # Gitignored extensionsCode Style
Section titled “Code Style”Preact
Section titled “Preact”- Use hooks, not class components
- Use functional components
- Keep components small and focused
State Management
Section titled “State Management”- Use TanStack Query for all server state
- Use Zustand for client state
- Derive state as expressions
- Never duplicate state in useState
React Hooks
Section titled “React Hooks”- No useCallback - ever
- No useEffect unless syncing with external systems
- Self-documenting names over explanatory comments
TypeScript
Section titled “TypeScript”- Strict mode enabled
- No
anytypes - Use branded types for better safety
Linting
Section titled “Linting”# Run lintingbun run lint
# Fix linting issuesbun run lint:fixMaking Changes
Section titled “Making Changes”1. Create a Branch
Section titled “1. Create a Branch”# For featuresgit checkout -b feature/your-feature-name
# For bugsgit checkout -b bugfix/your-bug-name2. Make Your Changes
Section titled “2. Make Your Changes”- Follow existing code patterns
- Add tests for new functionality
- Update documentation as needed
3. Test Your Changes
Section titled “3. Test Your Changes”# Run all testsbun test
# Run frontend testsbun --cwd frontend run test
# Run E2E testsbun --cwd frontend run test:e2e
# Run specific testbun test src/test/unit/some-test.test.ts4. Run Linting
Section titled “4. Run Linting”bun run lint5. Commit Your Changes
Section titled “5. Commit Your Changes”Use conventional commit format:
feat: add support for video uploadsfix: resolve login issue on mobiledocs: update configuration guiderefactor: move API client to separate module6. Push and Create PR
Section titled “6. Push and Create PR”# Push branchgit push origin feature/your-feature-name
# Create pull request on GitHubPull Request Guidelines
Section titled “Pull Request Guidelines”- Keep PRs focused - one feature or fix per PR
- Write clear descriptions - explain what changed and why
- Include screenshots for UI changes
- Don’t refactor unrelated code in the same PR
- Respond to review comments promptly
Architecture Principles
Section titled “Architecture Principles”Keep It Simple
Section titled “Keep It Simple”- Avoid unnecessary complexity
- Follow existing patterns
- Assume future maintainers are smart but unfamiliar
Performance First
Section titled “Performance First”- Optimize for 120 users per instance
- Database indexes for common queries
- Lazy loading for routes and components
Security by Default
Section titled “Security by Default”- Input validation on all endpoints
- XSS sanitization on user content
- Rate limiting on sensitive endpoints
- SQL parameterized queries
Testing
Section titled “Testing”Unit Tests
Section titled “Unit Tests”bun --cwd frontend run testTest files: src/**/*.test.ts and src/**/*.test.tsx
Component Tests
Section titled “Component Tests”bun --cwd frontend run testTest files: src/test/component/*.test.tsx
E2E Tests
Section titled “E2E Tests”bun --cwd frontend run test:e2eTest files: src/e2e/*.spec.ts
API Tests
Section titled “API Tests”bun testTest files: server/src/__tests__/*.test.ts
Documentation
Section titled “Documentation”Updating Docs
Section titled “Updating Docs”- Update relevant docs in
docs/ - Update README.md if needed
- Add inline comments only for non-obvious logic
API Documentation
Section titled “API Documentation”For new API endpoints:
- Update docs/api.md
- Add type definitions
- Document request/response format
Common Tasks
Section titled “Common Tasks”Adding an API Route
Section titled “Adding an API Route”import { Hono } from "hono";import { ensureSession } from "../middleware/auth.js";
export const greetRouter = new Hono();
greetRouter.get("/", ensureSession, (c) => { return c.json({ message: "Hello!" });});Mount it in server/src/app.js:
import { greetRouter } from "./routes/greet.js";app.route("/api/greet", greetRouter);Adding a Component
Section titled “Adding a Component”Components are plain Preact functions. Import hooks from preact/hooks, and only reach for useState when the value can’t be derived.
import { useState } from "preact/hooks";
export function Counter() { const [count, setCount] = useState(0);
return ( <button onClick={() => setCount(count + 1)}> Count: {count} </button> );}Adding a Data Hook
Section titled “Adding a Data Hook”All server state goes through TanStack Query. A data hook wraps useQuery with a shared query key:
import { useQuery } from "@tanstack/react-query";import { users } from "../api/endpoints";import { queryKeys } from "../api/queryKeys";
export function useMembers() { return useQuery({ queryKey: queryKeys.users.all(), queryFn: () => users.list(), staleTime: 1000 * 60, });}For writes, wrap useMutation and invalidate the affected query keys in onSuccess. Never mirror server data into useState or Zustand.
Issue Templates
Section titled “Issue Templates”Bug Report
Section titled “Bug Report”**Description**A clear description of what the bug is.
**To Reproduce**Steps to reproduce the behavior:1. Go to '...'2. Click on '...'3. See error
**Expected behavior**A clear description of what you expected to happen.
**Screenshots**If applicable, add screenshots to help explain.
**Environment:**- Browser: [e.g. Chrome, Safari]- OS: [e.g. iOS, Windows]- Knitly Version: [e.g. v0.1.0]Feature Request
Section titled “Feature Request”**Problem**A clear description of what problem this would solve.
**Solution**A clear description of the proposed solution.
**Alternatives**Any alternative solutions considered.
**Additional context**Add any other context or screenshots.Getting Help
Section titled “Getting Help”- Join our Discord (if available)
- Check existing issues
- Ask questions in PRs
License
Section titled “License”By contributing, you agree that your contributions will be licensed under the MIT License.
Happy coding! 🎉