diff --git a/.claude/guides/README.md b/.claude/guides/README.md new file mode 100644 index 0000000..5242047 --- /dev/null +++ b/.claude/guides/README.md @@ -0,0 +1,7 @@ +# Guides + +Brief "How To" guides that explain both _what_ and _why_ for each topic. Aimed at students and anyone picking up this template for the first time. Each guide is ~100 lines — enough to understand the concept and get productive, not a reference manual. + +| Guide | What it covers | +| -------------------------------------------------- | --------------------------------------------------------------------------------- | +| [AI-Assisted Workflow](ai-assisted-workflow.md) | Using Claude Code skills to plan iterations, create issues, branch, commit, and PR | diff --git a/.claude/guides/ai-assisted-workflow.md b/.claude/guides/ai-assisted-workflow.md new file mode 100644 index 0000000..6a593cd --- /dev/null +++ b/.claude/guides/ai-assisted-workflow.md @@ -0,0 +1,69 @@ +# AI-Assisted Workflow Guide + +This guide walks you through using Claude Code and the skills in this repo to follow the team development workflow. Each section matches a phase of your iteration cycle. + +## Prerequisites + +- GitHub CLI authenticated: `gh auth login` +- Claude Code installed and working +- `docs/team-agreement.md` and `docs/product-requirements.md` are updated and committed to the repo + +## Step 1: Create GitHub Issues + +Ask Claude to turn the plan into issues: + +```plaintext +> Read the iteration 1 plan and create GitHub issues for each requirement + and task. The team members are listed in docs/team-agreement.md. + Use the "Iteration 1" milestone. +``` + +Check GitHub to verify: correct labels, milestone, assignees, and acceptance criteria on each issue. + +## Step 2: Work on an Issue (Branch, Commit, PR) + +> **Note:** Before you can work on actual features, you need to scaffold your tech stack and update `README.md` and `CLAUDE.md` with your project's setup, commands, and architecture. Without this, Claude won't know how to generate code that fits your project. + +**Branch:** Pick an issue and create a branch: + +```plaintext +> Create a branch for issue #3 (task: setup CI pipeline). I am . +``` + +Claude creates a branch following the naming convention: `/task/issue-3-setup-ci-pipeline` + +**Commit:** After making changes, commit with the issue reference: + +```plaintext +> Commit my changes +``` + +**PR:** Push and open a pull request: + +```plaintext +> /pr-workflow 3 +``` + +Claude verifies the branch name, checks diff size, pushes, and creates a PR with `Closes #3` and the PR template filled in. + +## Step 3: Retrospective + +At the end of an iteration, create the retrospective issue: + +```plaintext +> /retrospective 1 +``` + +Claude creates a GitHub issue with the right labels (`task` + `retrospective`), milestone, and all team members assigned. The three sections (what went well, what didn't, lessons learned) are for the team to fill in — be specific and actionable. + +## What to Verify on GitHub + +After completing these steps, your repo should show: + +- **Milestone view** — all iteration issues in one place with open/closed status +- **Issue list** — features and tasks with correct labels and assignees +- **PR list** — PRs linked to issues with review status +- **Commit history** — every commit references an issue number +- **Branch list** — branches follow the naming convention + +The analytics scripts parse labels, branch names, and PR metadata. If conventions are not followed, your work won't be attributed to you. diff --git a/.claude/rules/branch-naming.md b/.claude/rules/branch-naming.md new file mode 100644 index 0000000..3efd501 --- /dev/null +++ b/.claude/rules/branch-naming.md @@ -0,0 +1,20 @@ +# Branch Naming Convention + +When creating branches, ALWAYS follow this pattern: + +`//issue--` + +Where: + +- `author` is the GitHub username of the branch creator +- `type` matches the issue label: `enhancement`, `bug`, or `task` +- `number` is the GitHub issue number +- `short-description` is 2-4 lowercase words separated by hyphens + +Examples: + +- `asmith/enhancement/issue-5-user-authentication` +- `jdoe/bug/issue-12-fix-login-redirect` +- `mchen/task/issue-3-setup-ci-pipeline` + +Never push directly to master. Always create a branch and open a PR. diff --git a/.claude/rules/commit-conventions.md b/.claude/rules/commit-conventions.md new file mode 100644 index 0000000..a961eb3 --- /dev/null +++ b/.claude/rules/commit-conventions.md @@ -0,0 +1,18 @@ +# Commit Conventions + +When creating commits: + +1. Reference the issue number in the commit message: `Add file validation (#12)` +2. Use imperative mood in the subject line: "Add feature" not "Added feature" +3. Keep the subject line under 72 characters + +Examples: + +- `Add login form component (#5)` +- `Fix redirect loop on logout (#12)` +- `Set up CI pipeline (#6)` + +Do not co-sign the commit! For example, do not end the commit with something like this: + +Co-Authored-By: Claude Opus 4.6 + diff --git a/.claude/rules/pr-conventions.md b/.claude/rules/pr-conventions.md new file mode 100644 index 0000000..3d42c6f --- /dev/null +++ b/.claude/rules/pr-conventions.md @@ -0,0 +1,6 @@ +# Pull Request Conventions + +- Include `Closes #` or `Fixes #` in every PR body +- Keep PRs under ~400 changed lines +- Use merge commits only — never squash or rebase +- See the `pr-workflow` skill for full details and the collaborative feature branch flow diff --git a/.claude/skills/github-issues/SKILL.md b/.claude/skills/github-issues/SKILL.md new file mode 100644 index 0000000..5b48e11 --- /dev/null +++ b/.claude/skills/github-issues/SKILL.md @@ -0,0 +1,91 @@ +--- +name: github-issues +description: Create and manage GitHub issues following project conventions. Use when creating issues, managing issue labels, assigning issues, setting up milestones, or asking about issue conventions and requirements. +argument-hint: "issue title or number" +allowed-tools: + - Read + - Grep + - Glob + - "Bash(gh issue *)" + - "Bash(gh api *)" + - "Bash(gh label *)" +--- + +# GitHub Issues Skill + +## Related Skills + +- `iteration-planning` — creates issues from the task breakdown +- `pr-workflow` — PRs reference issues via `Closes #` + +## Creating Issues + +Every issue must have: + +1. **Descriptive title** — clear and concise +2. **Description** — what needs to be done and why +3. **Acceptance criteria** — checklist of testable criteria +4. **Label** — exactly one of: `enhancement`, `task`, or `bug` +5. **Milestone** — the current iteration (e.g., "Iteration 1") +6. **Assignees** — see ownership rules below + +## Labels + +The project uses these labels: + +- `enhancement` — new feature or request +- `task` — development work that isn't a feature or bug +- `bug` — something isn't working +- `documentation` — improvements or additions to documentation +- `retrospective` — added alongside `task` for retrospective issues + +If labels don't exist yet, create them: + +```bash +gh label create enhancement --description "New feature or request" --color A2EEEF +gh label create task --description "Development task" --color 1D76DB +gh label create bug --description "Something isn't working" --color D73A4A +gh label create retrospective --description "Iteration retrospective" --color FBCA04 +``` + +## Milestones + +Milestones are named "Iteration 1" through "Iteration 4". Create them if they don't exist: + +```bash +gh api repos/{owner}/{repo}/milestones -f title="Iteration 1" +``` + +## Issue Ownership + +- **Feature issues** — assign one owner + one supporting (up to three if the feature is large) +- **Task and bug issues** — assign one person (optional supporting) +- **Retrospective issues** — assign all team members + +If you contribute to an issue but aren't assigned, analytics won't attribute the work to you. + +## Creating Issues via CLI + +Read the matching template in `.github/ISSUE_TEMPLATE/` (feature.yml, task.yml, or bug.yml) and use its structure for the `--body`. Example: + +```bash +gh issue create \ + --title "Add search-by-title to items list endpoint" \ + --label "enhancement" \ + --milestone "Iteration 1" \ + --assignee "owner,supporting" \ + --body "## Description +Add a query parameter to the items endpoint that filters by title. + +## Acceptance Criteria +- [ ] Search is case-insensitive +- [ ] Empty query returns all items +- [ ] Tests added for all cases" +``` + +For task and bug issues, follow the same pattern using the corresponding template structure. + +## Rules + +- Only create issues for the **current** iteration — never for future ones +- Use the GitHub issue templates (feature, task, or bug) when creating from the UI diff --git a/.claude/skills/iteration-planning/SKILL.md b/.claude/skills/iteration-planning/SKILL.md new file mode 100644 index 0000000..770ca00 --- /dev/null +++ b/.claude/skills/iteration-planning/SKILL.md @@ -0,0 +1,57 @@ +--- +name: iteration-planning +description: Create and manage iteration plans. Use when starting a new iteration, writing an iteration plan, breaking down requirements into tasks, planning sprint work, or asking about iteration plan format and structure. +argument-hint: "iteration number" +allowed-tools: + - Read + - Write + - Edit + - Grep + - Glob + - "Bash(gh issue *)" + - "Bash(gh api *)" +--- + +# Iteration Planning Skill + +## Related Skills + +- `github-issues` — create the issues from the task breakdown +- `retrospective` — close out the iteration after work is done + +## Process + +1. Read the template at `docs/iteration-plan-template.md` +2. Ask the user for the iteration number and requirements (or read from a PRD if available) +3. **Draft** `docs/iteration--plan.md` with the 3 required sections: + - **Requirements & Acceptance Criteria** — each requirement gets a title, description, and testable acceptance criteria checklist + - **Coordination & Design Decisions** — architecture choices, API contracts, shared interfaces, responsibilities, dependencies + - **Task Breakdown** — table of tasks with type, assignee(s), and issue number +4. **Ask the user to review the draft** — the team owns the final plan, not the AI. In particular: + - Are the acceptance criteria complete? Any edge cases missing? + - Do the coordination decisions (API contracts, schemas, shared interfaces) match what the team agreed on? + - Is the task breakdown realistic? Are assignments correct? +5. Incorporate feedback and finalize the plan + +## Timeline + +- **Iteration plan due:** Tuesday 6 PM of Week 1 +- **Iteration work period:** remainder of Week 1 + Week 2 +- **Retrospective due:** Monday end-of-day after the iteration ends + +## Guidelines + +- Each requirement must have clear, testable acceptance criteria +- Break requirements into issues that can be completed by 1-2 people +- Follow issue ownership rules from the `github-issues` skill (not a fixed number of assignees) +- Only create issues for the current iteration — never for future iterations +- Apply the correct label to each issue: `enhancement`, `task`, or `bug` +- Set the milestone to the current iteration (e.g., "Iteration 1") + +## After Planning + +Offer to create the GitHub issues from the task breakdown using the `github-issues` skill conventions: + +- Each issue gets a descriptive title, description, and acceptance criteria +- Apply the correct label and milestone +- Assign the correct team members diff --git a/.claude/skills/pr-workflow/SKILL.md b/.claude/skills/pr-workflow/SKILL.md new file mode 100644 index 0000000..ec9a36d --- /dev/null +++ b/.claude/skills/pr-workflow/SKILL.md @@ -0,0 +1,80 @@ +--- +name: pr-workflow +description: Create pull requests and manage the PR workflow. Use when creating a PR, reviewing PRs, managing feature branch merges, or asking about PR conventions, code review process, or the collaborative feature branch workflow. +argument-hint: "PR or issue number" +allowed-tools: + - Read + - Grep + - Glob + - "Bash(gh pr *)" + - "Bash(gh api *)" + - "Bash(git *)" +--- + +# PR Workflow Skill + +## Related Skills + +- `github-issues` — PRs must reference an issue via `Closes #` +- Branch naming rules: `.claude/rules/branch-naming.md` + +## Before Creating a PR + +1. **Verify branch naming** — must match `//issue--` +2. **Check diff size** — warn if over ~400 changed lines and suggest splitting +3. **Ensure commits reference the issue** — e.g., `Add validation (#12)` +4. **Push the branch** to remote if not already pushed + +## Creating a PR + +Use the PR template (`.github/PULL_REQUEST_TEMPLATE.md`): + +```bash +gh pr create \ + --title "Short descriptive title" \ + --body "## Summary + +Closes # + +## Changes + +- Change 1 +- Change 2 + +## How to Test + +1. Step 1 +2. Step 2 + +## Checklist + +- [x] PR is under ~400 changed lines +- [x] Branch follows naming convention +- [x] Commits reference the issue number +- [ ] Tests pass +- [ ] At least one teammate has been requested for review" \ + --reviewer "teammate-username" +``` + +## Key Rules + +- Always include `Closes #` or `Fixes #` in the PR body +- Target the `master` branch (or parent feature branch for sub-branches) +- Request at least one teammate as reviewer +- Use **merge commits only** — never squash or rebase +- Delete the branch after merging + +## Collaborative Feature Branch Workflow + +When multiple people are actively coding the **same feature** in parallel: + +1. Feature owner creates a feature branch from `master` +2. Each contributor creates a sub-branch off the feature branch +3. Contributors open PRs targeting the feature branch (not master) +4. Feature owner reviews and merges contributor PRs +5. Each contributor is responsible for resolving conflicts in their own PR +6. When complete, feature owner PRs the feature branch into `master` + +If only one person is implementing a feature, skip this — PR directly to `master`. + +See [reference.md](reference.md) for the git commands. diff --git a/.claude/skills/pr-workflow/reference.md b/.claude/skills/pr-workflow/reference.md new file mode 100644 index 0000000..b898c49 --- /dev/null +++ b/.claude/skills/pr-workflow/reference.md @@ -0,0 +1,73 @@ +# PR Workflow Reference + +## Common Commands + +### Create a PR + +Use the PR template from `.github/PULL_REQUEST_TEMPLATE.md` for the body structure: + +```bash +gh pr create --title "Title" --body "body matching the template" --reviewer "username" +``` + +### List open PRs + +```bash +gh pr list +``` + +### View a PR + +```bash +gh pr view +``` + +### Check PR diff size + +```bash +gh pr diff --stat +``` + +### Merge a PR (merge commit only) + +```bash +gh pr merge --merge --delete-branch +``` + +## Collaborative Feature Branch Flow + +```plaintext +master + └── feature-branch (created by feature owner) + ├── contributor-1-branch → PR into feature-branch + └── contributor-2-branch → PR into feature-branch +``` + +### Create the feature branch + +```bash +git checkout master +git pull +git checkout -b //issue-- +git push -u origin //issue-- +``` + +### Create a sub-branch + +```bash +git checkout +git pull +git checkout -b //issue-- +``` + +### PR for sub-branch (targets feature branch, not master) + +```bash +gh pr create --base --title "Title" --body "Closes #N (partial)" +``` + +### Final PR to master + +```bash +gh pr create --base master --title "Feature title" --body "Closes #N" +``` diff --git a/.claude/skills/retrospective/SKILL.md b/.claude/skills/retrospective/SKILL.md new file mode 100644 index 0000000..bdd9856 --- /dev/null +++ b/.claude/skills/retrospective/SKILL.md @@ -0,0 +1,60 @@ +--- +name: retrospective +description: Create iteration retrospective issues. Use when writing a retrospective, closing out an iteration, reflecting on iteration progress, or asking about retrospective format. +argument-hint: "iteration number" +allowed-tools: + - Read + - Write + - Edit + - Grep + - Glob + - "Bash(gh issue *)" + - "Bash(gh api *)" +--- + +# Retrospective Skill + +## Related Skills + +- `iteration-planning` — the iteration plan this retrospective reviews +- `github-issues` — conventions for the retrospective issue + +## Process + +1. Ask for the iteration number (or infer from recent milestones) +2. Gather context by reading: + - The iteration plan (`docs/iteration--plan.md`) + - Closed issues and merged PRs in the iteration's milestone +3. Create a GitHub issue titled **"Iteration X Retrospective"** with the correct format (see below), including a brief summary of what was accomplished based on the data gathered +4. Tell the team to fill in the three sections themselves — the AI cannot know what went well or didn't from the team's perspective + +## Issue Format + +- **Labels:** `task`, `retrospective` +- **Milestone:** Current iteration (e.g., "Iteration 1") +- **Assignees:** All team members + +### Issue Body Structure + +```markdown +## What Went Well + +- (bullet points) + +## What Didn't Go Well + +- (bullet points) + +## Lessons Learned + +- (bullet points) +``` + +## Guidelines + +- Be specific — reference actual issues, PRs, or events +- Focus on process improvements, not blame +- Each section should have at least 2-3 bullet points +- The retrospective is due **Monday end-of-day** after the iteration ends +- Encourage the team to comment on the issue with their own reflections +- Leave the issue open — the instructor closes it after reviewing diff --git a/.claude/skills/skill-authoring/SKILL.md b/.claude/skills/skill-authoring/SKILL.md new file mode 100644 index 0000000..70db2d0 --- /dev/null +++ b/.claude/skills/skill-authoring/SKILL.md @@ -0,0 +1,172 @@ +--- +name: skill-authoring +description: Best practices for writing and auditing Claude Code SKILL.md files. Use when creating a new skill, editing an existing skill, auditing skills, improving skill frontmatter, optimizing skill descriptions for auto-triggering, or asking about skill file structure, frontmatter fields, or allowed-tools configuration. +allowed-tools: + - Read + - Write + - Edit + - Grep + - Glob +--- + +# Skill Authoring Guide + +Best practices for writing effective Claude Code skills. A skill is a `SKILL.md` file in `.claude/skills//` that teaches Claude how to handle specific tasks. + +## Quick Reference + +| Topic | File | Description | +| --------------- | ---------------------------- | ---------------------------------------- | +| **Frontmatter** | [reference.md](reference.md) | All YAML frontmatter fields with details | +| **Tool names** | [reference.md](reference.md) | Complete allowed-tools list | + +--- + +## Core Principles + +1. **Description is the trigger** — Claude decides whether to load a skill based solely on its `description`. Make it specific with concrete trigger phrases. +2. **Lean SKILL.md, heavy reference files** — Only the description is always in context. Full skill content loads on invocation. Keep SKILL.md focused; move detailed docs to separate files. +3. **Minimize permission friction** — List every tool the skill needs in `allowed-tools` so Claude doesn't pause to ask. +4. **No fluff** — Every line should teach Claude something actionable. Cut marketing language, redundant explanations, and obvious statements. + +--- + +## Writing the Description + +The `description` field is the single most important part of a skill. It controls auto-triggering. + +**Pattern:** `{What it does}. Use when {trigger phrase 1}, {trigger phrase 2}, ... or asking about {topic 1}, {topic 2}.` + +**Good:** + +```yaml +description: Component patterns and UI library usage. Use when creating components, using shadcn/ui, implementing theming, working with forms, or asking about component organization. +``` + +**Bad:** + +```yaml +description: Helps with frontend components and UI stuff. +``` + +Rules: + +- Include 3-8 specific trigger phrases that a user would naturally say +- Use action verbs: "creating", "implementing", "debugging", "adding", "configuring" +- Include tool/library names: "shadcn/ui", "Legend-State", "zod" +- Never use second person ("you") or marketing language + +--- + +## Choosing allowed-tools + +Only include tools the skill actually needs. Read-only tools (`Read`, `Grep`, `Glob`, `WebSearch`, `WebFetch`) don't require permission anyway, but listing them documents intent. + +**The tools that matter most** (these are the ones that prompt for permission): + +| Tool | Include when the skill... | +| ------- | ----------------------------------------------------- | +| `Write` | Creates new files | +| `Edit` | Modifies existing files | +| `Bash` | Runs shell commands (use patterns: `Bash(npm run *)`) | +| `Task` | Spawns subagents for parallel work | + +**Common combos:** + +- **Read-only skill:** `Read, Grep, Glob` +- **Code-writing skill:** `Read, Write, Edit, Grep, Glob` +- **Full autonomy skill:** `Read, Write, Edit, Grep, Glob, Bash, WebSearch, WebFetch` + +--- + +## SKILL.md Structure + +Follow this structure for consistency: + +```markdown +--- +name: my-skill +description: {What it does}. Use when {triggers}. +allowed-tools: + - {tools} +--- + +# {Skill Name} Guide + +One-line summary of what this skill covers. + +## Related Skills (optional) + +Links to related skills for cross-referencing. + +## Quick Reference (optional) + +Table linking to supporting files. + +## {Core Sections} + +The actual instructions, patterns, and examples. +Keep to what Claude needs to act. No preambles. + +## Checklist (optional) + +Steps for common tasks within this skill's domain. + +## Detailed Documentation (optional) + +Links to reference files for deep content. +``` + +--- + +## Content Guidelines + +**Do:** + +- Use tables for structured reference (locations, conventions, mappings) +- Show code examples from the actual codebase, not hypotheticals +- Use checklists for multi-step procedures +- Link to reference files for anything over ~20 lines of detail + +**Don't:** + +- Explain why skills exist or how Claude works +- Repeat information available in referenced files +- Add "Notes:", "Important:", or "Remember:" prefixes — just state the instruction +- Include code examples for things Claude already knows (basic JS, HTML, etc.) + +--- + +## Supporting Files + +Move heavy content out of SKILL.md into the skill's directory: + +```plaintext +my-skill/ +├── SKILL.md # Core instructions (~100-200 lines) +├── patterns.md # Code patterns and examples +├── reference.md # Detailed API/config reference +└── conventions.md # Naming and style rules +``` + +Claude loads these lazily — only when it follows a link or decides it needs more detail. This keeps the context window lean. + +--- + +## Auditing Checklist + +When reviewing an existing skill: + +- [ ] **Description** — Contains 3-8 specific trigger phrases? Uses action verbs? +- [ ] **allowed-tools** — Lists every tool the skill needs? No missing write/edit/bash? +- [ ] **Content length** — SKILL.md under ~200 lines? Heavy content in reference files? +- [ ] **No fluff** — Every line is actionable? No marketing, no obvious statements? +- [ ] **Code examples** — From the actual codebase, not generic? +- [ ] **Cross-references** — Links to related skills where relevant? +- [ ] **Frontmatter fields** — Using `argument-hint` if the skill takes args? `disable-model-invocation` for dangerous workflows? + +--- + +## Detailed Documentation + +- [reference.md](reference.md) — Complete frontmatter field reference and tool name list diff --git a/.claude/skills/skill-authoring/reference.md b/.claude/skills/skill-authoring/reference.md new file mode 100644 index 0000000..f7889fb --- /dev/null +++ b/.claude/skills/skill-authoring/reference.md @@ -0,0 +1,154 @@ +# Skill Authoring Reference + +Complete reference for all SKILL.md frontmatter fields, tool names, and variables. + +--- + +## All Frontmatter Fields + +### Core Fields + +| Field | Type | Default | Description | +| --------------- | ------ | --------------- | ------------------------------------------------------------------------------------------------ | +| `name` | string | directory name | Slash-command name. Lowercase, hyphens, max 64 chars. Must match directory name. | +| `description` | string | first paragraph | What the skill does + trigger phrases. **Primary signal for auto-triggering.** | +| `allowed-tools` | array | none | Tools Claude can use without permission during skill execution. Array or comma-separated string. | + +### Invocation Control + +| Field | Type | Default | Description | +| -------------------------- | ------- | ------- | --------------------------------------------------------------------------------------------- | +| `disable-model-invocation` | boolean | `false` | `true` = Claude cannot auto-invoke. Description not loaded into context. Manual `/name` only. | +| `user-invocable` | boolean | `true` | `false` = hidden from `/` menu. Claude can still auto-invoke. Use for background knowledge. | + +### Execution Context + +| Field | Type | Default | Description | +| --------- | ------ | ----------------- | -------------------------------------------------------------------------------------------------- | +| `model` | string | session default | Override model for this skill. Values: `opus`, `sonnet`, `haiku`, or full model ID. | +| `context` | string | none | `fork` = run in isolated subagent. Skill content becomes subagent prompt. No conversation history. | +| `agent` | string | `general-purpose` | Subagent type when `context: fork`. Options: `Explore`, `Plan`, `general-purpose`. | + +### User Experience + +| Field | Type | Default | Description | +| --------------- | ------ | ------- | ------------------------------------------------------------------ | +| `argument-hint` | string | none | Autocomplete hint for expected arguments. E.g., `"[issue-number]"` | + +### Skill-Scoped Hooks + +```yaml +hooks: + PreToolUse: + - matcher: "Bash" + hooks: + - type: command + command: "./scripts/validate.sh" + PostToolUse: + - matcher: "Edit|Write" + hooks: + - type: command + command: "./scripts/lint.sh" +``` + +Only `PreToolUse`, `PostToolUse`, and `Stop` events are supported in skill frontmatter. + +--- + +## Complete Tool Names for allowed-tools + +### Standard Tools + +| Tool | Permission needed | Purpose | +| ----------------- | ----------------- | ------------------------------ | +| `Read` | No | Read file contents | +| `Write` | Yes | Create or overwrite files | +| `Edit` | Yes | Replace text in existing files | +| `Glob` | No | Find files by pattern | +| `Grep` | No | Search file contents | +| `Bash` | Yes | Execute shell commands | +| `WebFetch` | No | Fetch and process web content | +| `WebSearch` | No | Search the web | +| `Task` | Yes | Spawn subagents | +| `NotebookEdit` | Yes | Edit Jupyter notebooks | +| `AskUserQuestion` | No | Request input from user | + +### Bash with Command Filtering + +Restrict Bash to specific commands using patterns: + +```yaml +allowed-tools: + - "Bash(npm run *)" + - "Bash(git commit *)" + - "Bash(npx shadcn@latest add *)" +``` + +`*` is a wildcard. `Bash` without a pattern allows all commands. + +### MCP Tools + +Format: `mcp____` + +```yaml +allowed-tools: + - "mcp__memory__create_entities" + - "mcp__filesystem__read_file" +``` + +--- + +## Variables Available in Skill Content + +| Variable | Description | +| ---------------------- | ---------------------------------------------------- | +| `$ARGUMENTS` | All arguments passed to the skill | +| `$ARGUMENTS[N]` | Nth argument (0-indexed) | +| `$0`, `$1`, `$2` | Shorthand for `$ARGUMENTS[0]`, `$ARGUMENTS[1]`, etc. | +| `${CLAUDE_SESSION_ID}` | Current session ID | + +### Shell Preprocessing + +Use `` !`command` `` to run shell commands before Claude sees the content: + +```markdown +Current branch: !`git branch --show-current` +Recent changes: !`git log --oneline -5` +``` + +The output replaces the placeholder before Claude processes the skill. + +--- + +## Frontmatter Template + +```yaml +--- +name: my-skill +description: What this does. Use when {trigger1}, {trigger2}, {trigger3}, or asking about {topic1}, {topic2}. +allowed-tools: + - Read + - Write + - Edit + - Grep + - Glob +# argument-hint: "[optional-args]" +# disable-model-invocation: false +# model: sonnet +# context: fork +# agent: Explore +--- +``` + +--- + +## Auto-Triggering Tips + +Skill descriptions compete for a ~16KB context budget. With many skills: + +- Keep descriptions under 2 sentences +- Front-load the most important trigger phrases +- Combine related skills if their descriptions overlap significantly +- Use `disable-model-invocation: true` for skills that should only run manually (deploys, destructive operations) + +If a skill isn't auto-triggering reliably, make the description more specific rather than longer. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..7caba0b --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,90 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Stacky is a Mastodon-compatible social media client built with Next.js. Users browse posts from a Mastodon instance, organize them into "stacks" (categorized discussion threads), and interact via favorites, bookmarks, and annotations. + +## Commands + +```bash +pnpm install # Install dependencies +pnpm dev # Start dev server at localhost:3000 +pnpm build # Production build +pnpm lint # ESLint via next lint +``` + +No test framework is configured. + +## Tech Stack + +- **Framework**: Next.js 14 (App Router) with TypeScript +- **Runtime**: Node.js 22.x +- **Package manager**: pnpm +- **UI library**: Mantine v7 (AppShell, components, hooks, notifications) +- **HTTP client**: axios +- **Icons**: @tabler/icons-react, lucide-react +- **Styling**: CSS Modules + PostCSS with Mantine preset +- **Backend**: Mastodon-compatible API at `https://beta.stacky.social:3002` + +## Architecture + +### Routing & Layout + +The app uses Next.js App Router with a **route group** `(shell)` that wraps all authenticated pages in a three-panel layout (`Shell.tsx`): +- **Left navbar**: `NavBar/Navbar` — navigation links +- **Center main**: page content +- **Right aside**: `@aside` parallel route slot — shows related stacks for the active post + +The landing page (`/`) handles Mastodon OAuth instance selection. `/callback` completes the OAuth flow and stores tokens in localStorage. + +### Parallel Routes + +`src/app/(shell)/@aside/` is a Next.js parallel route that renders the aside panel independently. Each route under `(shell)` has a corresponding `@aside` directory that controls what appears in the right panel. + +### State Management + +- **RelatedStacksContext** (`related-stacks-context.tsx`): Shared context in the shell layout. Manages which post's related stacks are shown in the aside panel. Provides toggle behavior — clicking the same post hides its stacks. +- **localStorage**: `accessToken`, `currentUser` (JSON), `authCode` +- **sessionStorage**: `scrollY:{path}` for scroll restoration, `previousPath` for back navigation + +### Post List Caching (PostList.tsx) + +`PostList` uses a module-level `Map` cache (not React state) that survives component remounts during SPA navigation: +- 5-minute TTL, LRU eviction at 20 entries +- Stale-While-Revalidate: serves cached posts instantly, revalidates in background +- Scroll position saved to sessionStorage on navigation, restored on return +- Stack data loaded in batches of 2 concurrent requests per post + +### API Integration + +All API calls go to `https://beta.stacky.social:3002`. Auth is via OAuth Bearer token from localStorage. Key patterns: +- `mastoActions.ts`: Mastodon interaction helpers (favorite, bookmark, boost) +- Stack-specific endpoints: `/stacks/{postId}/related`, `/api/stacks/{stackId}/questions` +- Standard Mastodon endpoints: `/api/v1/statuses/`, `/api/v1/timelines/`, etc. + +### Environment Variables + +Required in `.env.local`: +``` +NEXT_PUBLIC_MASTODON_OAUTH_CLIENT_ID=... +NEXT_PUBLIC_MASTODON_OAUTH_CLIENT_SECRET=... +NEXT_PUBLIC_MODE=development +``` + +## Key Directories + +- `src/app/(shell)/` — All authenticated routes and the shell layout +- `src/app/(shell)/@aside/` — Parallel route for right sidebar content +- `src/components/` — Reusable components (Posts/, Header/, NavBar/, SubmitPost/, etc.) +- `src/utils/` — Helpers (mastoActions.ts, useAccessToken.ts, emojiMapping.ts) +- `src/types/PostType.tsx` — Core TypeScript interfaces (PostType, ReplyType, PreviewCardType) +- `src/app/FakeData/` — Mock data for development + +## Git Conventions + +- **Branches**: `//issue--` (e.g., `asmith/enhancement/issue-5-user-auth`) +- **Commits**: Imperative mood, reference issue number: `Add login form (#5)`. No co-author signatures. +- **PRs**: Include `Closes #` in body. Keep under ~400 changed lines. Merge commits only — never squash or rebase. +- Never push directly to the main branch. diff --git a/src/components/Posts/Post.tsx b/src/components/Posts/Post.tsx index c1d1544..aa52390 100644 --- a/src/components/Posts/Post.tsx +++ b/src/components/Posts/Post.tsx @@ -2,7 +2,7 @@ import React, { useState, useRef, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import { Text, Avatar, Group, Paper, UnstyledButton, Divider, Anchor } from '@mantine/core'; import { notifications } from '@mantine/notifications'; -import { IconHeart, IconBookmark, IconNote, IconMessageCircle, IconHeartFilled, IconBookmarkFilled, IconLink } from '@tabler/icons-react'; +import { IconHeart, IconBookmark, IconNote, IconMessageCircle, IconHeartFilled, IconBookmarkFilled } from '@tabler/icons-react'; import { format, formatDistanceToNow } from 'date-fns'; import StackCount from '../StackCount'; import axios from 'axios'; @@ -17,23 +17,13 @@ const MastodonInstanceUrl = 'https://beta.stacky.social'; interface CleanedPost { html: string; publishedDate: string | null; - articleUrl: string | null; } -/** Strip external URLs and extract "Published" date from post HTML. - * Only strips URLs when a preview card exists to preserve legitimate links in conversational posts. */ -function cleanPostHtml(html: string, card: PreviewCard | null | undefined): CleanedPost { +/** Extract "Published" date from post HTML and clean up empty tags. */ +function cleanPostHtml(html: string): CleanedPost { let cleaned = html; let publishedDate: string | null = null; - if (card) { - // Remove all tags linking to external URLs (entire tag + contents) - cleaned = cleaned.replace(/]*href=["']https?:\/\/[^"']+["'][^>]*>[\s\S]*?<\/a>/gi, ''); - - // Remove bare URLs in text (not inside tags) - cleaned = cleaned.replace(/https?:\/\/[^\s<]+/g, ''); - } - // Extract "Published: DATE" and remove from text cleaned = cleaned.replace(/Published:\s*(\d{4}-\d{2}-\d{2}T[\d:.]+Z?)/g, (_match, iso) => { try { @@ -54,7 +44,7 @@ function cleanPostHtml(html: string, card: PreviewCard | null | undefined): Clea // Collapse leftover empty

