From d45523e16a1b00ad4ddbd477b4400527428c72ae Mon Sep 17 00:00:00 2001 From: Alex Garrett-Smith Date: Wed, 8 Jul 2026 09:07:19 +0100 Subject: [PATCH 1/4] Add initial project documentation Create one docs page per feature under /docs, covering authentication, account settings, feedback, voting, reactions, comments, notifications, the internal dashboard, public pages, and the changelog workflow. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/README.md | 25 +++++++++++++++++++ docs/account-settings.md | 25 +++++++++++++++++++ docs/authentication.md | 32 ++++++++++++++++++++++++ docs/changelog.md | 29 ++++++++++++++++++++++ docs/comments.md | 23 ++++++++++++++++++ docs/feedback.md | 35 ++++++++++++++++++++++++++ docs/internal-dashboard.md | 50 ++++++++++++++++++++++++++++++++++++++ docs/notifications.md | 35 ++++++++++++++++++++++++++ docs/public-pages.md | 17 +++++++++++++ docs/reactions.md | 31 +++++++++++++++++++++++ docs/voting.md | 18 ++++++++++++++ 11 files changed, 320 insertions(+) create mode 100644 docs/README.md create mode 100644 docs/account-settings.md create mode 100644 docs/authentication.md create mode 100644 docs/changelog.md create mode 100644 docs/comments.md create mode 100644 docs/feedback.md create mode 100644 docs/internal-dashboard.md create mode 100644 docs/notifications.md create mode 100644 docs/public-pages.md create mode 100644 docs/reactions.md create mode 100644 docs/voting.md diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..6bee9e7 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,25 @@ +# FeedbackPit Documentation + +FeedbackPit is a suggestion-tracking application where users submit feedback ("ideas"), vote and react to them, discuss them in comments, and follow their progress as the team moves them through a status pipeline. + +These pages describe each user-facing feature and how it works, grounded in the application code. + +## Features + +- [Authentication](authentication.md) — registration, login, and password resets. +- [Account settings](account-settings.md) — updating profile details and password. +- [Feedback (ideas)](feedback.md) — submitting, viewing, editing, and deleting feedback. +- [Voting](voting.md) — upvoting ideas. +- [Reactions](reactions.md) — emoji reactions on ideas. +- [Comments](comments.md) — public discussion on ideas. +- [Subscriptions & email notifications](notifications.md) — following ideas and receiving updates. +- [Internal team dashboard](internal-dashboard.md) — team-only triage, status changes, and internal notes. +- [Public pages](public-pages.md) — landing page, about page, and the shared dashboard. +- [Changelog & release notes](changelog.md) — how release notes are generated. + +## Roles + +There are two kinds of users: + +- **Members** — any registered user. They can submit feedback, vote, react, and comment. +- **Team members** — users with `is_team_member` set to `true`. In addition to everything a member can do, they have access to the internal dashboard where they manage status and post internal notes. See [Internal team dashboard](internal-dashboard.md). diff --git a/docs/account-settings.md b/docs/account-settings.md new file mode 100644 index 0000000..961a4f5 --- /dev/null +++ b/docs/account-settings.md @@ -0,0 +1,25 @@ +# Account settings + +Signed-in users manage their account from the `Account/Settings` page. All account routes are behind the `auth` middleware. + +## Profile information + +`PUT /account/settings` (`account.settings.update`) updates the user's profile. It delegates to the Fortify action `UpdateUserProfileInformation`, passing only `first_name`, `last_name`, and `email`. + +Validation: + +- `first_name` — required, string, max 255. +- `last_name` — required, string, max 255. +- `email` — required, valid email, max 255, unique (ignoring the current user), and not a disposable address. + +On success the user is redirected back to the settings page with the flash message "Your changes have been saved." + +## Password + +`PUT /account/password` (`account.password.update`) changes the password via the Fortify action `UpdateUserPassword`, passing `current_password`, `password`, and `password_confirmation`. The current password must be correct and the new password must satisfy the shared password rules. + +On success the user is redirected back with "Your password has been updated." + +## Notification preferences + +The account area also includes a notification-preferences page for managing which ideas the user follows. That is documented separately in [Subscriptions & email notifications](notifications.md). diff --git a/docs/authentication.md b/docs/authentication.md new file mode 100644 index 0000000..a7f72cb --- /dev/null +++ b/docs/authentication.md @@ -0,0 +1,32 @@ +# Authentication + +Authentication is powered by [Laravel Fortify](https://laravel.com/docs/fortify). The enabled features are **registration** and **password resets** (`config/fortify.php`). Login is always available; email verification is not enabled. + +## Registration + +New users register through the `Auth/Register` page. Accounts are created by `App\Actions\Fortify\CreateNewUser`, which validates: + +- `first_name` — required, string, max 255. +- `last_name` — required, string, max 255. +- `email` — required, a valid email, max 255, must be unique, and must **not** be a disposable/throwaway address (`indisposable` rule). +- `password` — must satisfy the shared password rules (see `PasswordValidationRules`). + +The password is hashed automatically via the model's `hashed` cast. A user has a computed `name` attribute (`first_name` + `last_name`) and an auto-generated avatar from `ui-avatars.com`. + +Newly registered users are ordinary members. Team access (`is_team_member`) is not granted through registration — see [Internal team dashboard](internal-dashboard.md). + +## Login + +Login uses the `Auth/Login` page and Fortify's standard login flow. Routes such as `login` are provided by Fortify (they are referenced by name, e.g. the team middleware redirects unauthenticated users to `route('login')`). + +## Password reset + +Users who forget their password use the `Auth/ForgotPassword` page to request a reset link and the `Auth/ResetPassword` page to set a new one. The reset itself is handled by `App\Actions\Fortify\ResetUserPassword`. + +## Disposable email protection + +Both registration and profile email changes reject disposable email domains via the `indisposable` validation rule. The domain list is refreshed weekly by the scheduled `disposable:update` command (`routes/console.php`). + +## Shared auth state + +The authenticated user is shared with every Inertia page as `auth.user` (via `HandleInertiaRequests`), serialized through `UserResource`. The user's own email address is only exposed to themselves; other users never see it. diff --git a/docs/changelog.md b/docs/changelog.md new file mode 100644 index 0000000..c8773e6 --- /dev/null +++ b/docs/changelog.md @@ -0,0 +1,29 @@ +# Changelog & release notes + +FeedbackPit generates both its `CHANGELOG.md` and its GitHub release notes automatically when a release is published, driven by the `.github/workflows/changelog.yml` workflow. + +## Trigger + +The workflow runs on published GitHub releases. Because the Claude Code action does not support the `release` event directly, a published release simply **re-dispatches** the workflow as a `workflow_dispatch` run, passing the release tag. It can also be run manually via `workflow_dispatch` with a `tag` input. + +## Collecting the changes + +The workflow diffs the new tag against the previous tag (or, for the first release, against the empty tree so every file counts as added). It gathers three things for that range: + +- The commit subjects (a rough hint only). +- A diff stat of files changed. +- The full code diff, capped at ~90 KB. + +Generated and vendored files are excluded from the diff so it reflects real source changes: `CHANGELOG.md`, `*.lock`, `package-lock.json`, `composer.lock`, and the generated `resources/js/routes/**` and `resources/js/actions/**` output. + +## Two generated outputs + +The code diff — not the commit messages — is treated as the source of truth for both outputs. + +1. **`CHANGELOG.md`** — a technical changelog. Claude groups entries under `### Features`, `### Fixes`, and `### Chores`, written as plain past-tense sentences. A new dated section (`## - `) is prepended to the file, and the update is committed to `main` by `github-actions[bot]`. If a section for the tag already exists, the step is skipped. + +2. **GitHub release notes** — user-facing notes written for the people who use the app, in a fun and playful tone, covering only user-visible changes. These are written back to the release description via `gh release edit`. + +Both steps enforce that Claude may only describe capabilities the diff actually shows: removals are never described as additions, and nothing is invented. + +The current changelog lives in [`CHANGELOG.md`](../CHANGELOG.md) at the project root. diff --git a/docs/comments.md b/docs/comments.md new file mode 100644 index 0000000..02895b7 --- /dev/null +++ b/docs/comments.md @@ -0,0 +1,23 @@ +# Comments + +Ideas have a public comment thread where signed-in users can discuss them. Team members also have a separate internal comment thread (see [Internal team dashboard](internal-dashboard.md)). + +## Posting a public comment + +`POST /feedback/{idea}/comments` (`feedback.comments.store`) posts a comment. The body is required and limited to 5000 characters (`StoreCommentRequest`). + +Comment creation is handled by the `PostComment` action, which, in a single transaction: + +1. Creates the comment (with `is_internal = false`) associated with the author and idea. +2. **Subscribes the author to the idea** so they are notified of future activity. +3. If the author is a **team member**, sends an `IdeaCommentPosted` email notification to every other subscriber of the idea. + +Note that email notifications for new comments are only sent when the commenter is a team member — ordinary member comments do not trigger emails. After posting, the user is redirected back with "Comment posted!" + +## Viewing comments + +The public idea page (`Ideas/Show`) loads all comments where `is_internal` is `false`, oldest first, each with its author. Comments are serialized by `CommentResource` (`id`, `body`, `user`, `created_at`). + +## Data model + +Comments belong to a `User` and an `Idea` (`Comment` model). The `is_internal` boolean flag separates public comments from team-only internal notes; the two share the same table and are filtered by this flag everywhere they are displayed. diff --git a/docs/feedback.md b/docs/feedback.md new file mode 100644 index 0000000..561d633 --- /dev/null +++ b/docs/feedback.md @@ -0,0 +1,35 @@ +# Feedback (ideas) + +Feedback items are called **ideas** internally. They are the core content of FeedbackPit: a title, a description, an author, a vote count, a status, and threads of comments and reactions. + +## Submitting feedback + +Signed-in users create feedback from the `Ideas/Create` page. + +- `POST /feedback` (`feedback.store`) validates `title` (required, max 255) and `description` (required, max 5000). +- The idea is created through the author's relationship (`$request->user()->ideas()->create(...)`), so the author is set automatically. +- The user is redirected to the dashboard with "Your feedback has been submitted!" + +New ideas start with the default status defined by the database (see [Internal team dashboard](internal-dashboard.md) for the status pipeline). + +## Viewing feedback + +`GET /feedback/{idea}` (`feedback.show`) renders the `Ideas/Show` page. This route is public — anyone, signed in or not, can view an idea. The page includes: + +- The idea itself (title, description, status, vote count, reactions, latest status update). +- Whether the current user has voted, is subscribed, and can edit/delete it. +- All **public** comments (internal comments are excluded), oldest first. + +## Editing feedback + +`GET /feedback/{idea}/edit` (`feedback.edit`) and `PUT /feedback/{idea}` (`feedback.update`) let the **author** edit their own idea. Authorization is enforced by `IdeaPolicy::update`, which only allows the user who created the idea. Only `title` and `description` can be changed, with the same validation as creation. + +On success the user is redirected to the idea page with "Your feedback has been updated!" + +## Deleting feedback + +`DELETE /feedback/{idea}` (`feedback.destroy`) deletes an idea. Authorization is enforced by `IdeaPolicy::delete` — again, only the author may delete. The user is redirected to the dashboard with "Your feedback has been deleted!" + +## What the frontend receives + +Ideas are serialized by `IdeaResource`, which exposes: `id`, `title`, `description`, `status`, `votes`, `has_voted`, `reactions` (when loaded), `is_subscribed`, `can.update` / `can.delete` (per-user permissions), the author, comment counts, and status-update history. This resource is reused across the dashboard, the idea page, and the internal dashboard. diff --git a/docs/internal-dashboard.md b/docs/internal-dashboard.md new file mode 100644 index 0000000..f3e2f24 --- /dev/null +++ b/docs/internal-dashboard.md @@ -0,0 +1,50 @@ +# Internal team dashboard + +The internal dashboard is a team-only area for triaging feedback, changing status, and keeping private notes. All routes live under the `/internal` prefix and the `internal.` route-name group. + +## Who can access it + +Access is gated by the `auth` and `team` middleware. The `team` middleware (`EnsureTeamMember`): + +- Redirects unauthenticated visitors to the login page. +- Aborts with **403** for signed-in users who are not team members. + +A user is a team member when `is_team_member` is `true`. This flag is not exposed through registration or account settings — it is set out of band (e.g. via seeder or database). + +## Idea list + +`GET /internal` (`internal.ideas.index`) renders `Internal/Ideas/Index` with every idea, newest first, including author, votes, subscribers, and a count of **public** comments. + +## Idea detail + +`GET /internal/ideas/{idea}` (`internal.ideas.show`) renders `Internal/Ideas/Show`. Unlike the public idea page, it loads the idea's full status-update history and separates comments into two threads: + +- **Public comments** (`is_internal = false`). +- **Internal comments / notes** (`is_internal = true`). + +## Status pipeline + +Ideas move through the `IdeaStatus` enum: + +| Value | Label | +| --------------- | ------------ | +| `under_review` | Under Review | +| `planned` | Planned | +| `in_progress` | In Progress | +| `completed` | Completed | +| `declined` | Declined | + +`PATCH /internal/ideas/{idea}/status` (`internal.ideas.status.update`) changes an idea's status. It requires a valid `status` and accepts an optional `message` (max 5000). Behaviour: + +- If the new status equals the current one, nothing changes and a "Status was already set to ..." message is returned. +- Otherwise, in a transaction, an `IdeaStatusUpdate` record is written capturing `from_status`, `to_status`, the optional message, and the team member who made the change; the idea's status is then updated. +- Every subscriber **except** the team member who made the change is emailed an `IdeaStatusChanged` notification. See [Subscriptions & email notifications](notifications.md). + +Each status change is preserved as history, so the idea detail page can show a full timeline of how an idea has progressed. + +## Comments and internal notes + +Team members can post to either thread on an idea: + +- `POST /internal/ideas/{idea}/comments` (`internal.ideas.comments.store`) posts a **public** comment via the same `PostComment` action used on the public site. Because the author is a team member, this also emails other subscribers. +- `POST /internal/ideas/{idea}/notes` (`internal.ideas.notes.store`) posts an **internal** note (`is_internal = true`). Internal notes are only ever shown inside the internal dashboard and never trigger notifications. diff --git a/docs/notifications.md b/docs/notifications.md new file mode 100644 index 0000000..19aa809 --- /dev/null +++ b/docs/notifications.md @@ -0,0 +1,35 @@ +# Subscriptions & email notifications + +Users can follow ("subscribe to") ideas to receive email updates. Subscriptions drive who gets notified when an idea's status changes or a team member comments. + +## How you become subscribed + +You are subscribed to an idea when you: + +- **Vote** on it (see [Voting](voting.md)). +- **Comment** on it (see [Comments](comments.md)). +- **Explicitly subscribe** from the notification-preferences page. + +Subscriptions are stored in the `idea_subscribers` pivot with timestamps (`Idea::subscribers()` / `User::subscribedIdeas()`). + +## Managing preferences + +The `Account/Notifications` page (`account.notifications.edit`) lists the ideas the current user follows, most recently subscribed first, along with each idea's latest status update. + +- `POST /account/notifications/{idea}` (`account.notifications.store`) subscribes the user, with the flash message `Subscribed to "..."`. +- `DELETE /account/notifications/{idea}` (`account.notifications.destroy`) unsubscribes the user, with `Unsubscribed from "..."`. + +Subscribed ideas are serialized by `SubscribedIdeaResource` (`id`, `title`, `status`, `latest_status_update`). + +## Emails + +Two queued email notifications are sent (both use Markdown mail templates under `resources/views/mail/ideas/`): + +- **`IdeaStatusChanged`** — sent when a team member changes an idea's status. Delivered to every subscriber **except** the team member who made the change. Subject: `Status update on ""`. +- **`IdeaCommentPosted`** — sent when a **team member** posts a public comment. Delivered to every other subscriber. Subject: `New comment on ""`. + +Both emails include a link to the idea and a **signed, one-click unsubscribe link**. + +## One-click unsubscribe + +`GET /feedback/{idea}/unsubscribe/{user}` (`feedback.unsubscribe`) is protected by Laravel's `signed` middleware, so it can only be reached through the signed URL embedded in a notification email. Visiting it removes that user's subscription to the idea and shows the `Ideas/Unsubscribed` confirmation page. No login is required. diff --git a/docs/public-pages.md b/docs/public-pages.md new file mode 100644 index 0000000..5adc74d --- /dev/null +++ b/docs/public-pages.md @@ -0,0 +1,17 @@ +# Public pages + +These pages are reachable without signing in. + +## Landing page + +`GET /` (`landing`) renders the `Landing` page — the marketing/home page shown to visitors. + +## About page + +`GET /about` (`about`) renders the static `About` page. + +## Dashboard + +`GET /dashboard` (`dashboard`) renders the `Dashboard` page, listing every idea newest first with its author, votes, and subscriber data (serialized by `IdeaResource`). + +The route is not behind auth middleware, so the dashboard is viewable by anyone. Signed-in users additionally see per-idea state such as whether they have voted or can edit each idea. Submitting, editing, or deleting feedback still requires signing in — see [Feedback (ideas)](feedback.md). diff --git a/docs/reactions.md b/docs/reactions.md new file mode 100644 index 0000000..b1b11ef --- /dev/null +++ b/docs/reactions.md @@ -0,0 +1,31 @@ +# Reactions + +Signed-in users can add emoji reactions to an idea, in addition to voting. + +## Available emoji + +Reactions are limited to a fixed set defined on the `Reaction` model: + +``` +👍 ❤️ 🎉 🚀 👀 +``` + +## How it works + +`POST /feedback/{idea}/reactions` (`feedback.react`) toggles a single emoji reaction for the current user: + +- The submitted `emoji` is validated to be one of the allowed set (`StoreReactionRequest`). +- If the user has already reacted with that emoji, the reaction is removed. +- Otherwise a new reaction is created and associated with the user and idea. + +Each user can react once per emoji, but can use several different emoji on the same idea. After reacting, the user is redirected back. + +## What the frontend receives + +When the idea's `reactions` relation is loaded, `IdeaResource` returns one entry per allowed emoji with: + +- `emoji` — the emoji character. +- `count` — how many users have used it. +- `reacted` — whether the current user has used it. + +This lets the idea page render every available reaction with its running total and highlight the ones the current user has chosen. diff --git a/docs/voting.md b/docs/voting.md new file mode 100644 index 0000000..25ef604 --- /dev/null +++ b/docs/voting.md @@ -0,0 +1,18 @@ +# Voting + +Signed-in users can upvote ideas to signal support. + +## How it works + +`POST /feedback/{idea}/vote` (`feedback.vote`) toggles the current user's vote on an idea: + +- If the user **has not** voted, their vote is added, the idea's cached `votes` count is incremented, and they are **automatically subscribed** to the idea (so they receive updates — see [Subscriptions & email notifications](notifications.md)). +- If the user **has** voted, their vote is removed and the `votes` count is decremented. Removing a vote does **not** unsubscribe them. + +The whole toggle runs inside a database transaction with a row lock (`lockForUpdate`) so concurrent votes can't corrupt the count. After voting, the user is redirected back to the page they came from. + +## Data model + +Votes are stored in the `idea_vote` pivot table linking users and ideas (`Idea::voters()` / `User::votedIdeas()`). The `votes` column on the idea is a denormalized counter kept in sync with the pivot. + +`IdeaResource` exposes `votes` (the count) and `has_voted` (whether the current user has voted) so the frontend can render vote buttons and totals. From ce360b984ef0fcdf9d0fcde1186695ffb131bb55 Mon Sep 17 00:00:00 2001 From: Alex Garrett-Smith Date: Wed, 8 Jul 2026 09:17:32 +0100 Subject: [PATCH 2/4] Add docs-writer pre-commit hook and track the subagent Shared git hook (.githooks, enabled via core.hooksPath) runs the docs-writer subagent headlessly on the staged diff at commit time and auto-stages any /docs updates. Un-ignore .claude/agents so the subagent is version-controlled while local .claude settings stay ignored. Co-Authored-By: Claude Opus 4.8 (1M context) --- .claude/agents/docs-writer.md | 51 +++++++++++++++++++++++++++++++++++ .githooks/README.md | 24 +++++++++++++++++ .githooks/pre-commit | 51 +++++++++++++++++++++++++++++++++++ .gitignore | 3 ++- 4 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 .claude/agents/docs-writer.md create mode 100644 .githooks/README.md create mode 100755 .githooks/pre-commit diff --git a/.claude/agents/docs-writer.md b/.claude/agents/docs-writer.md new file mode 100644 index 0000000..960c755 --- /dev/null +++ b/.claude/agents/docs-writer.md @@ -0,0 +1,51 @@ +--- +name: docs-writer +description: Maintains the project documentation in /docs. On a first run (no /docs folder) it reads the app and creates one page per feature. On subsequent runs it works from the staged git diff, updating only the docs pages affected by the change and adding a new page when a feature is brand new. Use this agent whenever documentation needs creating or updating. +tools: Read, Write, Edit, Glob, Grep, Bash +--- + +You are the documentation writer for this project. Your one and only job is to keep the docs in the `/docs` directory accurate. + +# Hard boundaries (never violate these) + +- You may **only ever create or edit files inside `/docs`**. Never edit, create, move, or delete any file outside `/docs` — not app code, not config, not tests, nothing. You may *read* app code and run read-only git/shell commands, but every write must land in `/docs`. +- **Never rewrite unrelated docs.** Only touch the pages that the current change actually affects. If a docs page is unrelated to what changed, leave it exactly as it is. Do not "improve", reformat, or re-flow pages you weren't asked to touch. +- Never run commands that modify the working tree, the index, or app code (no `git add`, `git commit`, `git checkout`, no code formatters, no build steps). Read-only git commands only. + +# Deciding which mode you're in + +First, check whether the `/docs` directory exists (e.g. `ls docs` or a Glob for `docs/**`). + +- **If `/docs` does NOT exist → First-run mode.** +- **If `/docs` already exists → Incremental mode.** + +## First-run mode (no /docs yet) + +1. Read the application to understand what it does: routes, controllers, models, key Vue pages/components, and any existing README. Identify the distinct user-facing **features**. +2. Create the `/docs` folder. +3. Write **one page per feature** (e.g. `docs/authentication.md`, `docs/feedback.md`, `docs/changelog.md`). Each page should describe what the feature does and how it works from a user/maintainer perspective — grounded in the actual code you read, not invented. +4. Keep pages focused and self-explanatory. A short `docs/README.md` index linking the feature pages is welcome. + +## Incremental mode (/docs already exists) + +Work strictly from the **staged diff** — the changes that are staged for commit: + +1. Get the staged diff with read-only git, e.g.: + - `git diff --cached --stat` to see which files changed + - `git diff --cached` to read the actual changes +2. Work out which existing docs pages correspond to the changed code. +3. **Update only those affected pages** so they match the new behaviour. Make the smallest edits that make the docs correct. +4. **If the change introduces a brand-new feature** that has no existing page, add a new page for it (and add it to the docs index if one exists). +5. Leave every other docs page untouched. + +If the staged diff is empty, report that there is nothing staged to document and stop — do not invent changes. + +# Style + +- Match the tone and structure of any existing docs pages. +- Write clearly and concisely for a maintainer/user audience. +- Ground every statement in the actual code. Don't document behaviour you haven't verified in the source. + +# When you finish + +Report which docs pages you created or edited and, in one line each, why. If you deliberately left pages untouched, you don't need to list them. diff --git a/.githooks/README.md b/.githooks/README.md new file mode 100644 index 0000000..8b49ae2 --- /dev/null +++ b/.githooks/README.md @@ -0,0 +1,24 @@ +# Git hooks + +Shared, version-controlled git hooks for this repo. + +## One-time setup (per clone) + +```sh +git config core.hooksPath .githooks +``` + +That's it — git will now run the hooks in this directory instead of `.git/hooks`. + +## Hooks + +### `pre-commit` + +On every commit, runs the **docs-writer** subagent (headless `claude -p`) against +the staged diff and auto-stages any `/docs` pages it updates, so documentation +stays in lockstep with the code in the same commit. + +- Best-effort: if the `claude` CLI is missing or the run fails, the commit still + proceeds — the hook never blocks a commit. +- Skips when nothing is staged, or when the only staged changes are under `docs/`. +- To commit without it once: `git commit --no-verify`. diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..e62d6a8 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# +# Keeps /docs in sync with the code on every commit. +# +# Runs the docs-writer subagent (via `claude -p`, headless) against the +# staged diff, then stages any docs it updates so they land in this commit. +# +# This is best-effort: if Claude is unavailable or errors, the commit still +# proceeds. It never blocks a commit. +# +set -euo pipefail + +# Prevent re-entry if anything spawned during the hook makes its own commit. +if [ -n "${FEEDBACKPIT_DOCS_HOOK_RUNNING:-}" ]; then + exit 0 +fi + +# Nothing we can do without the CLI — skip quietly. +if ! command -v claude >/dev/null 2>&1; then + echo "docs-hook: 'claude' CLI not found — skipping docs update." >&2 + exit 0 +fi + +# The docs-writer works from the staged diff, so look at what's staged. +staged="$(git diff --cached --name-only --diff-filter=ACMR)" +if [ -z "$staged" ]; then + exit 0 +fi + +# If the only staged changes are docs themselves, there's nothing new to document. +if ! printf '%s\n' "$staged" | grep -qvE '^docs/'; then + exit 0 +fi + +echo "docs-hook: updating /docs from staged changes via docs-writer…" >&2 + +if ! FEEDBACKPIT_DOCS_HOOK_RUNNING=1 claude -p \ + "Use the docs-writer subagent to update the project documentation based on the currently staged git diff. Work only from the staged diff as the source of truth. Update only the docs pages the change affects; add a new page only if the change introduces a brand-new feature. Make no code changes." \ + --permission-mode acceptEdits \ + --allowedTools "Task Agent Bash Read Write Edit Glob Grep"; then + echo "docs-hook: docs update failed — committing without doc changes." >&2 + exit 0 +fi + +# Stage anything the agent touched under docs/ (modified or newly created). +if [ -n "$(git status --porcelain -- docs/)" ]; then + git add docs/ + echo "docs-hook: staged updated docs into this commit." >&2 +fi + +exit 0 diff --git a/.gitignore b/.gitignore index 7d27924..eafc08c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ -.claude +/.claude/* +!/.claude/agents/ *.log .DS_Store .env From 5e37e032131c8dbc8e98d01f23d590a2ab0a3ae5 Mon Sep 17 00:00:00 2001 From: Alex Garrett-Smith Date: Wed, 8 Jul 2026 09:18:02 +0100 Subject: [PATCH 3/4] Added reaction emoji --- app/Models/Reaction.php | 2 +- docs/reactions.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Models/Reaction.php b/app/Models/Reaction.php index 40cbd43..02d3341 100644 --- a/app/Models/Reaction.php +++ b/app/Models/Reaction.php @@ -16,7 +16,7 @@ class Reaction extends Model * * @var list */ - public const EMOJIS = ['👍', '❤️', '🎉', '🚀', '👀']; + public const EMOJIS = ['👍', '❤️', '🎉', '🚀', '👀', '😂']; protected $fillable = [ 'emoji', diff --git a/docs/reactions.md b/docs/reactions.md index b1b11ef..3398148 100644 --- a/docs/reactions.md +++ b/docs/reactions.md @@ -7,7 +7,7 @@ Signed-in users can add emoji reactions to an idea, in addition to voting. Reactions are limited to a fixed set defined on the `Reaction` model: ``` -👍 ❤️ 🎉 🚀 👀 +👍 ❤️ 🎉 🚀 👀 😂 ``` ## How it works From 26c7c5131414d5c6b1033cb295b7726c6953f1f1 Mon Sep 17 00:00:00 2001 From: Alex Garrett-Smith Date: Wed, 8 Jul 2026 09:19:53 +0100 Subject: [PATCH 4/4] Added fire emoji to reactions --- app/Models/Reaction.php | 2 +- docs/reactions.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Models/Reaction.php b/app/Models/Reaction.php index 02d3341..3b81bdb 100644 --- a/app/Models/Reaction.php +++ b/app/Models/Reaction.php @@ -16,7 +16,7 @@ class Reaction extends Model * * @var list */ - public const EMOJIS = ['👍', '❤️', '🎉', '🚀', '👀', '😂']; + public const EMOJIS = ['👍', '❤️', '🎉', '🚀', '👀', '😂', '🔥']; protected $fillable = [ 'emoji', diff --git a/docs/reactions.md b/docs/reactions.md index 3398148..a748f07 100644 --- a/docs/reactions.md +++ b/docs/reactions.md @@ -7,7 +7,7 @@ Signed-in users can add emoji reactions to an idea, in addition to voting. Reactions are limited to a fixed set defined on the `Reaction` model: ``` -👍 ❤️ 🎉 🚀 👀 😂 +👍 ❤️ 🎉 🚀 👀 😂 🔥 ``` ## How it works