Skip to content

Latest commit

 

History

History
601 lines (438 loc) · 18.1 KB

File metadata and controls

601 lines (438 loc) · 18.1 KB

Contributing to ScriptHammer

Thank you for your interest in contributing to ScriptHammer. This guide will help you get started.

Table of Contents


Code of Conduct

Be respectful, inclusive, and constructive. We welcome contributors of all experience levels.


Prerequisites

  • Docker Desktop (required) - All development happens in containers
  • Git - Version control
  • Code editor - VS Code recommended with Docker extension

Important: Local Node.js/pnpm installation is NOT required for development — every build, test and lint runs inside Docker.

One caveat, because two versions of this guide used to contradict each other on it: the git hooks in .husky/ execute on the host, not in the container. They shell back into Docker to do the actual work (.husky/pre-push runs docker compose exec … pnpm run gitleaks), so a host Node install is still not required — but the hooks do need a POSIX shell and a working docker on your PATH. If you commit from inside the container, as this repo recommends, that is already true.


Development Environment

Initial Setup

# 1. Fork the repository on GitHub

# 2. Clone your fork
git clone https://github.com/YOUR_USERNAME/ScriptHammer.git
cd ScriptHammer

# 3. Add upstream remote
git remote add upstream https://github.com/TortoiseWolfe/ScriptHammer.git

# 4. Create your .env — REQUIRED, the container will not start correctly without it
cp .env.example .env

# 5. Start the development environment
docker compose up -d

# 6. Verify containers are running
docker compose ps

Step 4 is not optional. docker-compose.yml reads UID and GID from .env so the container writes files as you rather than as root; without it you get a node_modules and a .next you cannot delete from the host. .env.example ships UID=1000 / GID=1000, which is right for most single-user Linux and WSL installs — check with id -u and id -g and edit only if yours differ.

Copy the file rather than echoing into it. .env.example also carries the Supabase keys, feature flags and port pins you will want; a redirect (>) silently destroys all of them.

Running Commands

All commands run inside the Docker container:

# Enter the container shell
docker compose exec scripthammer sh

# Or run commands directly
docker compose exec scripthammer pnpm run dev
docker compose exec scripthammer pnpm run test
docker compose exec scripthammer pnpm run lint

Never run npm install or pnpm install on your host machine. This violates the Docker-first principle and may cause inconsistencies.

Available Scripts

Command Description
docker compose exec scripthammer pnpm dev Start the Next.js dev server
docker compose run --rm builder pnpm build Production build — see note
docker compose exec scripthammer pnpm test Run the Vitest suite
docker compose exec scripthammer pnpm test:coverage Generate a coverage report
docker compose exec scripthammer pnpm lint Run ESLint
docker compose exec scripthammer pnpm format Format with Prettier
docker compose exec scripthammer pnpm format:check Check formatting, change nothing
docker compose exec scripthammer pnpm type-check Run TypeScript type checking
docker compose exec scripthammer pnpm storybook Start Storybook

The build row is run --rm builder, not exec scripthammer, and that is load-bearing (#293). next dev and next build both own /app/.next. Building inside the dev container wipes the directory the dev server is serving from, and every route 500s until it recompiles. The builder service is the same image with its own .next volume, which is why it exists.

Wireframe Viewer

SVG wireframes live under each feature dir (features/<category>/<NNN-name>/wireframes/) and render via the Next.js /wireframes route. The viewer auto-discovers every wireframe via a manifest generated by scripts/sync-wireframes.sh on pnpm run dev and pnpm run build.

docker compose up
# Browse to http://localhost:3000/wireframes (or whatever port compose assigns)

Validate wireframes with the shipped 40+ rule validator:

docker compose exec scripthammer \
  python3 .specify/extensions/wireframe/scripts/validate.py --all --summary

Project Structure

ScriptHammer/
├── features/                    # Feature specifications (PRPs)
│   ├── foundation/              # Core features (000-006)
│   ├── core-features/           # Main features (007-012)
│   ├── auth-oauth/              # Auth features (013-016)
│   └── <category>/<NNN-name>/wireframes/   # SVG wireframes, per feature
├── docs/
│   ├── blog/                    # Technical blog posts
│   └── interoffice/             # Internal documentation
├── src/                         # Application source
│   ├── app/                     # Next.js App Router pages
│   ├── components/              # React components (5-file pattern)
│   ├── lib/                     # Utility functions
│   └── hooks/                   # Custom React hooks
├── .specify/                    # SpecKit configuration
│   ├── memory/                  # Constitution + inventory
│   └── templates/               # Output templates
└── scripts/                     # Automation scripts

Making Changes

Branch Naming

Use descriptive branch names with a prefix:

Prefix Purpose Example
feature/ New functionality feature/add-dark-mode
fix/ Bug fixes fix/login-redirect
docs/ Documentation docs/update-api-guide
refactor/ Code improvements refactor/auth-service
test/ Test additions test/payment-flows
# Create a new branch
git checkout -b feature/your-feature-name

Workflow for Features

ScriptHammer uses the SpecKit workflow for feature development:

# 1. Specification phase
/speckit.specify        # Generate spec.md from feature file
/speckit.clarify        # Refine requirements interactively

# 2. Design phase
/wireframe              # Generate SVG wireframes
/wireframe-review       # Review and classify issues
# Repeat until all wireframes pass

# 3. Implementation phase
/speckit.plan           # Generate implementation plan
/speckit.tasks          # Generate task breakdown
/speckit.implement      # Execute implementation

Keeping Your Fork Updated

# Fetch upstream changes
git fetch upstream

# Merge into your branch
git checkout main
git merge upstream/main

# Update your feature branch
git checkout feature/your-feature
git rebase main

Component Guidelines

5-File Pattern (Mandatory)

Every component MUST have exactly 5 files:

src/components/Button/
├── index.tsx                    # Re-exports
├── Button.tsx                   # Component implementation
├── Button.test.tsx              # Unit tests (Vitest)
├── Button.stories.tsx           # Storybook stories
└── Button.accessibility.test.tsx # Pa11y a11y tests

Use the component generator to ensure compliance:

docker compose exec scripthammer pnpm run generate:component Button

Component Structure

// Button.tsx
import { type ButtonHTMLAttributes } from 'react';

export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'primary' | 'secondary' | 'ghost';
  size?: 'sm' | 'md' | 'lg';
}