tags cleaned = cleaned.replace(/

\s*<\/p>/g, ''); - return { html: cleaned, publishedDate, articleUrl: card?.url ?? null }; + return { html: cleaned, publishedDate }; } @@ -119,7 +109,7 @@ export default function Post({ const [previewCards, setPreviewCards] = useState(initialCard ? [initialCard] : []); const [tempRelatedStacks, setTempRelatedStacks] = useState(relatedStacks); - const { html: displayText, publishedDate, articleUrl } = cleanPostHtml(text, previewCards[0]); + const { html: displayText, publishedDate } = cleanPostHtml(text); const [isOverflowing, setIsOverflowing] = useState(false); const textRef = useRef(null); @@ -276,11 +266,6 @@ export default function Post({ setAnnotationModalOpen(true); }; - const handleOpenInNewTab = () => { - const url = articleUrl || `${window.location.origin}/posts/${id}`; - window.open(url, '_blank'); - }; - const handleStackCountClick = async () => { setIsExpanded(true); const position = paperRef.current ? paperRef.current.getBoundingClientRect() : { top: 0, height: 0 }; @@ -352,8 +337,12 @@ export default function Post({ }; useEffect(() => { - const links = document.querySelectorAll('.post-content a'); + const container = textRef.current; + if (!container) return; + const links = container.querySelectorAll('a'); links.forEach(link => { + (link as HTMLElement).style.color = '#5a71a8'; + (link as HTMLElement).style.textDecoration = 'underline'; link.addEventListener('click', handleLinkClick as EventListener); }); return () => { @@ -361,7 +350,7 @@ export default function Post({ link.removeEventListener('click', handleLinkClick as EventListener); }); }; - }, [text]); + }, [text, displayText]); return (

@@ -544,11 +533,6 @@ export default function Post({ ariaLabel="Annotate" onClick={handleAnnotation} /> - } - ariaLabel="Open in new tab" - onClick={handleOpenInNewTab} - />