Releases: schemalabz/opencouncil
Release list
2026.7.5
Highlights
This release ships a completely new homepage — an immersive, map-first landing where you explore council subjects directly on the map — and rebuilds /explain into a long-form, deep-linkable guide to how Greek local government works, now with live coverage and pricing.
What's New
- Map-first landing page — the homepage is now an immersive interactive map. Browse subjects geographically, search and filter, open subject cards, switch basemaps, and locate yourself, with a refreshed mobile header. The previous landing moved to
/old-landing. (#534, #538) /explainguide to Greek local government — a single-page, server-rendered (fully indexable) guide in ten stacked sections, with a sticky table of contents, scroll-spy that keeps the URL hash in sync, and deep-linkable/shareable sections. (#400)- Coverage, pricing & CTA on
/explain— a dynamic per-city × administrative-body coverage table, pricing derived from the pricing config, a "Νέος κώδικας" callout, and a "Bring OpenCouncil to your municipality" CTA banner, reorganized into a two-part structure. (#539)
Changes
- Subject pages now show the decision (απόφαση) box above the summary, so the outcome is visible first. (#529)
- Map supports basemap switching and a cooperative-gestures option.
- Subject ranking generalizes the discussion signal and ranks non-located subjects together with located ones.
- Map feature links on
/explainand/aboutnow point to the landing page (where the interactive map now lives).
Fixes
/explaincoverage table excludes future pre-published meetings, formats dates in each city's own timezone (avoiding hydration/locale drift), and is cached (15 min) to avoid per-request scans. (#539)- Nix offline builds now resolve
@posthog/cliby repacking it. (#531)
Internal
- Local setup no longer requires
BIRD_API_KEYorCRON_SECRET. (#536) - New landing map data layer, API endpoints, clustering, analytics, and
landingV2i18n (el/en/fr). test-backupskill gains an S3 fetch mode.- Retired deprecated Copilot prompts and Cursor rules.
2026.7.4
Highlights
The subjects embed widget can now resolve the municipality from a location alone — embedders only need to pass a geohash, and the widget figures out which city it belongs to. Production builds also now upload source maps to PostHog, making error stack traces readable in error tracking.
What's New
- Subjects embed widget works without a
cityId(#526): when only a geohash is provided, the widget resolves the municipality containing that location via a point-in-polygon lookup on city boundaries (cached per geohash). Locations outside every covered municipality render the widget's empty state instead of a 404, and the footer falls back to linking the OpenCouncil homepage when no city is known.
Internal
- Production builds upload source maps to PostHog via
@posthog/nextjs-config, enabled only whenPOSTHOG_API_KEYandPOSTHOG_PROJECT_IDare set (and skipped in sandboxed Nix builds). Source maps are deleted after upload so they never ship to the client. copy_dbscript: tables are now schema-qualified (public.) so copies and clears work regardless of the target database'ssearch_path.
2026.7.3
Highlights
Reliability fixes for automatic livestream detection: the poll-livestreams cron now reads channels' upload logs directly instead of YouTube search, so just-finished council streams are found immediately and in-progress streams are no longer picked up mid-broadcast. Also fixes former committee members leaking into the committee filter.
Fixes
- Livestream matching now reads the channel uploads playlist instead of search.list. YouTube's search index lags by minutes-to-hours and intermittently 403s, so a council stream that ended inside a meeting's polling window could be missed entirely. Reading the uploads playlist (
playlistItems.list+videos.list) is immediate, reliable, and far cheaper (1 unit vs 100). - Scheduled and in-progress streams are excluded from livestream matching. "Upcoming" and "live" broadcasts have no complete recording, so matching one would transcribe a partial video. Only finished streams are now considered.
- The committee members filter now excludes ended memberships. Filtering people by administrative body only considers currently-active roles, so a former committee member is no longer shown under 'committee'.
2026.7.2
Highlights
A new Hot Subjects embed widget lets municipalities showcase the topics being discussed in recent council meetings — optionally narrowed to subjects near a specific address — alongside the existing meetings widget. Under the hood, subject ordering is now unified behind a single distribution-aware ranking primitive. This release also fixes several localization gaps that showed Greek labels on non-Greek realms.
What's New
- Hot Subjects embed widget: A second embeddable widget variant (
/embed/subjects) surfacing a city's recent "hot" subjects, ranked by recency and discussion. Configurable in the embed configurator with a widget-type toggle and a subject-count control. - Location-filtered subjects: The Hot Subjects widget accepts an optional address filter that restricts results to subjects discussed within ~500m of that location (plus municipality-wide items). Only an approximate area (a geohash) is stored in the embed code, not the exact address.
- Standardized subject ranking: A new ranking primitive (
src/lib/ranking) scores subjects with a z-scored blend of recency, discussion volume, administrative body, municipality size, and location. Existing importance sorters now route through it, so subjects order consistently across meeting cards, dashboards, exports, and the widgets.
Changes
- Two-level admin-body picker: A shared body-type → specific-body filter, now used by both the city meetings view and the embed configurator.
- Subject card refactor: The subject card is split into shared
SubjectCardContent/SubjectCardFootercomponents so the app and the embed widget render from one layout. - Added geohash decoding utilities (
decodeGeohashToCenter,isValidGeohash) and a radius-based location filter that accounts for the known lat/lng-swapped points in the data.
Fixes
- Localized hardcoded Greek role labels (Δήμαρχος / Μέλος / Πρόεδρος / Επικεφαλής) in
RoleDisplay, so non-Greek realms (e.g. the French mayor badge) no longer show Greek text. French president label is now "Président(e)". - Localized the party-leader abbreviation in the compact person badge — previously the Greek "(Επικ.)" showed on all locales (en: "Leader", fr: "Chef").
- Derived the About page's
SubjectDemodemo URLs from the "subjects" openness feature config instead of hardcoded Chania links, so the French realm shows the Rennes demo subject.
2026.7.1
Highlights
Council meetings now transcribe themselves: a new cron finds a meeting's YouTube livestream and kicks off transcription automatically, with a Claude matcher that refuses ambiguous multi-meeting streams. This release also brings bulk Diavgeia decision polling for admins, broader el/en/fr localization, realm-aware geocoding and maps for the French deployment, and a fix to person-filtered search.
What's New
- Automatic livestream transcription — the new
poll-livestreamscron finds a recent meeting's YouTube video on the administrative body's channel, matches it with Claude (confidence ≥ 0.8, refusing streams that cover more than one meeting), and triggers transcription automatically, with Discord alerts for matches and for streams that need manual handling. RequiresYOUTUBE_API_KEY; no-ops when unset. - Bulk poll Diavgeia decisions — admins can select multiple meetings and poll decisions for all of them in one action, with a shared progress dialog (#468).
- OSEK TUTU promo banner — shown on the signup confirmation step during onboarding (#497).
Changes
- Wider localization (el/en/fr) — subject & meeting pages, the account menu, notification onboarding, the petition flow, and assorted city UI components are now fully translated.
- Realm-aware French deployment — address geocoding is now scoped to the realm's country (France on opencouncil.fr instead of always Greece), maps fall back to a country-appropriate view when a city has no geometry, and the About page shows realm-aware demo links and browser-frame domains, including French demo links.
- Shared batch-action primitives — the admin bulk bar now uses a common
useSequentialDispatchhook andBatchProgressViewdialog;BatchRerunActionswas migrated onto them.
Fixes
- Person-filtered search — a subject now matches when the person either introduced it or spoke in it. Previously the two conditions were AND-combined, which almost never matched and broke search on person profile pages (#469).
- Meeting map centering — meetings for cities without stored geometry now center on the city's realm instead of defaulting to Greece.
- Onboarding labels — intermediate notification steps now use the "Continue" label instead of a final-step label.
Internal
- New optional env var
YOUTUBE_API_KEYand cron route/api/cron/poll-livestreams; docs added todocs/task-architecture.md. - New shared Valkey/Redis cache helper (
src/lib/cache/valkey.ts) for dedup markers and API-listing caching; degrades gracefully whenCACHE_URLis unset. transcribetask split into an internal path (transcribeInternal.ts) so the cron can trigger it directly without exposing it as a Server Action.- Added test coverage for livestream polling, pollable-meeting selection, the search query builder, YouTube URL parsing, geo helpers, and the OSEK TUTU banner.
2026.6.9
Highlights
Admin forms get a proper image cropper, so party logos and councillor photos no longer end up squashed or cut off. Vote results are now hidden from the public, and a notifications bug that truncated stored message bodies is fixed.
What's New
- Image crop & zoom for logos and photos — uploading a party logo, councillor photo, or city logo now opens an interactive crop/zoom dialog (square frame for logos, round for photos). The framed region is rendered onto a 512×512 white-background PNG, so zooming out pads with white instead of cropping (#326, #504).
- Remove uploaded images — each admin form now has a trash button to clear a stored logo or photo.
Changes
- Vote sections on subjects are now visible to counsellors only and hidden from the public (#503).
Fixes
- Notifications: incoming Bird webhook messages now persist the full message body instead of the ~140-char conversation-list preview, which previously truncated stored messages mid-word.
- Image uploads: image processing failures (canvas/encoding errors) now surface an error and keep the crop dialog open, instead of silently uploading the unprocessed file (#504).
- i18n: the party edit form no longer renders raw translation keys (
PartyForm.logo, …) — the missingPartyFormnamespace was added to el/en/fr, and the color picker and crop dialog are now localized.
Internal
- Added
react-easy-cropfor the new image crop dialog.
2026.6.8
Highlights
A follow-up to the realm rollout that fixes the about page and untranslated UI on the French and English sites: the about-page hero stats and supported-city marquee now render everywhere (they were blank on opencouncil.fr), and the map, search and topic filter are now fully localized instead of showing hardcoded Greek.
Changes
- About page works across realms — the hero counters and supported-municipality marquee were realm-scoped and rendered blank/empty on opencouncil.fr; both queries are now global (the about page is marketing copy showing platform-wide totals), and municipality names render in English on every non-Greek locale
- Localized map, search and topic filter — the map filters + explainer, search page, and topic filter rendered hardcoded Greek strings even on the English and French sites; these are now translated (el/en/fr) via next-intl
Fixes
- French about page no longer errors — the French
about.jsonwas missing recognition items present in the Greek/English sources, which threw aMISSING_MESSAGEerror on the French locale; the recognition set is now aligned
Internal
- TopicFilter test mocks
next-intl(its ESM build isn't transformed by Jest)
2026.6.7
Highlights
This release makes opencouncil.gr (Greece) and opencouncil.fr (France) behave as fully isolated tenants ("realms") on a single deployment and database — separate city data, caches, topic taxonomy, and SEO per host. It also fixes a persistent "Failed to find Server Action" error caused by prod and staging sharing a cache namespace.
⚠️ Database migration: this release adds theTopic.realmcolumn (additive, backfills existing rows togreece). Apply it after deploy.
What's New
- Realm-based tenant isolation — the platform resolves a tenant "realm" from the request host, so
.grand.frserve isolated city data, caches, and map routes despite sharing one deployment and database (#494) - Realm-scoped topic taxonomy — topics now belong to a realm, so French communes are classified against the right taxonomy instead of Greek-only institutions; agenda/transcript tasks pass the city's realm to the backend (#494)
- Host-aware SEO — canonical, hreflang, Open Graph and sitemap URLs are derived from the request host; French pages now emit
frhreflang, and a dynamicrobots.tsadvertises the per-host sitemap (#494) - Footer country switcher — a small footer control shows the current country and lets visitors switch realms (#494)
Changes
- French about page copy refreshed to the reviewed wording; pricing section and its nav entry hidden on the
.frrealm (#494)
Fixes
- Server Action errors — cache keys are now namespaced by environment and build, so prod and staging (which share a Valkey instance) no longer serve each other's RSC payloads, fixing persistent "Failed to find Server Action … older or newer deployment" errors (#498)
- Stale notification content — meeting/subject caches are revalidated immediately after new data is persisted and before notifications are sent, so early recipients no longer see pre-summarize content during the rate-limited send loop (#492)
Internal
- Migration
20260621120000_add_topic_realm: addsTopic.realmcolumn with arealmindex (additive, backfills existing rows togreece)
2026.6.6
Highlights
OpenCouncil now speaks French. This release adds a full French locale, .fr domain routing, and a language-aware AI city creator so French communes can be onboarded end to end. Alongside it: a refreshed About page, a new admin cities table, and a batch of UI and i18n fixes.
What's New
- French locale support — complete French (
fr) translations,.frdomain routing, and French landing-page copy (#491, #496) - Language-aware AI city creator — the city creator and downstream tasks (transcribe, summarize, process-agenda) now respect a city's language, enabling French commune onboarding. The
Citymodel gainslanguageandrealmfields (see migration note below) (#491) - Admin cities table — new admin view listing all cities
Changes
- About page: refreshed Awards & Mentions / Recognition section and added Alexandra Ranunkel to the team
- Footer and header login button are now localized
- Centralized name shortening into shared helpers for consistent name display across the app (#490)
- Default AI model moved off the retired
claude-sonnet-4-0(#478)
Fixes
- Speaker timeline color now resolves as of the meeting date, fixing independent-councilor colors (#484)
- Story template picker dialog is responsive on mobile; spacing fix between emoji and meta text in the classic story
- Added missing French about-team translation for Alexandra
Internal
- DB migration
20260619170648_add_city_language_realm: addsCityLanguageandRealmenums andCity.language/City.realmcolumns (defaultsel/greece) - Dependency bumps: tailwind-merge 3.6.0 (#440), framer-motion 12.40.0 (#442), development-minor-patch group (#486)
Migration note: This release contains the Prisma migration
20260619170648_add_city_language_realm. It is applied automatically on deploy (the App Platform build runsdb:deploy→prisma migrate deploybefore building).
2026.6.5
Highlights
Restores the ability to label a speaker who isn't in our database when
re-assigning speakers in a meeting transcript — a regression from the recent
speaker-search ranking change.
Fixes
- Transcript speaker picker: typing a name that matches no person again offers
"Set label to …", so speakers who aren't in the database can be given a custom
label. It also stays available below the ranked matches when the typed name
does match someone, and the top match stays highlighted for Enter.
(Regression from #452.)
Internal
- Added component tests covering the speaker picker.