Files
shadow-gunbuilder-ai-proto/docs/superpowers/specs/2026-03-30-changelog-feedback-design.md
T

10 KiB
Raw Blame History

Changelog, Roadmap & Feedback Design Spec

Date: 2026-03-30 Status: Approved


Overview

Add a public /changelog page with three tabs (What's New, What's Next, Give Feedback), auth-gated upvoting on roadmap items, a shared feedback form available as a standalone page and a modal triggered from the nav/footer, and an admin view for reviewing submissions.


Goals

  • Publicly share release notes (changelog) and upcoming work (roadmap)
  • Let authenticated users upvote roadmap items to signal priority
  • Collect structured user feedback (bug / feature / general) with optional email
  • Store feedback in Spring Boot DB, reviewable in the admin panel
  • Add "Send Feedback" entry point to TopNav and homepage footer

Routes

Route Description
/changelog Three-tab page: What's New / What's Next / Give Feedback
/feedback Standalone feedback page — full-width FeedbackForm
/admin/feedback Admin table of feedback submissions

Tab state on /changelog is driven by URL hash (#whats-new, #whats-next, #give-feedback) so tabs are deep-linkable. When no hash is present, the default active tab is What's New. The "Don't see what you need? Give us feedback →" nudge on the roadmap tab links to #give-feedback.

Middleware

/changelog and /feedback are public routes — no authentication required. Both must be added to isPublicPath() in middleware.ts so they remain accessible when LAUNCH_ONLY_ROOT=true. The Next.js proxy routes /api/feedback (POST) and /api/roadmap/votes (GET) require no auth and will pass through middleware as-is since they are not prefixed with /api/admin or matched by isProtectedLaunchPath(). This is intentional and requires no middleware change for those API routes.


Sanity Schema (two new document types)

changelogEntry

Field Type Notes
title string Display title of the release
version string e.g. v0.4
publishedAt datetime Controls ordering and display date
tags array of string Values: feature, fix, improvement
body Portable Text Rich text body

roadmapItem

Field Type Notes
title string Short item title
description text Plain-text description
status string enum planned | in-progress | shipped
order number Manual sort order within each status group

Both use the existing Sanity project (zal102rv, dataset production). GROQ query strings are added to lib/sanity.ts alongside the existing postsQuery. The RSC page must call sanityFetch() from lib/sanityFetch.ts to execute those queries — not client.fetch() from the Sanity client directly. This is the same pattern used by the guides page and is required for correct behaviour in Docker/server environments where CDN DNS resolution fails.

Pages use export const revalidate = 60 ISR, consistent with guides.

The Sanity _id of each roadmapItem serves as the foreign key in Spring Boot's votes table — no additional ID field required. Sanity _id values are stable UUIDs.

GROQ Query Filters

  • changelogEntriesQuery: filter _type == "changelogEntry" && publishedAt <= now(), order by publishedAt desc
  • roadmapItemsQuery: filter _type == "roadmapItem" && status != "shipped", order by order asc within each status group. Shipped items are excluded from the roadmap query — when an item ships, the author changes its status to shipped in Sanity and creates a corresponding changelogEntry. The roadmap tab never displays shipped items.

Spring Boot Backend

New Tables

feedback

Column Type Notes
id UUID, PK
category enum BUG, FEATURE_REQUEST, GENERAL
message text Max 1000 chars, enforced at API level
email varchar, nullable Optional follow-up email
ip_address varchar Captured server-side for spam visibility
user_agent varchar Captured server-side for spam visibility
reviewed boolean, default false Admin marks reviewed
created_at timestamp

Rate limiting on POST /api/v1/feedback is deferred — IP and user agent capture provides enough visibility for manual spam review at this scale.

roadmap_votes

Column Type Notes
user_id UUID, FK → users Part of composite PK
sanity_item_id varchar Part of composite PK; Sanity _id of the roadmap item
created_at timestamp
PRIMARY KEY(user_id, sanity_item_id) Composite PK — enforces one vote per user per item

New API Endpoints (Spring Boot)

Method Path Auth Notes
POST /api/v1/feedback None Creates feedback record; captures IP + user agent server-side
GET /api/v1/roadmap/votes?ids=... Optional Returns vote counts + current user's voted state for a list of Sanity IDs. For unauthenticated callers, returns vote counts with voted: false for all items — never 401.
POST /api/v1/roadmap/{sanityId}/vote Required Toggles vote on/off; returns new count and voted state

Next.js Proxy Routes

All three endpoints are proxied via app/api/ — client code never calls Spring Boot directly, consistent with the existing proxy pattern.

Next.js route Proxies to
POST /api/feedback POST /api/v1/feedback
GET /api/roadmap/votes GET /api/v1/roadmap/votes
POST /api/roadmap/[sanityId]/vote POST /api/v1/roadmap/:id/vote

The Next.js proxy for POST /api/feedback captures x-forwarded-for and user-agent from the incoming request headers and forwards them to Spring Boot — the client form never sends these values.


Frontend Components

New Files

File Type Responsibility
app/(app)/changelog/page.tsx RSC Fetches Sanity changelog + roadmap data via sanityFetch, renders tab shell + passes data as props
components/ChangelogTabs.tsx "use client" Hash-based tab switcher (default: #whats-new), renders all three tab panels
components/RoadmapItem.tsx "use client" Single roadmap item with optimistic upvote toggle
components/FeedbackForm.tsx "use client" Shared form (category + message + email), posts to /api/feedback
components/FeedbackModal.tsx "use client" Backdrop + dialog wrapper around FeedbackForm
app/(app)/feedback/page.tsx RSC Standalone page rendering FeedbackForm full-width
app/admin/feedback/page.tsx RSC Admin table of submissions, sortable + filterable
app/api/feedback/route.ts API route Proxy to Spring Boot; forwards IP + user-agent headers
app/api/roadmap/votes/route.ts API route Proxy to Spring Boot
app/api/roadmap/[sanityId]/vote/route.ts API route Proxy to Spring Boot

Modified Files

File Change
lib/sanity.ts Add changelogEntriesQuery and roadmapItemsQuery
middleware.ts Add /changelog and /feedback to isPublicPath()
components/TopNav.tsx Add "Send Feedback" ghost button; modal open state is local to TopNav via useState
app/page.tsx Add "Send Feedback" link to the homepage's inline footer (homepage-only; this is intentional — TopNav covers all other pages)

FeedbackModal State

FeedbackModal open/close state is managed locally in TopNav with useState. No global context or portal is required — the modal renders inside TopNav's JSX and is positioned fixed via CSS. This is sufficient given "Send Feedback" is the only trigger point in the nav.


UI Design

/changelog Page Header

BATTL BUILDERS            (small caps label)
Updates                   (h1)
What we've shipped and what's coming next.   (subhead)

[ What's New ] [ What's Next ] [ Give Feedback ]   (tab bar, amber underline on active)

What's New Tab

Two-column entry layout: left column is version + date (120px fixed), right column is tags + title + body. Entries separated by bottom border, ordered newest first.

Tag values rendered as small pill badges:

  • feature → amber
  • fix → green
  • improvement → blue

What's Next Tab

Items grouped under "In Progress" and "Planned" headings. Shipped items are never shown here (filtered out in the GROQ query). Each item is a card with:

  • Left: upvote button (chevron up + count) — bordered pill, amber when voted
  • Center: title + description
  • Right: status badge (In Progress = amber, Planned = muted)

Unauthenticated users clicking upvote are prompted to sign in (toast). Voted state is toggled optimistically — rolls back on error.

Footer nudge: "Don't see what you need? Give us feedback →" links to #give-feedback.

Give Feedback Tab & /feedback Page

Full-width form:

  • Category + email in a two-column row
  • Message textarea (full width, 1000 char limit with counter)
  • Submit button bottom-right, privacy note bottom-left

Feedback Modal

Triggered by "Send Feedback" in TopNav and homepage inline footer. Same fields as the tab form. Full-width submit button inside the modal.


Admin View (/admin/feedback)

Table with columns: Date, Category, Message (truncated to 120 characters), Email, Reviewed. Sortable by date. Filterable by category (Bug / Feature / General) and reviewed status. Follows existing admin page patterns.


Feedback Form Fields

Field Required Validation
Category Yes One of: Bug report, Feature request, General feedback
Message Yes 11000 characters
Email No Valid email format if provided

ip_address and user_agent are captured server-side in the Next.js proxy route — never sent from the client form.


SEO Metadata

Both /changelog and /feedback export a metadata object following the existing page pattern:

  • /changelog: title: "What's New — Battl Builders", relevant description
  • /feedback: title: "Send Feedback — Battl Builders", relevant description

Out of Scope

  • Email notifications when feedback is submitted (separate task)
  • Sorting roadmap items by vote count (manual order field only for now)
  • Public display of vote counts on the changelog tab
  • Sanity Studio schema deployment (handled separately via Sanity CLI)
  • Spring Boot entity/migration code (lives in ballistic-builder-spring repo, tracked separately)
  • Rate limiting on the feedback endpoint (deferred; IP capture provides visibility at current scale)