MODULES / ENGAGEMENT

Feedback & Support

Free · MITv0.1.1

A public feedback board with votes and comments, private support tickets with internal notes, and an embeddable feedback widget, all inside your app.

terminalnpx ajolla add feedback

What you get

  • Public board at /feedback: ideas, bugs and questions with votes and threaded comments
  • Sort by top, new or trending; filter by status and category; search
  • Support tickets at /support with numbered threads and staff-only internal notes
  • Staff inbox: unassigned, mine and all, with status, priority and assignee
  • Embeddable feedback widget that captures the page URL (optional anonymous mode)
  • Moderation: status workflow, pinning, merging duplicates (votes move with them)
  • Email/in-app notifications for replies and status changes via Notifications

Routes it adds

/admin/feedback/admin/feedback/posts/admin/feedback/posts/[postId]/admin/feedback/tickets/admin/feedback/tickets/[ticketId]/feedback/feedback/[postId]/support/support/[ticketId]

Plus 1 Supabase migration with row-level security, and an AGENTS.md your coding agent reads after install.

Installs with

Kernel (added automatically).

Configuration

NEXT_PUBLIC_SITE_URL
Absolute URL of your app (e.g. https://example.com); used for links in notifications.
FEEDBACK_SUPPORT_EMAIL
Team inbox notified about new support tickets.
FEEDBACK_ALLOW_ANONYMOUS
Set to "true" to let signed-out visitors send widget feedback (with an email address). Off by default.
FEEDBACK_IP_SALT
Random secret mixed into the IP hashes used to rate-limit anonymous feedback. Falls back to one derived from SUPABASE_SECRET_KEY.

Events

Other modules (and your own code) can react to these:

feedback.post_createdfeedback.post_status_changedfeedback.votedfeedback.ticket_createdfeedback.ticket_repliedfeedback.ticket_status_changednotifications.requested

Use it from any stack

Ajolla Hosted runs the same code as a multi-tenant API. Create an app in your dashboard, enable Feedback & Support, and call /api/m/feedback/v1 with your app key.

OpenAPI document — feed it to Ajolla to generate SDKs in 7 languages and an MCP server.

AGENTS.md

Read the instructions installed with this module
markdown# Ajolla Feedback & Support

This module adds a public **feedback board** (ideas, bugs and questions, with votes and threaded comments), private **support tickets** (numbered threads with staff-only internal notes), and an embeddable **feedback widget**. "Staff" means users with the `feedback.manage` permission.

## What was installed

**Routes**
- `/feedback`: the board. It sorts by top, trending (votes in the last 14 days) or new, and filters by status and type. Search matches titles. Signed-in users post and vote; anyone can read.
- `/feedback/[postId]`: a post with its comments (one level of replies). Authors can edit or delete their post while it's still open.
- `/support`, `/support/[ticketId]`: a signed-in user's tickets. They can open one, read the thread (internal notes are never shown to them) and reply until it's closed.
- `/admin/feedback`: the staff home.
  - **Board triage:** posts by status, including private widget posts.
  - **Support inbox:** filter by unassigned, mine or all, plus status.
- `/admin/feedback/posts/[postId]`: change status, visibility and pinning, answer as the team, merge a duplicate (its votes move and are deduplicated), or delete.
- `/admin/feedback/tickets/[ticketId]`: the full thread with notes. Reply, or add an internal note; change status, priority and assignee.

**Components** (`components/ajolla/feedback/`)
- `<FeedbackWidget />`: a floating button that opens a native `<dialog>` form on any page. Put it once in your root layout. It sends a private post (only staff see it) with the page URL.
  - Signed-out visitors get a sign-in link.
  - With `FEEDBACK_ALLOW_ANONYMOUS=true`, signed-out visitors can submit with an email instead.
- `VoteButton`, the forms in `post-forms.tsx`, and the badges in `status-badge.tsx` are the building blocks.

**Code**
- `lib/ajolla/feedback/core.ts`: all logic, framework-free.
- `server.ts`: the acting user, notification links, IP hashing and event emission.
- `constants.ts`: values shared with client code.
- `lib/ajolla/admin/feedback.ts`: the admin page registration.

**Tables** (`ajolla_feedback_`)
- `posts`: `vote_count` and `comment_count` are kept by triggers; `merged_into` points at the post a duplicate was merged into.
- `votes`: unique `(post_id, user_id)`.
- `comments`: `parent_comment_id` holds one level of replies; `is_staff` marks team comments.
- `tickets`: `number` comes from an identity column starting at 1001.
- `ticket_messages`: `is_internal` notes are staff only.

**RLS**
- **Everyone, including anonymous visitors:** reads public posts and their comments.
- **Signed-in users:**
  - Post, vote and comment as themselves.
  - Edit or delete their own posts while open. A guard trigger blocks them from changing status, counters, pinning or other moderation fields.
  - Read their own private posts, their own tickets, and the non-internal messages on them.
  - Reply to their own tickets unless the ticket is closed.
- **Staff:** everything.

**Events**
- `feedback.post_created`, `feedback.post_status_changed`, `feedback.voted`.
- `feedback.ticket_created`, `feedback.ticket_replied`, `feedback.ticket_status_changed`.
- `notifications.requested`, sent when:
  - staff reply to a ticket → to the customer;
  - a ticket's status changes → to the customer;
  - a post's status changes → to the author and voters, at most 500 people;
  - staff comment on a post → to the post's author;
  - a ticket is opened → to `FEEDBACK_SUPPORT_EMAIL`.

  Install Notifications to deliver them; without it nothing breaks.

**Env** (all optional)
- `NEXT_PUBLIC_SITE_URL`: makes links in notifications absolute.
- `FEEDBACK_SUPPORT_EMAIL`: your team inbox for new tickets.
- `FEEDBACK_ALLOW_ANONYMOUS`: lets signed-out visitors use the widget.
- `FEEDBACK_IP_SALT`: salts the IP hashes used for the anonymous rate limit.

## Setup
1. Apply the migration with `supabase db push`.
2. Add `<FeedbackWidget />` to your root layout, and link to `/feedback` and `/support` from your navigation.
3. Optionally set `FEEDBACK_SUPPORT_EMAIL` and `NEXT_PUBLIC_SITE_URL`, then install Notifications to send email.

## Anonymous widget feedback
This is off by default. With `FEEDBACK_ALLOW_ANONYMOUS=true`, the widget action:
1. Checks a hidden honeypot field.
2. Validates the input, including requiring an email address.
3. Allows 5 messages per 10 minutes per client IP. IPs are stored only as salted sha256 hashes.
4. Inserts the post with the secret-key client, because visitors have no insert rights.

Anonymous posts are always private. For more protection, put Vercel Firewall rate limiting or a CAPTCHA in front of the widget.

## Customising
- **Status names:** `STATUS_LABELS` in `core.ts`.
- **Trending window:** `TRENDING_WINDOW_DAYS` in `core.ts`.
- **Anonymous rate limit:** `ANONYMOUS_RATE_LIMIT` in `core.ts`.
- **Notification wording:** in `moderatePost`, `addComment`, `createTicket`, `replyToTicket` and `updateTicket`.
- **Styles:** `components/ajolla/feedback/feedback.module.css`, plus the kit's `--aj-*` variables.