export function Button({
  variant = 'primary',
  size = 'md',
  children,
  ...props
}: ButtonProps) {
  return (
    <button className={`btn btn-${variant} btn-${size}`} {...props}>
      {children}
    </button>
  );
}
// index.tsx
export { Button } from './Button';
export type { ButtonProps } from './Button';

Testing Requirements

Test-First Development

Write tests BEFORE implementation (RED-GREEN-REFACTOR):

  1. RED: Write a failing test
  2. GREEN: Write minimal code to pass
  3. REFACTOR: Improve code while tests pass

Minimum Coverage

Type Minimum Tool
Unit tests 60% statements, branches, functions, lines Vitest
E2E tests Critical paths Playwright
Accessibility All components Pa11y

The single source of truth is vitest.config.ts:154-157; check there before trusting this table. It said 25% here and 0.5% in a second copy of this guide, against a real threshold of 60 — neither number was ever true, and both survived because nothing compared the prose to the config.

Running Tests

# All tests
docker compose exec scripthammer pnpm run test

# Unit tests only
docker compose exec scripthammer pnpm run test:unit

# E2E tests
docker compose exec scripthammer pnpm run test:e2e

# Accessibility tests
docker compose exec scripthammer pnpm run test:a11y

# Watch mode for development
docker compose exec scripthammer pnpm run test:watch

Test File Examples

// Button.test.tsx
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import { Button } from './Button';

describe('Button', () => {
  it('renders children', () => {
    render(<Button>Click me</Button>);
    expect(screen.getByRole('button')).toHaveTextContent('Click me');
  });

  it('applies variant class', () => {
    render(<Button variant="secondary">Secondary</Button>);
    expect(screen.getByRole('button')).toHaveClass('btn-secondary');
  });
});
// Button.accessibility.test.tsx
import { describe, it, expect } from 'vitest';
import { axe, toHaveNoViolations } from 'jest-axe';
import { render } from '@testing-library/react';
import { Button } from './Button';

expect.extend(toHaveNoViolations);

describe('Button Accessibility', () => {
  it('has no accessibility violations', async () => {
    const { container } = render(<Button>Accessible</Button>);
    const results = await axe(container);
    expect(results).toHaveNoViolations();
  });
});

Submitting Changes

What the git hooks actually do

Knowing this matters, because it tells you what is not checked for you.

pre-commit — two things, in order:

  1. gitleaks protect --staged — secret scan of staged content. A hit blocks the commit.
  2. lint-stagedprettier --write then eslint --fix on *.{js,jsx,ts,tsx}, and prettier --write on *.{css,md,json}.

pre-pushgitleaks detect over the full history, then the CI gate (including a production build). This is why a push takes minutes.

It does NOT run your tests or type-check on commit. An older version of this guide claimed pre-commit ran "related tests" and "TypeScript types"; it never did. Run pnpm test and pnpm type-check yourself before pushing, or the push gate is where you will find out.

Never --no-verify. These hooks have caught real secrets in this repo. If one fails, the output names the file and line — fix it and re-stage.

