Better Changelog: Settings Page & Public Changelog Spec

Overview

Build the Settings page (multi-tab) and enhance the public changelog view.


1. Settings Page (/dashboard/settings)

Architecture

  • Tab-based navigation (URL-driven: /dashboard/settings/general, /dashboard/settings/team, etc.)
  • Server components for data fetching, client components for forms
  • Role-based access: some tabs owner-only, some admin+

Tabs

1.1 General (/dashboard/settings or /dashboard/settings/general)

Access: Admin+

FieldTypeNotes
Team Nametext inputRequired, max 255 chars
Slugtext inputURL-safe, lowercase, auto-generated from name, editable
Logoimage uploadStore in R2/S3, display in public changelog header
TimezoneselectFor scheduled publishing

Actions:

  • Save Changes button
  • Preview link to public changelog

Validation:

  • Slug: unique across all tenants, URL-safe characters only
  • Logo: max 2MB, jpg/png/svg/webp

1.2 Branding (/dashboard/settings/branding)

Access: Admin+

FieldTypeNotes
Primary Colorcolor pickerHex, used for buttons/links
Secondary Colorcolor pickerOptional
Accent Colorcolor pickerOptional
Custom CSStextareaAdvanced users, max 10KB
Show β€œPowered by”toggleFree tier: forced on. Paid: can hide

Preview: Live preview panel showing how public changelog looks with colors


1.3 Custom Domain (/dashboard/settings/domain)

Access: Owner only

FieldTypeNotes
Custom Domaintext inpute.g., changelog.acme.com
Verification StatusbadgePending / Verified / Failed

Flow:

  1. User enters domain β†’ we generate CNAME record instructions
  2. β€œVerify Domain” button triggers DNS check
  3. Once verified, we configure SSL via Cloudflare/Vercel

Display:

  • Current domain with status badge
  • Instructions for CNAME setup
  • β€œVerify” / β€œRemove” buttons

Notes:

  • Defer actual implementation β€” just build the UI for now
  • Backend domain routing will need middleware changes

1.4 Team (/dashboard/settings/team)

Access: Admin+ (but only owner can change roles or remove admins)

Current Members Table:

ColumnNotes
AvatarFrom user profile
Name/EmailUser info
RoleBadge: owner/admin/editor/viewer
JoinedDate
ActionsChange role (dropdown), Remove (button)

Invite Form:

FieldTypeNotes
Emailtext inputRequired
Roleselectadmin/editor/viewer (not owner)
Send InvitebuttonSends email with invite link

Pending Invites Section:

  • List of pending invites with Resend/Cancel buttons

Role Permissions:

RoleEntriesSettingsTeamBillingDelete Org
Ownerβœ… CRUDβœ… Allβœ… Allβœ…βœ…
Adminβœ… CRUDβœ… Mostβœ… Invite/Remove editors❌❌
Editorβœ… CRUD❌❌❌❌
Viewerβœ… Read❌❌❌❌

1.5 API Keys (/dashboard/settings/api)

Access: Admin+

Existing Keys Table:

ColumnNotes
NameUser-defined
Key Prefixcl_live_xxxx... (masked)
ScopesBadges
CreatedDate
Last UsedDate or β€œNever”
ActionsRevoke button

Create New Key Form:

FieldTypeNotes
Nametext inpute.g., β€œCI/CD Pipeline”
Scopesmulti-selectentries:read, entries:write, etc.
ExpirationselectNever / 30d / 90d / 1yr

On Create:

  • Show full key ONCE in modal (user must copy)
  • Store only hash in DB

Security:

  • Keys prefixed with cl_live_ or cl_test_
  • Hash using SHA-256 before storage
  • Rate limit API endpoints

1.6 Notifications (/dashboard/settings/notifications)

Access: Admin+

Email Settings:

FieldTypeNotes
Send email on publishtoggleMaster switch
From Nametext inpute.g., β€œAcme Updates”
Reply-Toemail inputOptional

Webhook Settings:

FieldTypeNotes
Webhook URLtext inputHTTPS required
Secretauto-generatedFor signature verification
Eventsmulti-selectentry.published, entry.updated, etc.
ActivetoggleEnable/disable

Webhook List:

  • Table of configured webhooks with Edit/Delete/Test buttons

1.7 Danger Zone (/dashboard/settings/danger)

Access: Owner only

Actions:

ActionConfirmationNotes
Export DataNoneDownload all entries as JSON
Transfer OwnershipEmail + PasswordTransfer to another team member
Delete OrganizationType org name + passwordPermanent, cascades all data

UI: Red-bordered section, clear warnings


2. Public Changelog Page (/c/[tenant])

