Changelog
All notable changes to TimeTiles are documented here. Format based on Keep a Changelog .
See the release process for how new versions are cut.
[Unreleased]
Changes landed on main since the last tagged release.
[0.1.0-alpha.28] - 2026-11-09
Fixed
- Job workers: a long job holds one slot instead of blocking its whole queue until it ends
- Scraper setup: bootstrap step 13 installs the runner of the release in
.env.production, not an older one from the bootstrap config
[0.1.0-alpha.27] - 2026-11-09
Fixed
- Scheduled ingests: a finished run no longer resets the next run time to an old value, which made every schedule fire again each minute
[0.1.0-alpha.26] - 2026-11-08
Fixed
- Background jobs: every task reports a heartbeat, and
timetiles ingest recover --applycloses a scheduled task whose worker died, so its schedule resumes
[0.1.0-alpha.25] - 2026-11-08
Added
- Ingest: each sheet runs as its own job within a recorded run, the event quota is charged through a ledger, so retries and crashes cannot charge twice, and
timetiles ingest recoverreports work whose worker died and resolves it with--apply - Layout templates: site default and per-page header and footer frames
Changed
- Production refuses to start without
EMAIL_SMTP_HOSTunlessEMAIL_DISABLED=trueis set; SMTP requires TLS by default - Workers run
scripts/jobs-worker.tsand finish the batch in flight on shutdown - Login, registration and account forms carry the autocomplete attributes password managers rely on
Fixed
- Ingest: every pipeline stage is safe to retry; geocoding keeps its results between attempts
- Import wizard: a stale edit session no longer takes over a new import, and the next sheet’s review starts with a fresh decision
- Account deletion removes trashed rows too and deletes upload files only after the transaction commits
- All-in-One: the documented
docker runcommand sets the required mail option
Security
- TimeScrape: git clones connect only to the addresses the SSRF guard vetted and cap the transferred bytes
- Only editors and admins can write media; REST requests check their ingest targets
[0.1.0-alpha.24] - 2026-11-07
Changed
- Linting runs on oxlint alone; the remaining ESLint plugins run as oxlint JS plugins, and layer boundaries are now also checked for
@/imports - Minor and patch dependency updates, tsup 9; Dependabot skips Payload, which is upgraded as one unit
Fixed
- Explore: a click on the map/list switch is no longer lost when the map writes its position at the same moment; the destination opens where the map came to rest
- Footer funding logos render through
next/image, and the global error page reloads home without the router - Request timing padding no longer swallows falsy rejections
- DNS lookups for URL validation and the TimeScrape SSRF guard use
order: "verbatim"instead of the deprecated flag - TimeScrape: the node scraper image installs its build tools at the versions locked in
pnpm-lock.yaml, so tool updates no longer break the image build
[0.1.0-alpha.23] - 2026-10-30
Fixed
- Deployment: Podman copies images through a directory inside the app user’s image store, so pulls and builds also work once the runner is up, whose unit keeps
/var/tmpread-only
[0.1.0-alpha.22] - 2026-10-30
Fixed
- TimeScrape: the runner addresses its scraper images as
localhost/timescrape-<runtime>; resolving the short name needs a lock the runner unit keeps read-only, so after a reboot no image was found
[0.1.0-alpha.21] - 2026-10-30
Fixed
- Deployment: the scraper setup resets a Podman store that recorded
/tmpas its run root, which the runner unit cannot write, so the runner finds its scraper images again instead of reporting them unavailable - TimeScrape: the runner logs the Podman error behind a failed image or network check
[0.1.0-alpha.20] - 2026-10-30
Changed
- Explore: tag filters use the GIN index on
events.transformed_data, with a migration that drops the duplicate index on that column - Quotas: an admin can approve a quota-exceeded import only once the user’s limit allows it; until then the approval answers 409
- Minor updates of the lint tooling, with patched audited advisories (handlebars)
Fixed
- Deployment: the TimeScrape runner starts its scraper containers again after a host reboot; its unit no longer sets
NoNewPrivileges, which kept rootless podman from mapping its user namespace - Map: explains when the map cannot start instead of loading forever
- Mobile: the navigation drawer shows account links and sign-out, and the import wizard keeps its back and edit-mode labels on small screens
- Forms: values entered before hydration are kept
- Explore: the event list keeps its live-announced description of counts and filters when nothing matches
- Imports: a source with only a header row completes as a run with nothing to import, and sheets are routed by the workbook’s sheet count rather than by the sheets that contain rows
- Event detail: a mapped description path is authoritative
[0.1.0-alpha.19] - 2026-10-26
Changed
- Payload CMS 3.92.0, with a migration that indexes the localized content tables
- API: requesting a document’s draft by ID (
?draft=true) without access to that draft answers “not found” instead of returning the published version @timetiles/ui:DataTablebuilds on TanStack Table 9, andDataTableandDataTablePropstake the row type as their only type parameter- Minor and patch updates of the remaining dependencies, including MapLibre GL 6.16
Fixed
- Docs: the documentation site builds again with zod 4.4 and later
- Imports: files with several sheets no longer fail sporadically while creating schema versions in parallel
- Quotas: concurrent creates at a reached limit answer “limit reached” instead of stalling the database connection pool
- Scheduled imports: saving a schedule applies its schema configuration to several datasets one after another instead of concurrently on one transaction
- Development: the e-mail fallback replaces an expired ethereal.email account instead of failing with “Invalid login: 535”
[0.1.0-alpha.18] - 2026-10-26
Changed
- Payload CMS 3.90.2 and Next.js 16.4.3
Fixed
- Explore: focusing on a cluster cell shows the clusters inside that cell instead of the whole viewport
- Import: large uploads arrive whole, and multipart uploads are capped at 100 MiB to match the file-size quota
- Deployment: the web container gets an executable cache mount for the SWC native addon, which the
noexec/tmpcould not load
Security
- Upgraded dependencies with known vulnerabilities: Next.js, nodemailer, undici, zod, DOMPurify, happy-dom, esbuild, simple-git and brace-expansion
[0.1.0-alpha.17] - 2026-09-29
Fixed
- Import: CSV preview, schema detection, row count and import read a file the same way — a leading byte order mark, blank lines before the header, duplicate or padded header names and delimited
.txtfiles no longer make them disagree - Import: valid UTF-8 files are no longer misread as a single-byte charset, so
KölnstaysKöln - Import: counting rows of a missing sheet or an unsupported file type fails instead of reporting zero, and duplicates written in place under the
updatestrategy are no longer reported as skipped - Login: overlapping logins of one account no longer drop each other’s session, and expired sessions are removed on login
- Explore: a zoom or pan made before the map style loads is kept, and a map view from the URL is no longer replaced by the fit to the data
- Explore: the map reports itself busy until its clusters match the view, and the theme picker no longer shifts the header while the page loads
- API:
/api/v1/sources/statscounts events of public datasets whose catalog was deleted intotalEvents - API:
/api/legal-noticesand dataset schema inference report failures instead of answering with empty notices or “not found” - Development:
validate-typeschecks Payload’s generated files without rewriting them or leaving backups
Security
- A catalog made public, or a dataset moved to a public catalog, only in a draft no longer makes its events public before the change is published
- Approving an import job no longer applies field-mapping changes to a dataset the approver cannot edit
[0.1.0-alpha.16] - 2026-09-28
Upgrade note: the scraper runner no longer defaults its work area to /tmp/timescrape and refuses to start without an absolute SCRAPER_DATA_DIR. Bootstrap step 13 writes it into the runner’s systemd unit. timetiles update leaves a runner whose unit predates this in place and names the step to rerun: sudo deployment/bootstrap/bootstrap.sh --step 13-scraper-setup.
Changed
- Cache: the general cache keeps only its filesystem backend; the cleanup job fails, and is retried, when cleanup or statistics fail instead of reporting invented numbers
- Scrapers: the reported row count skips a record holding one empty field, as the import does
Fixed
- Scrapers: CSV rows are counted per record, so quoted fields spanning lines or containing literal quotes no longer skew the count
- Scrapers: the Node and Python SDKs refuse rows without fields instead of writing an empty file
- Scrapers: runs whose output the watchdog cannot read are killed, and the reason names the path
- Scrapers: output download and cleanup errors are reported instead of answering 404 or staying silent
- Scrapers: a clone whose repository size cannot be read fails instead of passing the size limit
- Scrapers: the run’s stderr names the signal that ended podman
- Scrapers: the runner rejects empty or out-of-range settings at startup, and
initrejects unknown arguments - CLI and bootstrap: the runner is swapped by renames, so an old install that cannot be deleted is still replaced as a whole
- CLI and bootstrap:
.envlines with CRLF endings are read without the carriage return
Security
- Scrapers: repository clones refuse a git host the SSRF check cannot resolve
[0.1.0-alpha.15] - 2026-09-28
Fixed
- CLI:
updatefinishes in a fresh process after pulling the deployment files, also with--no-self-update, so the rest of the update runs the pulled CLI with the pulled libraries
[0.1.0-alpha.14] - 2026-09-28
Changed
- Ingest: uploads accept one shared MIME allow-list; the Google Sheets type is gone because no parser reads it
- CLI and bootstrap: runner install, podman calls and
.envreads share one implementation; bootstrap replaces the runner atomically, so a failed install keeps the old one - CLI and bootstrap:
.envvalues containing=or quotes are read whole; a repeated key takes its last assignment
Fixed
- Scrapers: an invalid cron expression disables the scraper and marks the run failed instead of skipping it silently forever
- Ingest and seeding: lookup errors other than “not found” are raised instead of treated as a missing record
- CLI: a failed image pull shows the registry error before falling back to a local build; a failing alert script is reported as a warning
Security
- URL fetches and scraper repo sync refuse the request when the SSRF DNS check cannot resolve the host, instead of skipping the check
[0.1.0-alpha.13] - 2026-09-28
Fixed
- CLI: a root invocation keeps
TIMETILES_DISCARD_CONTAINER_DBwhen it hands over to the deploy user, so the “Database Outside the Volume” reset works undersudo - CLI:
create-adminwithout a terminal fails with a message namingTIMETILES_ADMIN_PASSWORDinstead of exiting silently - File cache:
destroy()waits for the index write and reports its errors instead of dropping them - CMS blocks: list items are keyed by their Payload id
Security
- Ingest: transform expressions use
expr-eval-fork, which rejects prototype access such asconstructorand__proto__(CVE-2025-12735, CVE-2025-13204)
[0.1.0-alpha.12] - 2026-09-28
Fixed
- Bootstrap: steps run from
/, so podman invoked as the app user no longer aborts on an inherited working directory it cannot enter - Bootstrap: the scraper-setup step restarts the runner, so a rerun applies the rewritten systemd unit instead of leaving the old one active
[0.1.0-alpha.11] - 2026-09-28
Republishes alpha.10: its image release stopped at the deployment test, so no alpha.10 images were published. The upgrade note from alpha.10 applies.
Fixed
- Release pipeline: the deployment test environment gives the log and export bind mounts to the container user, as bootstrap does, so the containers no longer crash-loop on unwritable log files
[0.1.0-alpha.10] - 2026-09-28
A large hardening release covering deployment, background jobs, ingest, the URL cache and access control. Production compose now runs one worker per job queue, and only the web container runs migrations. Imports into the same dataset now run one at a time. Upgrade note: the postgres cluster now lives in the postgres_data volume (kartoza DATADIR). On installs created before this release, the database sits in the container’s writable layer, and timetiles up/down/restart/update refuse to run. Take a backup first, then follow “Database Outside the Volume” in deployment/README.md.
Added
timetiles create-admin <email>creates the first admin on a fresh install. It takes the password from the environment, skips email verification, and refuses to change an existing account. Bootstrap now prints next steps that point to it- New deployment settings:
INGEST_FILE_RETENTION_HOURS/INGEST_FILE_ORPHAN_GRACE_HOURS(import working-file cleanup),TRUSTED_PROXY_CIDRS(client IP for rate limiting behind a proxy),DOCKER_NETWORK_SUBNET(pinned bridge subnet that the scraper-runner firewall rule relies on) andEXPORT_HOST_DIR(shared data-export directory) - Scrapers: owner-scoped repository sync status, with sync errors shown in the UI
- Development:
make setup-macprovisions a macOS machine (including the h3 extension), andPG_LOCAL_PORTsets the local PostgreSQL port
Changed
- Database: the postgres data directory is now
/var/lib/postgresql/datainside thepostgres_datavolume. Lifecycle commands guard against installs whose database is outside the volume, andTIMETILES_DISCARD_CONTAINER_DB=1overrides the guard - Version history is capped at 10 versions per event and 5 per ingest job. Media no longer keeps drafts or versions
- Imports: imports into one dataset are serialized with a Postgres advisory-lock lease, while different datasets still import in parallel. A failed update-strategy import rolls back fully, including sheet imports
- Deployment:
worker-general,worker-ingestandworker-maintenanceeach consume their own queue, and exactly one of them handles schedules. The workers wait for web to finish migrating. The all-in-one image now runs background jobs and logs nginx to stdout. Updates pull deployment files from the pinned release tag - The map uses maplibre-gl v6 with its worker served from the app, so the CSP no longer allows blob workers
- Ingest-created catalogs, datasets and events are published on creation. Draft catalogs, datasets, events, footers and menus are hidden from public and non-owner reads
- Generated slugs transliterate umlauts and accents, and stored slugs no longer change on save. Feature flags apply as soon as settings are saved
- Accessibility and i18n: map control popovers are keyboard accessible, closed filter surfaces leave the tab order, result changes and form errors are announced, the date slider moves by whole days, and dates, chart labels and remaining UI strings follow the app locale (EN/DE)
Removed
- The
HOST_PORTandCONTAINER_PORTdeployment variables. The app port is fixed at 3000, and changingCONTAINER_PORTused to deadlock the stack - The placeholder
EMAIL_SMTP_HOSTin the env template. Leave it unset to run without outgoing mail - npm from the production runtime image, and ineffective schedule-retry and job-timeout settings
Security
- Access control: closed IDOR holes, XSS sinks, fail-open query and H3 filters, and a catalog-delete grant leak. Clients can no longer write credential, derived-access, pipeline, scraper run-state or scheduler fields. Native job controls and user creation are restricted to admins, and ingest-job updates to editors and admins
- Auth: password policy enforced and sessions revoked on reset, credentials revoked on deactivation, cancel-deletion attempts rate-limited, and rate-limit keys namespaced with the client IP taken only from trusted proxies
- SSRF and redirects: closed the IPv4-mapped IPv6 bypass and redirect targets that smuggle a host past the prefix check. Bracketed IPv6 clone hosts are classified correctly
- Data leaks: user data is escaped in emails, 5xx responses and export emails no longer expose internal errors, logs no longer contain URLs, OAuth, cache or provider details, or subscriber emails, and persisted cache keys hash their source URLs. Exports no longer strand personal data, and account deletion serializes with export publication
- CSV downloads are formula-escaped at the download boundary, including handling for delimiters, BOM,
sep=and SYLK. Stored ingest data is left unchanged - Scraper sandbox: runner containers are bounded, rootless Podman egress is fenced by the app user’s uid, the seccomp profile ships, and run output is read without following symlinks
Fixed
- Deployment and backup: four containers no longer migrate one database at once, and quoting
/etc/timetiles.envprotects passwords with spaces or backticks. A failed runner is detected and kept on a bad update, and a failed offsite backup alerts.restore latestpicks the newest snapshots and swaps uploads in atomically. Also fixed: a certbot anonymous-volume leak, and nginx static asset locations that dropped security headers - Ingest and schema: source encoding is detected and phantom blank rows are dropped. EU decimals, timed dates and multiline CSV headers parse correctly. Excel previews match imported column names. Pagination limits and OAuth, token and custom-header auth configs are validated. Cron and DST schedule edge cases (nonexistent midnight, leap days, empty timezones) are handled. Retries and manual triggers are queued in the claim transaction. Stale mappings are cleared, and data-package activation parameters are validated
- Jobs and cleanup: stuck-ingest wedging, quota rollback and an audit transaction leak are fixed. Partial cleanup failures (exports, previews, ingest files, audit IPs, schema maintenance) are retried. Processing and restarted jobs are kept out of cleanup. A scraper run that never started refunds its quota
- Scrapers: run timeouts use kill escalation and keep partial logs. Leftover work directories are swept. Deleting a scraper while a run is active is refused, and a duplicate active run id returns 409. Schedules stop after deactivation, and cron syntax is validated before saving
- URL cache: honors Vary, quoted and mixed-case cache-control, zero TTL and mandatory revalidation. Partial responses are kept out of the cache, the persisted index is validated, and cleanup runs in the ingest worker
- Geocoding: provider calls abort on timeout, backoff and throttles are respected, disabled fallback and provider groups are honored in batches, Photon gateway failures are retried, and the kill switch no longer fails open when settings cannot be read
- Explore, charts and events: histogram buckets cover ranges beyond 68 years, and a UTC session allows pre-1900 timestamps. Trashed events are hidden from SQL read paths and counts. Invalid numeric ranges in shared URLs are rejected. Event titles render through the dataset field mapping. Dark-mode chart theme and map label halo are fixed
- Accounts: account deletion scheduling, cancellation and execution are serialized, all due accounts are processed, and verification and reset state is isolated per token
[0.1.0-alpha.9] - 2026-06-30
A large stabilization release. Five multi-agent bug sweeps across correctness, security, privacy, internationalization, and ingest/scraper robustness, plus ICU pluralization and localization of the remaining hardcoded copy. Closes the interpretation follow-ups from alpha.7 and hardens the production image build.
Added
- Background job that reclaims and sweeps orphaned ingest CSV files
timetiles checkverifies cron and systemd path references
Changed
- Count strings use ICU plurals; page metadata, account-deletion copy, and remaining hardcoded strings localized (EN/DE)
- Draft preview restricted to content roles (admin/editor); draft content is no longer served to anonymous visitors
Fixed
- Security and privacy: drop all credential headers on cross-origin redirect; isolate the URL-fetch cache by auth identity; cap quota-remaining to block privileged-account enumeration; close a private-event leak in the H3 cluster branches; pair the pinned SSRF dispatcher with undici fetch (and stop it blocking public
fd*/fc*domains); addX-Frame-Optionsandframe-ancestorsCSP; restrict credential reads and rotate sessions on change - Accounts and auth: allow cancelling a scheduled deletion; defer email swap to verification time to prevent lockout; re-validate deletion eligibility at execution; unbreak email verification and enforce
isActive; stop faking reset-email success - Ingest and schema: make dataset-detection idempotent and
findOrCreateDatasetconcurrency-safe; prevent duplicate data-package activations under concurrency; reuse the latest schema version when unchanged and persist real enum frequencies; fail oversized imports terminally instead of stranding them; fix re-import data loss and progress/review stalls; numerous import-wizard and edit-schedule state fixes - Scrapers: recover stuck and orphaned runs; coerce off-contract runner output; RFC 4180 CSV escaping in the SDK writer; handle webhook-queue failures; make scraper deletion (and account-deletion coverage) work
- Maps, charts, and explore: derive H3 resolution from returned cells; fix histogram tooltip dates, bar clicks, and locale; clamp range-slider bounds and keep drags alive with pointer capture; validate explore URL filter params to prevent crashes; apply range and cluster filters consistently across views
- Quota and events: stop double-releasing event quota; pin the daily reset to a UTC boundary; clear the quota claim after a successful create
- Geocoding: apply provider confidence grades correctly; preserve hemisphere for
-0°DMS coordinates; score by result type and gate bias params - Deploy and build: pin timescrape’s
zodso the production image builds; threadDEFAULT_LOCALEinto image builds; shiphealth-check.shandalert.shas tracked files
[0.1.0-alpha.8] - 2026-06-04
A same-day follow-up to alpha.7: data-package auto-activation in deployed environments and map-rendering fixes.
Added
- Auto-activate the three bundled data packages on staging and production
Fixed
- Preserve antimeridian-crossing map bounds and accept wrapped world-view bounds; use map glyphs that exist
- Unbreak the map CSP (allow the worker
blob:source and the bare VersaTiles host)
[0.1.0-alpha.7] - 2026-06-03
Introduces a unified row-interpretation pipeline (ADR 0040) that fixes silent data misreads — day/month date order, single-column combined coordinates, and locale-aware numbers — plus numeric range filtering on event fields. Includes two large multi-agent bug sweeps.
Added
- Numeric range filtering on event fields, with EU-aware decimal and thousands parsing
- Single-column combined coordinates (e.g.
"52.5, 13.4"in one field), with a review gate for ambiguous axis order - Per-column date day/month order detection with a review gate, fixing a silent DD/MM vs MM/DD misread
- Locale-aware number parsing during record merge
- Best-effort opt-in for ambiguous interpretations (ADR 0040)
Changed
- Row interpretation unified into a single persisted interpretation plan — coordinate, date, and number handling now flow through one path (ADR 0040, Phases 0–4)
- Layered-architecture ESLint boundaries now actually enforce the
lib/layering
Fixed
- Two multi-agent bug sweeps (a 28-bug sweep and a backend correctness/security/rate-limit sweep): event quota charged only for newly-created events, locale-independent sort keys for dedup and cache, committed sub-batches retained on partial bulk-insert failure, import config validation (ID-strategy and mapping vs transforms), and map labels/antimeridian/single-day timezone/virtualization
- Salvage swapped axis order for ambiguous combined coordinates
- Deploy: writable tmpfs for the Next.js
.next/cache; scraper returns 409 before 429 on an in-progress run
[0.1.0-alpha.6] - 2026-05-12
Tightens the authenticated surface: data-packages moved behind the account section, optional public contact email on the disabled-registration screen, and the Payload migration history is reconsolidated so migrate:create stops rediscovering historical changes.
Added
contactEmailfield on the Branding global, rendered as a mailto link on the disabled-registration screen so visitors know how to request an accounttimetiles checkflags drift between deployeddeployment/files and the repository checkout
Changed
/data-packagesis now/account/data-packagesand requires authentication; the link moved from the main menu to the header user dropdown- Snapshot baseline reset migration so
payload migrate:createstarts from the current schema instead of rediscovering changes from earlier hand-written migrations
Removed
- Unused retry-tracking columns (
retry_attempts,last_retry_at,next_retry_at,last_successful_stage) and dependent enums fromingest_jobs/_ingest_jobs_v, catching up a code-vs-schema drift
Fixed
timetiles checkSSL, podman, andrun_checkrobustness; false negatives inverify_ufw,verify_fail2ban, andverify_backup_cron
[0.1.0-alpha.5] - 2026-05-11
First post-funding-period release. Operational hardening for the public launch: deployment robustness, geocoding correctness, CMS bootstrap, and image-supply-chain security.
Added
- Environment banner on non-prod deployments
- Auto-activation of data packages on boot per environment
- Bundled data-package manifests delivered via
git pullon deploy - Auto-bootstrap of free-tier geocoding providers on first boot
- Default CMS content bootstrapped as part of the deploy seed preset
- Demonstrations.org data packages with POST pagination
- UCDP HDX data package and catalog metadata
- NSU Offener Prozess scraper example
- Transactional emails queued via Payload tasks
- Storybook stories for UI primitives, charts, layout/editorial blocks, overlays, navigation, map controls, and form/feedback components
LOGIN_SUCCESS,LOGIN_FAILED, andREGISTEREDaudit events- Points cluster display mode
- Smart groupBy filtering with shared settings panel
- URL-sync for fullscreen state and groupBy UX
- Contextual category filters with cross-filtering
- Multi-column grid layout (renamed from List view)
- Collapsible chart section with unified header
CardToolbarprimitive and “Analyze” chart button- Info popover on catalog group headers
- Split-to-array transform for multi-value CSV fields
E2E_MODEsignal andisE2E()helper- ESLint rule preventing
sql.raw()with dynamic strings - SonarCloud scripts: coverage-per-file, security-hotspot review, quality-gate fetch
Changed
- Wizard store actions renamed to intent-based names
- Trivy scan and
wait_for_healthextracted into reusable CI helpers - Explore view-toggle terminology unified on “List”
editDatasetIdreplaced witheditScheduleDatastate- Documentation
<img>tags converted tonext/image - Favicon metadata and assets refreshed
- Seed bootstrap consolidated into the deploy preset of
SeedManager
Fixed
- Scheduled-ingest retries, quotas, and URL-fetch hardening regressions
- CSV identity values preserved through import
- Map and import flows hardened (replacing fake tests with real assertions)
- Maintenance job task enum values aligned with code
- Numeric strings no longer accepted as inferred dates; trusted HTTP date guard simplified
- Deployment: nginx healthcheck timing, postgres race on first boot, SMTP cert mismatch, container UID for
logs//uploads/,DEPLOYMENT_ENVIRONMENTpropagated to the web container, post-pull re-exec schemaModepropagation in wizard first-run and scraper paths- “Create new catalog/dataset” placed at top of dropdown
- Spreadsheet-prefixed coordinates (e.g.
=52.5, 13.4) now parse correctly during import - Duplicate retry-exhausted notification emails on scheduled ingests
- Array-form callback emitted for pinned undici hostname lookup
Security
- Scheduled ingest validation hardened
- HTTP security headers baseline and SSRF/path-traversal hardening on import
- Critical and high-priority findings from the 2026-04-16 audit closed
- Docker image security scanning hardened (Trivy fails the build on findings)
- Seeded user credentials moved out of
tests/so they no longer ship in production builds
[0.1.0-alpha.4] - 2026-03-31
End-of-funding-period iteration. Final state of the platform on the last day of the Prototype Fund Software Sprint funding period (FKZ 16IS25S03).
Added
- H3 hexagonal grid clustering with expert mode
- Cluster focus mode, H3-based cluster filtering, mini dashboard
- Unique-locations model — separate locations from clusters
- Dataset-centric filter sidebar with multi-catalog support
- Dataset info popover, colored checkboxes, dataset bounds API
- Auto-zoom to data on dataset selection change
- Beeswarm chart type with d3-force layout and server-side temporal clustering
- Grouped charts: groupBy for beeswarm and stacked histogram
- Chart type synced to URL parameter
- Tag field support for multi-value array filtering
- HTML-in-JSON import format with detail-page enrichment
- JSON pre-processing with group-by and date merge
- Embeddable views via
/embedroute - Photon and LocationIQ geocoding providers (weighted distribution)
- Funding logos (Prototype Fund, BMFTR) in docs, web app, and README
Changed
- Tag chips in event detail show field labels
- Filter rows left-aligned; redundant catalog labels removed
- Catalog chart hidden for single-catalog selections
Fixed
cluster_events()location_name cast to text
[0.1.0-alpha.3] - 2026-03-22
Second Second-Stage iteration. Feature work in direct response to feedback from the partner-organisation cooperation: internationalization, scraper sandboxing, JSON API ingestion, full wizard redesign.
Added
- Internationalization with next-intl (English and German locales)
- Translation of all user-facing components and email templates (EN/DE)
- Payload CMS content localization for EN/DE
- Custom scraper system with isolated Podman runner, scraper management UI, and CLI
- Scraper quota enforcement
- Block registry, UI customization, and layout templates
- Branding global wired into emails (siteName and logo)
- API key authentication
- Configurable legal notices on the registration form
- Timezone support for scheduled imports
- JSON API import with pagination and wizard UX
- JSON file upload in the import wizard
- Edit scheduled import via the import wizard
- Multi-stage progress timeline and column-centric mapping in the import wizard
- Multi-worker support with concurrency control for jobs
- Error boundaries on explore and import pages
- React Compiler enabled
Changed
- Renamed service factories to
create*Serviceconvention - Replaced custom fade animations with native View Transitions API
- Migrated all frontend routes to locale-aware
[locale]/(frontend)/structure - Renamed
import→ingestacross the codebase - Schema-detection package consolidated into the app
[0.1.0-alpha.2] - 2026-03-12
First Second-Stage iteration. Code quality, infrastructure hardening, and platform consolidation in preparation for the partner-cooperation work.
Added
- Restic-based backup system with S3 offsite support
- All-in-one Docker image for single-server deployments
- Pre-built GHCR images in docker-compose, pull-based deployment workflow
- Integration tests for nginx routing, backup/restore, and Docker container lifecycle
- Forgot-password and reset-password pages
- Generic audit log with broad codebase coverage (extends the original audit-logging service)
apiRoute()framework with auto-wrapping; all API routes migrated- Zod validation schemas across all API routes
- Encryption of credentials at rest
- Sites collection wired into layouts with data-scope filtering
- Worktree-management script with env-file setup
- 80 additional oxlint native rules
- Shared
ContentStatecomponent for empty/error/no-match states
Changed
- Decomposed large files into focused modules
- Logo and asset reorganisation with new naming and folder structure
Fixed
- Access control hardening (5 audit findings, including a security bug)
- Dataset-schemas access control denormalised for performance
- Constant-time email change, quota caching, env naming
[0.1.0-alpha.1] - 2025-12-02
First feature-complete public alpha at the close of the regular Prototype Fund Software Sprint Phase 1 (June–November 2025, FKZ 16IS25S03). Implements the milestones from the original proposal: design system, geocoding/data ingest, layout components, filters, visualisation, documentation.
Added
- Initial public alpha of TimeTiles
- Interactive geospatial timeline visualization with MapLibre GL JS
- Data import system supporting CSV, Excel, and ODS formats
- Automatic schema detection and field mapping
- Multi-provider geocoding (Nominatim, Google) with database-driven provider configuration and persistent location cache
- Real-time map clustering and timeline scrubbing
- Timeline histogram for event distribution over time
- Dynamic filters by category, date, and location with URL-based state
- Categorical field filters with schema-based enum support
- Map theme toggle and zoom controls
- Adaptive tooltip formatting for histogram chart
- Responsive web interface with light/dark themes
- Mobile navigation drawer and filter sheet
- Import wizard with field-mapping detection and user usage tracking
- Transforms UI and flow editor
- Scheduled URL-based imports with automatic execution and webhook triggers
- Background job runner
- Account management, feature flags, and branding globals (logos, favicons, site name)
- Newsletter signup integrated with Payload CMS in the footer
- Pages collection and MainMenu global for CMS-driven navigation; flexible page builder with block-based content
- Multi-user permission and quota system with trust levels (0–5)
- Owner-based access control for private data
- Comprehensive audit logging service
- Views collection for saved display configurations
- Comprehensive E2E testing with Playwright
- PostgreSQL 17 + PostGIS 3.5 backend
- Next.js 16 frontend with Payload CMS 3 (integrated mode)
- Documentation site (Nextra)
- AGPL-3.0 license
For development changes, see the Git commit history .