Add CHANGELOG.md and integrate user-facing release notes with technical details into release.sh and /news page. Enhance /news with collapsible technical sections and improve localization.

This commit is contained in:
MartinBraquet
2026-07-27 01:26:14 +02:00
parent 061c30e7f5
commit a091c7ad88
6 changed files with 218 additions and 32 deletions

107
CHANGELOG.md Normal file
View File

@@ -0,0 +1,107 @@
# Changelog
Release notes for Compass. `scripts/release.sh` reads the entry for the version being tagged straight out
of this file and uses it as the GitHub release body (falls back to `gh release --generate-notes` if no
entry matches). That release body is what the [/news](/news) page reads, via the GitHub Releases API — so
an entry here only reaches users once its version is actually tagged and released.
Each entry:
- Starts with `## <version>` (must match `package.json`'s `version` exactly) and ends at the next `## `
heading or a `---` separator line — `scripts/release.sh` parses on that boundary.
- Has two parts, in order:
1. A **user-facing summary** — plain language, no internals. Shown by default on `/news`.
2. A **technical details** section — implementation-level notes. Collapsed behind a "Show technical
details" toggle on `/news`.
- Separates those two parts with an HTML comment, `<!--tech-->`, alone on its own line. That exact marker
is what `web/pages/news.tsx` splits on. An entry without the marker (e.g. old releases before this
convention) just renders as-is on `/news`, with no expandable section — keep that in mind while
backfilling.
To edit an already-published release's notes to match this convention, use
`gh release edit <tag> --notes-file <(sed ...)` or paste manually in the GitHub UI — `scripts/release.sh`
only runs at tag-creation time, so it won't touch releases that already exist.
---
## 1.13.0 — 2026-07-27
### New features
- Voice messages: record, transcribe, and play back audio directly in profiles and chats
- Added a media creator tool for building profile photos/videos in-app
- Added a searchable FAQ page with categories and deep links to individual answers
- Signal is now a supported way to share contact info (phone number or signal.me link)
- Finnish added to the list of profile languages
- Sexual orientation is now its own profile field, with expanded gender options
- Added neurotype and other accessibility fields to profiles
- "Get Notified" button on saved searches — get alerted as soon as a new match appears
- Redesigned notifications: clearer settings and empty states explaining what triggers an alert
### Improvements
- Redesigned the About page with growth stats and clearer help cards
- Redesigned the sidebar for better accessibility and visual hierarchy
- Saved profiles are now called "bookmarks" instead of "stars" throughout the app
- Added a lightbox for viewing a profile's pinned photos/videos
- Nudge to keep your account (instead of deleting it) when matches are scarce
- Customizable share icon and a targeted share sheet for sending profiles to friends
- Hint to help you center your face when uploading a profile picture
- Tidied up filter labels; moved "relationship style", "raised in", and "causes" into clearer sections
- Refined numeric-range and diet filters to handle edge cases better
- Added a world map and demographic breakdowns to the `/stats` page
### Trust & safety
- Added a spam guard that limits new conversations per day and auto-bans abusers
- Banned and disabled profiles no longer appear in search results
- Added limitations for accounts flagged as suspicious
- Added the ability to report a message channel
<!--tech-->
### Backend & API
- `compute-scores`: delete invalid compatibility-score rows instead of nulling them; exclude null scores
when ordering profiles, with test coverage
- `searchUsers`: exclude banned users and disabled profiles at the query level when `excludeUnavailable` is
set, with unit tests
- Hardened SQL across notification settings, private messages, and GeoDB API requests (input sanitization,
stricter limit validation)
- `content_owner_id` made nullable on `reports`; removed the outdated "self update" policy on `profiles`
- Added `getChannelsCount` API endpoint
- Refactored profile column retrieval for better caching
- API version bumped 1.40.0 → 1.50.0 across the range
### Android
- Declared microphone permissions and runtime permission handling for voice recording
- Added a custom icon/color for FCM push notifications
- Added `MEDIA_SOURCE_BASE_URL` to the CD build environment
- Bumped `compileSdkVersion`/`targetSdkVersion` to 36; app version bumped through 1.31.1 (versionCode 133)
### Testing
- Restructured the Playwright E2E suite around a POM/Fixture app-class pattern
- Added a multi-account context manager for tests that need several accounts interacting at once
- Added coverage for messaging, profile hiding/bookmarking, filters, onboarding, compatibility questions,
and Google-account sign-in
- Added DB seeding and account-cleanup helpers for test isolation
### Performance
- Reduced excessive re-renders in the rich-text editor; editor max-height is now computed dynamically
- Added loading skeletons and avoided redundant refetches on the profiles grid
### Tooling & docs
- Added scripts for OG/social-preview card rendering, AI-generated showcase portraits, vote-tally capture,
and demo/tour video generation (Remotion)
- Vote-tally images switched to PNG; dropped the narrow and WebP variants
- Added `.env.local` override support to `run_local.sh`; added dev DB/Firebase reset commands
- Added F-Droid publishing documentation and a React/TypeScript fundamentals guide; added `CLAUDE.md` to
several sub-packages
---
<!-- Backfill older releases above this line, following the same two-section pattern and marker described at the top of this file. -->

View File

@@ -642,10 +642,12 @@
"nav.social": "Sozial",
"nav.vote": "Abstimmung",
"news.failed": "Abrufen der Versionen fehlgeschlagen.",
"news.hide_technical_details": "Technische Details ausblenden",
"news.no_release_notes": "_Keine Versionshinweise vorhanden._",
"news.note": "Hinweis: Leider ist diese Seite nur auf Englisch verfügbar.",
"news.seo.description": "Die neuesten Updates und Funktionen ansehen",
"news.seo.description_general": "Alle Neuigkeiten und Code-Updates für Compass",
"news.show_technical_details": "Technische Details anzeigen",
"news.title": "Neuigkeiten",
"news.view_on_github": "Auf GitHub ansehen",
"notifications.comment.commented": "kommentierte ",

View File

@@ -642,10 +642,12 @@
"nav.social": "Social",
"nav.vote": "Vote",
"news.failed": "Échec de la récupération des versions.",
"news.hide_technical_details": "Masquer les détails techniques",
"news.no_release_notes": "_Aucune note de version fournie._",
"news.note": "Note : Malheureusement, cette page n'est disponible qu'en anglais.",
"news.seo.description": "Voir les dernières mises à jour et fonctionnalités",
"news.seo.description_general": "Toutes les actualités et mises à jour du code pour Compass",
"news.show_technical_details": "Afficher les détails techniques",
"news.title": "Quoi de neuf",
"news.view_on_github": "Voir sur GitHub",
"notifications.comment.commented": "a commenté ",

View File

@@ -1,6 +1,6 @@
{
"name": "compass",
"version": "1.12.0",
"version": "1.13.0",
"private": true,
"workspaces": [
"common",

View File

@@ -15,11 +15,37 @@ if [ -z "$tagged" ]; then
git push origin "$tag"
echo "Tagged release $tag"
gh release create "$tag" \
--repo="$GITHUB_REPOSITORY" \
--title="$tag" \
--generate-notes
echo "Created release"
# Pull this version's entry out of CHANGELOG.md (see the file header for the format: a user-facing
# summary, a `<!--tech-->` marker, then technical details). An entry ends at the next `## ` heading or a
# `---` separator line. Falls back to --generate-notes when the version has no hand-written entry yet, so
# releases without a changelog entry still get created.
notes_file=$(mktemp)
awk -v tag="$tag" '
/^## / {
if (found) exit
if ($0 ~ ("^## " tag "([^0-9.]|$)")) { found=1; next }
next
}
found {
if ($0 == "---") exit
print
}
' CHANGELOG.md | sed -e '/./,$!d' > "$notes_file"
if [ -s "$notes_file" ]; then
gh release create "$tag" \
--repo="$GITHUB_REPOSITORY" \
--title="$tag" \
--notes-file "$notes_file"
echo "Created release from CHANGELOG.md entry"
else
gh release create "$tag" \
--repo="$GITHUB_REPOSITORY" \
--title="$tag" \
--generate-notes
echo "Created release (no CHANGELOG.md entry found for $tag, used --generate-notes)"
fi
rm -f "$notes_file"
# Release to ...

View File

@@ -1,6 +1,8 @@
import {ChevronDownIcon, ChevronUpIcon} from '@heroicons/react/24/solid'
import {githubRepoSlug} from 'common/constants'
import {useEffect, useState} from 'react'
import {Col} from 'web/components/layout/col'
import {Row} from 'web/components/layout/row'
import {CustomLink} from 'web/components/links'
import {CustomMarkdown} from 'web/components/markdown'
import {EnglishOnlyWarning} from 'web/components/news/english-only-warning'
@@ -11,6 +13,22 @@ import {useT} from 'web/lib/locale'
import {getPageData} from 'web/lib/util/page-data'
import {isNativeMobile} from 'web/lib/util/webview'
// Release notes are written with a user-facing summary followed by a technical section, separated by this
// marker on its own line (see CHANGELOG.md). Only the summary renders by default; the technical section is
// tucked behind a toggle. Releases predating this convention have no marker, so they render as-is.
const TECHNICAL_SECTION_MARKER = '<!--tech-->'
function splitReleaseNotes(body: string): {summary: string; technical: string | null} {
const markerIndex = body.indexOf(TECHNICAL_SECTION_MARKER)
if (markerIndex === -1) {
return {summary: body, technical: null}
}
return {
summary: body.slice(0, markerIndex).trim(),
technical: body.slice(markerIndex + TECHNICAL_SECTION_MARKER.length).trim(),
}
}
async function fetchReleases() {
const releases = await fetch(`https://api.github.com/repos/${githubRepoSlug}/releases`)
.then((r) => r.json())
@@ -109,32 +127,7 @@ export default function WhatsNew(props: {releases?: Release[]}) {
<Col className="max-w-3xl mx-auto py-8 px-4 custom-link gap-6">
<EnglishOnlyWarning />
{releases.map((release: Release) => (
<div
key={release.id}
className="bg-canvas-50 border border-canvas-200 rounded-xl p-6 transition-all hover:border-primary-300 hover:shadow-sm"
>
<div className="flex justify-between items-start mb-4">
<h2 className="font-heading text-2xl font-medium text-ink-900 leading-tight">
{release.name || release.tag_name}
</h2>
<span className="text-sm text-ink-300 font-mono whitespace-nowrap ml-4">
{new Date(release.published_at).toISOString().split('T')[0]}
</span>
</div>
<div className="mb-5 prose prose-neutral dark:prose-invert text-ink-900">
<CustomMarkdown>
{formatPullLinks(
release.body || t('news.no_release_notes', '_No release notes provided._'),
)}
</CustomMarkdown>
</div>
<CustomLink
href={release.html_url}
className="inline-flex items-center gap-2 text-primary-600 hover:text-primary-700 text-sm font-medium transition-colors"
>
{t('news.view_on_github', 'View on GitHub')}
</CustomLink>
</div>
<ReleaseCard key={release.id} release={release} />
))}
</Col>
)}
@@ -142,6 +135,62 @@ export default function WhatsNew(props: {releases?: Release[]}) {
)
}
function ReleaseCard(props: {release: Release}) {
const {release} = props
const t = useT()
const [showTechnical, setShowTechnical] = useState(false)
const {summary, technical} = splitReleaseNotes(
formatPullLinks(release.body || t('news.no_release_notes', '_No release notes provided._')),
)
return (
<div className="bg-canvas-50 border border-canvas-200 rounded-xl p-6 transition-all hover:border-primary-300 hover:shadow-sm">
<div className="flex justify-between items-start mb-4">
<h2 className="font-heading text-2xl font-medium text-ink-900 leading-tight">
{release.name || release.tag_name}
</h2>
<span className="text-sm text-ink-300 font-mono whitespace-nowrap ml-4">
{new Date(release.published_at).toISOString().split('T')[0]}
</span>
</div>
<div className="mb-5 prose prose-neutral dark:prose-invert text-ink-900">
<CustomMarkdown>{summary}</CustomMarkdown>
</div>
{technical && (
<div className="mb-5">
<button
onClick={() => setShowTechnical(!showTechnical)}
className="text-primary-500 hover:text-primary-700 z-10 select-none text-sm"
>
<Row className="items-center gap-0.5">
{showTechnical ? (
<ChevronUpIcon className="h-4 w-4" />
) : (
<ChevronDownIcon className="h-4 w-4" />
)}
{showTechnical
? t('news.hide_technical_details', 'Hide technical details')
: t('news.show_technical_details', 'Show technical details')}
</Row>
</button>
{showTechnical && (
<div className="mt-3 prose prose-neutral dark:prose-invert text-ink-900">
<CustomMarkdown>{technical}</CustomMarkdown>
</div>
)}
</div>
)}
<CustomLink
href={release.html_url}
className="inline-flex items-center gap-2 text-primary-600 hover:text-primary-700 text-sm font-medium transition-colors"
>
{t('news.view_on_github', 'View on GitHub')}
</CustomLink>
</div>
)
}
function formatPullLinks(text = '') {
return text
.replace('CompassMeet', 'CompassConnections')