Pre-Submission Checklist

  • Code follows Style Guide
  • All tests pass (docker compose exec scripthammer pnpm run test)
  • Type check passes (docker compose exec scripthammer pnpm run type-check)
  • Linting passes (docker compose exec scripthammer pnpm run lint)
  • Build succeeds (docker compose run --rm builder pnpm build — the builder, #293)
  • Components follow 5-file pattern
  • Storybook stories updated for UI changes (.stories.tsx is one of the five files)
  • New features have wireframes reviewed
  • Documentation updated if needed

Commit Messages

Use clear, descriptive commit messages:

<type>(<scope>): <description>

[optional body]

Co-Authored-By: Claude <name> <noreply@anthropic.com>

Types: feat, fix, docs, style, refactor, test, chore

Examples:

feat(auth): add password reset flow

Implements forgot password and reset password pages with
email verification via Supabase Auth.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
fix(messaging): resolve group chat notification bug

Messages in group chats now correctly trigger notifications
for all members except the sender.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Pull Request Process

  1. Push your branch

    git push origin feature/your-feature
  2. Create PR on GitHub

    • Use a descriptive title
    • Fill out the PR template
    • Link related issues
  3. PR Description Template

    ## Summary
    
    Brief description of changes (1-3 bullet points)
    
    ## Changes
    
    - Added X
    - Modified Y
    - Removed Z
    
    ## Testing
    
    - [ ] Unit tests added/updated
    - [ ] E2E tests added/updated
    - [ ] Manual testing completed
    
    ## Screenshots
    
    (if applicable)
    
    ## Related Issues
    
    Closes #123
  4. Address Review Feedback

    • Respond to all comments
    • Push fixes as new commits
    • Request re-review when ready
  5. Merge Requirements

    • The two required status checks pass: Test (20.x) and accessibility
    • main is protected, so a PR is the only route in — direct pushes are rejected for everyone, admins included

    Branch protection does not require an approving review, and does not require conversations to be resolved. Request a review when the change warrants one, but nothing blocks the merge while you wait for it.

    Only those two checks are required because they are the two workflows with no paths: filter, so they report on every PR. A required check that never reports is pending forever rather than skipped, which would make any docs-only PR permanently unmergeable. The other workflows still run and should still be green.


Style Guide

TypeScript

  • Use strict mode ("strict": true)
  • Prefer interfaces over types for objects
  • Export types alongside components
  • Use explicit return types on functions
// Good
export interface UserProps {
  name: string;
  email: string;
}

export function User({ name, email }: UserProps): JSX.Element {
  return (
    <div>
      {name} ({email})
    </div>
  );
}

// Avoid
export type UserProps = { name: string; email: string };
export const User = ({ name, email }) => <div>{name}</div>;

React

  • Use functional components with hooks
  • Prefer named exports
  • Keep components focused (single responsibility)
  • Use composition over inheritance

CSS/Tailwind

  • Use Tailwind utilities for styling
  • Use DaisyUI components when available
  • Follow mobile-first responsive design
  • Ensure 44px minimum touch targets
// Good - mobile-first
<button className="p-4 text-sm md:text-base lg:p-6">
  Click
</button>

// Avoid - desktop-first
<button className="p-6 lg:p-4">
  Click
</button>

Accessibility

  • All interactive elements must be keyboard accessible
  • Use semantic HTML (<button>, <nav>, <main>)
  • Include ARIA labels where needed
  • Maintain color contrast ratios (WCAG AA)
  • Test with screen readers

Strings that must survive a rebrand — rebrand:keep

This is a template. scripts/rebrand.sh rewrites the project name across 200+ files when someone forks it, so any literal you add that must keep naming the upstream project needs to say so. Mark it with rebrand:keep in a comment on the same line:

href: 'https://github.com/TortoiseWolfe/ScriptHammer', // rebrand:keep
  • Line-scoped, not file-scoped. A marker at the top of a file protects nothing below it.
  • The token is deliberately brand-neutral. scripthammer:keep would contain the very string the rebrand searches for.
  • Prettier keeps a trailing // rebrand:keep on its own line, so formatting will not separate the marker from what it protects.

Typical cases are upstream URLs, the attribution link, and anything naming this repository as the source rather than as the current project. When in doubt, ask whether a fork would still want that line to name the upstream project — if yes, mark it.

Note that this section is written to survive its own advice: it describes the rule without embedding the project name in a sentence that a rebrand would falsify. That is usually easier than marking prose line by line.

See docs/FORKING.md for the fork-side view.


Getting Help

  • Questions: Open a Discussion
  • Bugs: Open an Issue
  • Security: See SECURITY.md for the reporting process — do not open a public issue

Before opening an issue

  1. Search existing issues, including closed ones — a surprising number of things here were fixed and closed rather than never reported.
  2. Reproduce in a clean environment (docker compose down && docker compose up).

Include: a descriptive title, steps to reproduce, expected vs actual behaviour, any error output, and your environment (OS, browser, Docker version).

Two issue templates exist — .github/ISSUE_TEMPLATE/accessibility.md and optimization.md. Anything else is a blank issue; that is fine.


License

By contributing, you agree that your contributions will be licensed under the same license as the project.


Thank you for contributing to ScriptHammer!