Current State

Basic list of entries with minimal styling.

Enhancements

2.1 Header

  • Tenant logo (if uploaded)
  • Tenant name
  • Optional tagline/description
  • RSS feed button
  • Subscribe button (opens email modal)

2.2 Filter Bar

  • By update type (tabs or pills)
  • By date range (optional)
  • Search (optional, defer)

2.3 Entry Cards

  • Date prominently displayed
  • Type badge with color
  • Title with emoji
  • Summary text
  • β€œRead more” link to full entry

2.4 Subscribe Modal

  • Email input
  • Frequency preference (realtime/daily/weekly)
  • Checkbox for specific audiences/platforms (optional)
  • Double opt-in flow

2.5 Custom Domain Support

  • When accessed via custom domain, style should apply
  • β€œPowered by Changelog” footer (hideable for paid)

2.6 Branding

  • Apply tenant’s brandColors to:
    • Primary buttons/links
    • Header background (subtle)
    • Badge colors
  • Apply customCss if set

3. Database Migrations Needed

Already exists:

  • tenants.brandColors (jsonb)
  • tenants.customDomain, customDomainVerified
  • tenants.logoUrl
  • tenants.settings (jsonb)
  • tenantMembers.role
  • apiKeys table
  • webhooks table
  • subscribers table

Need to add:

  • team_invitations table (if not exists)
CREATE TABLE IF NOT EXISTS team_invitations (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
  email VARCHAR(255) NOT NULL,
  role VARCHAR(50) NOT NULL DEFAULT 'viewer',
  token VARCHAR(255) NOT NULL UNIQUE,
  invited_by UUID REFERENCES users(id),
  expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
  accepted_at TIMESTAMP WITH TIME ZONE,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL
);

4. Implementation Order

Phase 1: Core Settings (Today)

  1. Settings layout with tab navigation
  2. General tab (name, slug, logo upload via URL for now)
  3. Team tab (list members, change roles, remove)
  4. Danger Zone (delete org with confirmation)

Phase 2: Branding & Public Changelog

  1. Branding tab (colors, custom CSS)
  2. Apply branding to /c/[tenant] page
  3. Add filter bar to public changelog
  4. Subscribe modal (basic)

Phase 3: Advanced Features (Later)

  1. API Keys tab (generate, revoke)
  2. Custom Domain tab (UI only, defer routing)
  3. Notifications tab (webhooks, email settings)
  4. Team invitations (email flow)

5. Risk Mitigation

RiskMitigation
Slug change breaks linksWarn user, suggest redirect setup
Logo upload abuseSize limit (2MB), sanitize filenames, consider virus scan
Custom CSS XSSSanitize CSS, no url(), no expression(), no javascript:
API key leakageShow full key only once, store hash only
Accidental org deletionRequire typing org name + password
Domain verification spoofingUse unique verification tokens per tenant

6. Files to Create

apps/web/src/app/dashboard/settings/
β”œβ”€β”€ layout.tsx              # Tab navigation
β”œβ”€β”€ page.tsx                # Redirects to /general
β”œβ”€β”€ general/
β”‚   └── page.tsx            # General settings form
β”œβ”€β”€ branding/
β”‚   └── page.tsx            # Branding settings
β”œβ”€β”€ domain/
β”‚   └── page.tsx            # Custom domain
β”œβ”€β”€ team/
β”‚   └── page.tsx            # Team management
β”œβ”€β”€ api/
β”‚   └── page.tsx            # API keys
β”œβ”€β”€ notifications/
β”‚   └── page.tsx            # Email & webhooks
└── danger/
    └── page.tsx            # Danger zone

apps/web/src/components/settings/
β”œβ”€β”€ general-form.tsx
β”œβ”€β”€ branding-form.tsx
β”œβ”€β”€ team-table.tsx
β”œβ”€β”€ invite-form.tsx
β”œβ”€β”€ api-keys-table.tsx
β”œβ”€β”€ create-api-key-form.tsx
β”œβ”€β”€ webhooks-table.tsx
β”œβ”€β”€ delete-org-dialog.tsx
└── color-picker.tsx

apps/web/src/lib/actions/
β”œβ”€β”€ settings.ts             # Update tenant settings
β”œβ”€β”€ team.ts                 # Manage members, invites
└── api-keys.ts             # CRUD for API keys

7. Approval

Spec reviewed for:

  • Schema compatibility β€” all fields exist or have migration plan
  • Role-based access β€” documented per tab
  • Security risks β€” identified and mitigated
  • Implementation order β€” phased, MVP-first
  • UX β€” tab-based, familiar pattern

Ready to implement Phase 1.