Changelog

Product changelog

Every shipped change, newest first. The current release is v1.0.7 (Analytics that aggregate, and column pickers that cannot be wrong), and 230 changes are documented across the release history.

  1. v1.0.7

    latest

    October 12, 2026

    Analytics that aggregate, and column pickers that cannot be wrong

    Analytics is the product, and it was answering the wrong question in two places. A dataset was treated as available only while the *latest* run had succeeded, so a pipeline that failed once after a good run disappeared from the dataset picker even though its rows were still on disk - and the demo reported `status: null` for every pipeline, so demo analytics offered nothing to explore at all. Availability is now its own fact (`has_dataset`), derived from whether any run has ever succeeded. The second problem was the numbers themselves: a chart read raw rows, so `revenue by region` drew one bar per transaction instead of one per region, and a line chart plotted a time series in storage order. Charts now group by the X field and aggregate Y (sum, average, count, minimum, maximum or none), which is what makes the existing Sort by and Take first N controls mean something. The pipeline builder no longer asks anyone to type a column name: data sources report the columns they actually contain, discovered from the stored file, and every transformation step picks from them.

    Added

    • A grouping stage in the chart engine. Every chart config now carries an **Aggregate** setting - Sum, Average, Number of rows, Minimum, Maximum or None - applied before sorting and the row limit, so `Sort by revenue`, `Descending`, `Take first 10` produces a genuine top-10 list instead of ten rows of the same category. `src/lib/chartData.ts` owns the whole pass (group, sort, limit) so the dashboard, the table and the config preview can never disagree.
    • Column discovery for data sources. `DataSource` now exposes a read-only `columns` list, inferred from the stored file (CSV header, newline-delimited or whole-document JSON, spreadsheets) and cached on `config`, so the pipeline builder is still a single list request. The upload preview returns the same list, and a source that cannot be inspected locally - a remote API or a database connection - reports none, which is the signal to fall back to free text rather than offer a wrong menu.
    • `has_dataset` on the pipeline API: `true` once any execution has succeeded, read from a single annotated query for the whole list. The analytics picker and, indirectly, the dataset rows endpoint key off it.
    • Chart layouts are saved per pipeline and per session mode, so a dashboard survives a refresh without leaking into another workspace, and a demo layout can never appear in a real one. A chart can also be deleted, from the same dialog that edits it.
    • `apps/pipeline/test_transformations.py`, the first direct coverage of the transformation engine, pinning the step vocabulary including its aliases and the loud failure modes.

    Performance

    • A source's column list is discovered once and cached on the row, so opening the pipeline builder does not re-read any stored file.

    Changed

    • A line chart orders its X field ascending unless an explicit sort field is chosen: a time series plotted in storage order zigzags for no visible reason.
    • Axis ticks shorten ISO dates to `Oct 10`, in both bar and line charts, so a crowded date axis stays readable.
    • Analytics reads a 500-row window instead of 100. Grouping happens client-side, so a narrow slice of a transactional feed made every aggregate look like a rounding error.
    • `convert_type` accepts `to` as an alias of `converted_to`, and the filter step exposes the operators the engine already supported: `>=`, `<=`, `contains`, `starts with` and `ends with`.
    • `PipelineStatus` includes `cancelled`. It was a real terminal state the interface could not name, so a cancelled run rendered as an unknown status.

    Fixed

    • Analytics hid a dataset behind any later failure. Dataset availability was inferred from the newest run's status, so one failed run after a successful one removed the pipeline from the picker even though its materialised rows were untouched.
    • Demo analytics had no datasets. Every seeded pipeline reported `status: null` and the picker filtered on `success`, so the demo showed the empty state no matter which pipeline had run. Demo run state is now derived from the execution history, exactly as the API derives it.
    • The chart builder read its field list from the first row only, so a key that first appeared in a later row could not be selected or charted. Column lists are now the union across the loaded rows, in both the config dialog and the dataset table.
    • `convert_type` steps created in the interface could not be saved: the form sent `to` while the API required `converted_to`. Accepting the alias keeps hand-written payloads working and the two vocabularies cannot drift apart again.
    • A workspace-scoped row could be created with a null workspace. The tenant middleware only resolves `X-Tenant-ID` when the header is present, and the write path stored that empty result, producing a row the owner could never read back - the read path had always fallen back to the owner's workspace. Writes now resolve the same way reads do.
    • JSON data sources are read with a line-delimited fallback, and the URL branch no longer calls `pd.compat.StringIO`, which pandas 2 removed.
    • Recent activity labelled every pipeline "completed", including runs that had failed, been cancelled or were still executing. The badge now carries the outcome and the title stays neutral.

    Docs

    • The pipeline API documents `has_dataset` and why dataset availability is not the latest run's status; the datasource API documents the `columns` field and the preview payload's column list.
  2. v1.0.6

    hardening

    October 11, 2026

    Resilient live execution telemetry

    Opening a pipeline run could greet the visitor with a wall of red console output - `Failed to establish real-time socket connection with pipeline runner` and `Disconnected from engine (Code: 1006)` - and mark a perfectly healthy run as failed. The socket was thrown out of the handshake whenever the Redis channel layer was unreachable, which is exactly what happens when the API runs outside Docker while the configuration still points Redis at the Compose service name; the browser can only describe a handshake that never completed as `1006`, so nothing in the message named the cause. Telemetry is now treated as a view of a run rather than a precondition for it: the socket authenticates from the same JWT cookie the REST API uses, is scoped to the caller's workspace, answers every rejection with an explicit close code, and automatically falls back to polling the API when no socket can be established. A transport outage no longer marks a run failed, and a dropped connection no longer reaches the console as an error. The release also stops the WebSocket endpoint from leaking one workspace's run log to another, closes cross-site socket hijacking, and adds cancellation from inside the live view.

    Added

    • Automatic fallback from streaming to polling. When the socket cannot be established - an unreachable channel layer, a blocked upgrade, an offline network - the run view reads status and log text from `GET /api/pipeline/executions/{id}/` and keeps updating, instead of showing an empty terminal. Reconnects use a bounded, jittered backoff so a backend outage cannot turn every open dashboard into a synchronised retry storm.
    • Cancel a run from its own live view. The viewer offers Cancel for a queued or running execution, so a long run no longer has to be stopped from a separate history table.
    • A connection state badge - `Live`, `Reconnecting`, `Polling` or `Offline` - plus a plain-language explanation whenever the transport degrades, so the reason is visible in the interface rather than only in the console.
    • `apps/users/asgi_auth.py` - WebSocket authentication from the HttpOnly `access` cookie. Channels' stock `AuthMiddlewareStack` reads Django's *session* cookie, which this application never issues, so every socket previously arrived anonymous and nothing stopped it subscribing to another workspace's run.
    • `common/asgi_origin.py` - an `Origin` check on the handshake, closing cross-site WebSocket hijacking. `ALLOWED_HOSTS` is honoured, `WEBSOCKET_ALLOWED_ORIGINS` lists the frontend origin when it differs from the API host, and local `DEBUG` remains permissive.
    • `CHANNEL_LAYER_URL` - point the Channels layer at a different Redis than Celery. The two have different failure semantics: a dead broker stops new runs, a dead channel layer only downgrades live telemetry.
    • `apps/pipeline/tests.py` - 22 tests over the socket contract: cookie authentication, workspace scoping, every rejection close code, a refused channel-layer subscription, group delivery, the liveness probe, and a malformed client frame. `common/tests.py` gained eight more covering the origin validator.

    Security

    • Any authenticated client could subscribe to any pipeline's telemetry by guessing its id; run logs and status were readable across workspaces. The consumer now resolves the pipeline and refuses a workspace the caller does not own or belong to, answering `4403 forbidden`, and `4404 not_found` for an unknown pipeline. Platform operators keep full reach for support.
    • WebSocket handshakes were not origin-checked. A browser attaches cookies to a socket based on the target origin, so any site could have opened the endpoint in a signed-in visitor's browser and read their telemetry. Untrusted origins are now refused before the route runs.

    Changed

    • The consumer accepts the socket *before* subscribing to the channel layer. Previously an unreachable Redis took the connection down during the handshake, which the browser can only report as `1006` with no reason attached; it now answers with an explicit `4500 telemetry_unavailable` close so the client can explain itself and switch to polling.
    • The Redis host is `localhost` in `.env` for host-based `manage.py runserver`, and docker-compose overrides it to `redis://redis:6379/0` for the containers. One file, both workflows - the previous value only resolved inside Docker, which is what produced the bare `1006` in local development.
    • The channel layer caps its connect timeout at two seconds, so an unreachable Redis fails fast and visibly instead of hanging each handshake.
    • Telemetry broadcasts are best-effort. A message that cannot be delivered is logged and dropped, so a Redis blip can no longer turn a successful cancel into a `500` or fail a pipeline step.
    • Cancelling a run now broadcasts `cancelled` rather than `failed`, and the viewer renders the two outcomes differently.
    • The live viewer is a bottom sheet on a phone and a centred dialog from `sm` upwards, caps itself at `92dvh`, locks background scroll, traps focus, closes on `Escape`, and exposes the log as a polite live region for screen readers.

    Fixed

    • A dropped telemetry socket marked the run as failed. The socket is not the run: status and log text live in the database and the task queue, so a transport failure now says so and leaves the execution status alone.
    • `Failed to establish real-time socket connection` and `Disconnected from engine (Code: 1006)` were logged at error level, which renders as a red overlay during development for a condition the client already handles. Transport observations are warnings now, and the view tells the visitor what happened.
    • The socket URL no longer hard-codes port 8000 from `window.location.hostname`. It derives from `NEXT_PUBLIC_WS_URL` or the API origin, so it works behind a reverse proxy and from another device on the network - a tablet on the LAN previously dialled its own hostname.
    • The viewer opened before the run was queued and could therefore attach to the previous execution. The queue response's `execution_id` is now carried through to the viewer, and a rejected run opens no viewer at all.
    • A half-open socket - one a proxy had silently severed - kept the view looking live. A heartbeat now probes the connection and forces a reconnect when no answer arrives.

    Docs

    • The Executions workflow page documents the connection states, the polling fallback, cancellation and the close codes, and the API reference states the socket's authentication and workspace rules.
  3. v1.0.5

    hardening

    October 11, 2026

    Demo access, data-source editing and menu dismissal

    Opening the public site with an empty browser no longer reports an error. The session probe answered 401 for a visitor who had never signed in, so the client treated a first-time reader as an expired session, attempted a token exchange that could not succeed, and logged the failure on every page load; the probe now answers 200 UNAUTHORIZED when there is no session and reserves 401 for a session that a refresh cookie can actually recover. Every visitor who clicked Live demo was likewise bounced straight to sign-in, because the demo only set a browser flag while the routing middleware - which runs on the server and can only read cookies - saw no session and treated /dashboard as protected. The demo now carries a routing cookie that grants nothing on its own, and opening a real sign-in form leaves the demo behind so a converting visitor talks to the real API. The release also finishes the data-source lifecycle: editing had no handler at all, the form always created instead of updating, and an API source could never be saved because it posted a field name the serializer did not have. Menus finally dismiss on an outside click, on Escape, and after an item is chosen.

    Added

    • Editing a data source, end to end: the row action opens the form pre-filled, the form issues a partial update, and the list refreshes in place. Deleting now asks for confirmation first, because pipelines built on the source lose their input.
    • A search field on the Pipelines directory, filtered against the collection the page already holds.
    • `apps/datasource/tests.py` - nine tests pinning the per-type requirement, the field-scoped rejection shape, partial updates that must not demand a re-upload, and workspace isolation.

    Changed

    • The demo mirrors its flag into a route cookie so server-side middleware lets a demo session reach `/dashboard`. The cookie carries no authority - the API never reads it - and is cleared when the demo ends or an account signs in.
    • Opening any real authentication form (sign in, sign up, password reset) ends an active demo session, so a visitor converting out of the demo hits the real backend rather than the in-memory adapter.
    • Creating, running or deleting a pipeline and creating, editing or deleting a data source now refresh the shared workspace collection in place instead of reloading the page, so scroll position and filters survive.
    • Every chart renderer picks a responsive height preset - 240/280/320 for standard widgets, 200/220 when compact - instead of a fixed pixel value, and its loading placeholder uses the same preset so the swap never shifts the layout.
    • Dropdown menus dismiss on an outside click, on `Escape`, and after any item is chosen, and their triggers expose `aria-haspopup`, `aria-expanded` and a per-row accessible name.
    • The workspace-setup and onboarding forms are fluid (`max-w-[420px]`/`max-w-[450px]`) rather than fixed-width, so they no longer overflow a phone, and the route skeleton uses the same 1400px container as the pages it precedes.

    Fixed

    • A stale `access` cookie could lock a visitor out of the whole application. The cookie is validated before any view body runs, so an unusable one answered `401` even on sign-in, sign-up, password reset and the token exchange - the endpoints that exist to recover from exactly that state - and the edge middleware, which reads the cookie's mere presence, then bounced the browser between the sign-in page and the dashboard. The cookie is now treated as anonymous when it cannot be validated, and the credentials are cleared whenever the session is known to be over. A bad token sent deliberately in the `Authorization` header is still rejected.
    • A first-time visitor to the public site reported a failed request in the console. `GET /api/auth/session_status/` answered `401` for a caller with no session, which the client read as an expired session: it attempted a token exchange, got `400 refresh_token_missing`, and logged the result. The probe now answers `200 UNAUTHORIZED` when there is no session at all, and reserves `401` for a rejected access token that a present refresh cookie can actually recover.
    • The bootstrap no longer logs an expected "not signed in" outcome as an error, so a signed-out visitor produces a clean console. Only a connectivity failure or an unclassified fault is reported.
    • The live demo could not open. `/demo` set only a `localStorage` flag, and the route middleware redirected `/dashboard` to sign-in because it saw no session cookie.
    • Editing a data source did nothing: the dropdown item had no handler and the page discarded the row id it was given, while the form always created a new source even when the button read Update.
    • Creating an API data source always failed validation. The form posted `source_url` and `token` while the API expects `endpoint_url` and `auth_token`, and the auth-token input declared `type="url"`, which rejected any token that was not a URL.
    • Renaming a stored CSV or JSON source returned `400 File is required for CSV or JSON data sources`. A partial update now treats an absent key as keep the stored value, and reports a genuine failure against the field that needs attention instead of the whole form.
    • The settings page reported failures to the browser console only; it now logs through `logApiError` with the request id, so a reported problem can be traced to a server log line.

    Docs

    • The Data Sources workflow page documents editing and deleting a source, including what happens to the pipelines that depend on it.
  4. v1.0.4

    hardening

    October 11, 2026

    Guided demo and dependency-outage handling

    A visitor can now evaluate the whole product without creating an account: the live demo opens a seeded workspace and answers every request from an in-memory API that mirrors the real one, including writes. On the server, a dependency that is simply down - the database, the cache, the broker, an object store - is reported as a retryable 503 with a Retry-After hint instead of an opaque 500, and that distinction now flows all the way into the user-facing copy. The release also closes a set of landing-page dead ends: header links pointed at anchors that did not exist, and every pricing button was inert.

    Added

    • Live demo: `/demo` signs the browser into a seeded workspace (four sources, four pipelines, fourteen runs, a materialised dataset and a full chart dashboard) with no account. The demo is a runtime flag, so one deployment serves both the demo and the real product.
    • `src/lib/demoApi.ts` - a complete in-memory API behind the same adapter interface as the network: identity, workspace, members, sources, pipelines, executions with log text, dataset rows, reports, plan catalogue and the platform control centre. Writes mutate real state and are rebuilt on reload.
    • A persistent demo banner on every authenticated screen with a one-click Exit demo that clears the session and returns to the marketing site.
    • Dependency-outage classification. `database_unavailable` and `dependency_unavailable` report a downed dependency as `503` with `retry_after` in the envelope and a `Retry-After` header, both from the DRF exception handler and from a new middleware that covers failures raised before a view (workspace resolution, admin session lookups).
    • Create an additional workspace from the workspace switcher. The dialog posts to the same endpoint sign-up uses, then refreshes the switcher in place, so a contractor, side project or second brand gets its own sources, pipelines, members and plan without sharing a namespace.

    Security

    • The dependency middleware re-raises anything that is not a recognised outage, and stays silent while `DEBUG` is on, so a genuine defect keeps its traceback and its existing handling rather than being disguised as an availability problem.
    • Demo writes are guarded the same way the real API guards them: cancelling a finished run, modifying a platform operator account and subscribing to a paid plan without a billing profile each return the canonical error envelope instead of succeeding.

    Changed

    • Landing-page copy and calls to action now lead with the demo: the hero primary action is Launch live demo, the pricing section offers a no-signup route beneath the tiers, and the header carries a Live demo action next to Sign In.
    • The demo session is a platform operator, so the staff-only control centre - fleet health, workspace inventory, account directory and the cross-workspace execution feed - is explorable without an account. Account-modifying writes stay refused.
    • Staff-only routes (`/platform`, `/tenants`, `/users`) are guarded on the client as well as in the API: a signed-in customer who opens one by hand is returned to their dashboard instead of seeing a screen of failed admin requests.
    • The build-time demo mode is gone. `NEXT_PUBLIC_APP_ENV=demo` pointed the client at a `/mock` origin that no longer exists, so any deployment that set it would have broken; the demo is now purely the runtime session and the unused `NEXT_PUBLIC_DEMO_API_URL` setting was removed.
    • KPI bands are denser: metric tiles are two-up from the smallest phone and four-up from a tablet, with the icon and value scaled to match, so a whole band fits above the fold on a laptop.
    • User-facing copy now says how long to wait whenever the server sends a retry hint, instead of a bare "try again shortly".

    Fixed

    • Header dropdown links pointed at `#analytics`, `#dashboard`, `#reports` and `#settings`, none of which exist on any page. Every entry now resolves to a real section or route, and the anchors are site-absolute so they also work from `/privacy` and `/terms`.
    • Every pricing tier button was inert - a `Button` with no link around it. Starter and Pro now open sign-up, Enterprise opens contact, and the tier cards are focusable links.
    • A throttled or unavailable service no longer makes the client sign the user out: `503` is treated as a connectivity event, so the session is held and a retry is offered.
    • The candlestick chart was pinned to a fixed 280px canvas on every device while the Recharts widgets scaled from 240px to 320px. It now sizes from the same responsive wrapper as the rest of the chart engine.
    • The dashboard's top row declared five grid columns and filled two, so roughly two-thirds of the row was blank on a laptop. It is now a 2:1 split that fills the row at every width.
    • The workspace switcher's `+` button carried a tooltip and no action. It now opens a create-workspace dialog that posts to the same endpoint sign-up uses and refreshes the switcher without a reload.

    Docs

    • The documentation index gained a Try it without an account section covering what the demo includes, what it writes, and how to leave it.
    • API conventions lists `database_unavailable` and `dependency_unavailable` with their status, and Troubleshooting explains what an all-503 API means and why the session is deliberately kept.
    • Workspaces & Members gained a Creating another workspace walkthrough, and the administration guide no longer claims a new workspace is auto-provisioned on Free - it starts plan-less and the owner chooses, which is what the API actually does.
  5. v1.0.3

    hardening

    October 10, 2026

    Production error handling

    Every failure the API can produce now arrives in one predictable shape, carries a correlation id, and is written to a structured log. The frontend gained real error boundaries, a shared error reader that understands both the new envelope and the old response shapes, and visible messages where actions used to fail silently. The release also fixes a crash-on-startup in the development server, a broken cancel action, a storage leak on data-source deletion, and a plan catalogue that could show invented prices during an outage.

    Added

    • Canonical error envelope `{"error": {"code", "message", "fields", "request_id"}}` for every failure — serializer validation, authentication, permission, throttling, not found and unexpected exceptions all render through one DRF exception handler.
    • `X-Request-ID` on every request and response. A sane id sent by an upstream proxy is reused, otherwise one is generated; the id appears in error bodies, in the response header and on every log line.
    • Project-wide JSON handlers for `400`, `403`, `404` and `500` that serve the same envelope when a request never resolved to a view, so clients parse one shape on every failure path.
    • `429` responses now include `error.retry_after` in seconds, so a client can count down instead of guessing.
    • Structured logging (`LOGGING`) with a request-id filter, a console handler for every level, and `mail_admins` alerting for unhandled server errors once `DEBUG` is off and `ADMINS` is configured.
    • Route-level error boundaries: `app/error.tsx` for the application, `app/global-error.tsx` for a failure in the root layout, and a shared `ErrorPanel` with a retry action and the request id for support.
    • A dashboard route skeleton so navigation shows the shape of the page immediately rather than a frozen view.
    • `useApiError()` surfaces an API failure as a toast and keeps a request-id-tagged console line, so a user-visible failure is never silent.
    • The API origin is environment driven in every mode, including production, through `NEXT_PUBLIC_API_URL` and `NEXT_PUBLIC_DEMO_API_URL`.

    Security

    • The data-source preview endpoint no longer returns the raw exception text, which could expose file paths and parser internals; malformed uploads now answer `400` with a stable code.
    • The control-centre health probe no longer returns the database driver's message, which can carry host names and credentials. The detail is logged instead.
    • Rejected refresh-token exchanges are logged with a warning rather than failing silently.

    Changed

    • Rate limiting degrades open when the cache tier is unreachable instead of failing the request: a Redis outage used to turn every throttled endpoint, sign-in included, into a `500`. The trade-off is an unmetered window while the cache is down, logged at error level and reported by `/api/admin/system-health/`.
    • The frontend error reader understands the canonical envelope and the legacy `detail` / `error` / `non_field_errors` / raw-field shapes, so a response from an older deployment still renders a sensible message.
    • The auth bootstrap now distinguishes "the server said no" from "the server could not be reached". A dropped connection or a `5xx` no longer signs the user out or discards the selected workspace; it offers a retry instead.
    • Request timeout raised from 15s to 20s so large file uploads are not cut off mid-flight.

    Fixed

    • `manage.py runserver` crashed on start-up with `AssertionError: The thread-based exporter can't be safely used when django's autoreload is active`. The Prometheus port-range exporter is no longer enabled; the URL exporter at `/metrics/`, which `prometheus.yml` already scrapes, exposes the same registry.
    • Cancelling a pending or running execution raised `NameError: name 'timezone' is not defined` and returned `500`. The `cancel` action now completes and broadcasts the cancellation over the socket.
    • Deleting a data source left its uploaded file on disk forever: the app config connecting the cleanup signal was named `app.py`, a filename Django never loads. It is now `apps.py` and the receiver runs.
    • Queueing a run while the Celery broker was unreachable left the execution stuck as `pending`. It now fails fast, records the reason on the run, and answers `503` with a retryable message.
    • Previewing a dataset whose Parquet artifact had been removed returned `500`. It now answers `410` and tells the operator to re-run the pipeline.
    • Invalid `start` / `limit` values on the dataset rows endpoint returned a bare message; the bounds are now validated together and the accepted range is stated in the response.
    • The onboarding pricing page fell back to a locally invented plan matrix when the catalogue was unreachable, showing prices and features the backend does not sell. It now shows a retryable error.
    • Actions that used to fail silently now report: run, cancel, retry, delete and dataset preview on the pipeline detail view, plus the dashboard overview, tenant reports, settings save, plan change, data-source upload and creation, pipeline save and pipeline delete.
    • A failed pipeline save or edit closed the form and discarded the entered configuration. The editor now stays open with the error shown.
    • Data-source list queries could return rows with no workspace. The queryset now returns nothing rather than falling back to an unfiltered lookup.
    • The dashboard raised an unhandled promise rejection when its metrics failed to load, then rendered zeroed cards as if the numbers were real. It now keeps a visible error panel with a retry action.
    • Reports and Analytics degraded to a transient toast when their data failed to load, leaving empty cards and a blank canvas behind it. Both now keep a persistent error panel with a retry action and the request id.
    • A workspace bootstrap where one endpoint failed silently emptied that collection. Each collection now logs its own failure with the request id, so an empty list is never mistaken for there being no data.
    • An old-style single-field error body rendered as `Run time: Run time must be set when scheduling is enabled.`, repeating the sentence. The field label is now only prefixed when several fields fail at once.

    Docs

    • Troubleshooting gained a server-failure playbook: the runserver/autoreloader start-up crash, signing in while the cache is down, and how to quote the request id from a 5xx to support.
    • API conventions document the canonical error envelope, the full error-code registry with the status each code uses, and the request-correlation header.
    • The `.env.example` file in both applications and the backend README now document `ADMINS`, `SERVER_EMAIL`, `LOG_LEVEL` and `NEXT_PUBLIC_DEMO_API_URL`, alongside the error contract and observability model.

    Removed

    • Removed the unused `PROMETHEUS_METRICS_EXPORT_PORT_RANGE` setting that caused the development-server crash.
    • Removed the dead `apps/tenant/signals.py` module. It was never connected, and owner membership is created by the workspace serializer.
  6. v1.0.2

    October 10, 2026

    Account recovery and control-centre depth

    Account recovery is now a real, tested flow instead of a dead link, operators get adoption and attention signals in one screen, and the whole dashboard shell works from a phone to a wide monitor. Every administration view is deep-linkable, so a filtered directory or execution feed can be shared or opened straight from a control-centre tile. Scheduled token blacklisting is switched on, so signing out and changing a password genuinely revoke access.

    Added

    • Password recovery: `POST /api/auth/password-reset/` emails a single-use link (24 hour expiry, configurable through `PASSWORD_RESET_TIMEOUT`) and `POST /api/auth/password-reset/confirm/` sets the new password after validating it against the password policy.
    • The recovery request endpoint answers identically for known and unknown addresses, so it cannot be used to discover which emails have accounts.
    • New `/auth/forgot-password` and `/auth/reset-password` screens, linked from the sign-in form, with client-side confirmation matching, an expired-link state and an automatic redirect back to sign in.
    • SMTP configuration is now environment driven (`EMAIL_*`, `DEFAULT_FROM_EMAIL`). While `DEBUG=True` the console backend prints reset links to the server log, so recovery works locally with no mail provider.
    • Control centre: a plan mix donut showing how the fleet splits across plans, and a `Needs attention` grid covering suspended workspaces, workspaces without a plan, dormant workspaces, locked accounts, queued runs and failures in the last 24 hours.
    • Control centre: storage is now split into uploaded sources, materialised datasets and the tracked total, instead of a single combined figure.
    • 20 new backend tests — 16 covering password recovery (link generation, delivery, single use, expiry, weak passwords, token revocation) and 4 covering the control centre plan mix and attention counters. The suite is now 54 tests.
    • Every administration directory is deep-linkable: search, status, plan, activity, sort order and page are held in the query string, so a filtered view can be bookmarked, handed to another operator, or opened straight from a control-centre tile.
    • The workspace directory gained a plan filter (including `No plan`, for workspaces that cannot run anything yet), a `Never run` activity toggle, and a sort order covering newest, name, most members and most runs.
    • The account directory gained a sort order covering newest, name, most recently active and email.
    • The control centre execution feed is now filterable by status and by time window (any time, 24 hours, 7 days, 30 days), and reports how many runs match across the fleet before listing the newest page of them.
    • Attention tiles land on a pre-filtered surface instead of an unfiltered list: suspended, planless and dormant workspaces open the workspace directory with that filter applied, locked accounts open the account directory, and the queue and failure counters filter the execution feed in place.
    • Filter chips above each directory show exactly which filters are active, each one removable on its own, with a single `Clear all` that preserves the chosen sort order.
    • 8 new backend tests cover the directory filters: named plan, planless, status, dormant workspaces, ordering allowlists, and the activity status, window and workspace filters. The suite is now 62 tests.
    • Every chart canvas is responsive. Charts grow with the viewport — 240px on a phone, 280px on a tablet, 320px from `lg` — so a laptop shows meaningfully more of the plot instead of the same fixed box.
    • Charts carry one accessible name (`Bar chart: records by region`), so assistive technology no longer has to guess at an unlabelled SVG node tree.
    • The header bell is now a live alert counter: it shows how many pipelines are running or last failed, links straight to the pipeline directory, and drops its badge when nothing needs attention.
    • The dashboard refresh reloads the headline metrics and every shared workspace collection in one pass, so the counts in the header, the tables and the charts can never disagree.
    • Search and social metadata: a generated Open Graph card (no committed binary asset), a title template, a canonical origin shared with the sitemap and robots, and `robots`/`sitemap` routes that enumerate the documentation from the same registry that renders its navigation.

    Security

    • Enabled `rest_framework_simplejwt.token_blacklist`. Refresh-token rotation, sign-out and password resets now actually revoke tokens; previously the blacklist call was silently discarded because the app was not installed.
    • Resetting a password revokes every outstanding refresh token for the account, so a stolen session cannot outlive the credential change.

    Changed

    • The dashboard rail is persistent from the `lg` breakpoint upward and becomes an overlay drawer with a scrim below it, triggered from the header. Tablets and phones keep the full width for charts and tables.
    • Only the main region scrolls, so the header and navigation stay in place while long tables and chart dashboards scroll.
    • The authentication shell collapses to a single column below `lg` and the forms are fluid (`max-w-[380px]`) instead of a fixed 380px that overflowed small screens.
    • The chart configuration modal shows its live preview on tablets and phones as well, stacked under the form, instead of hiding it below `lg`.
    • `Button` now defaults to `type="button"`, so a button placed inside a form can no longer submit it accidentally.
    • In-page alerts use the semantic success, warning and error tokens instead of raw Tailwind palette classes, so they follow the theme.
    • Spacing tightened across the dashboard: section stacks dropped from 24px to 16px, empty states normalised to a single scale, and the onboarding cards and pricing grid use the compact padding.
    • Removed the unused `mobile:` and `laptop:` spacing overrides on the reports grid, which duplicated the standard gap at two breakpoints.
    • The page masthead is a single wrapping row — title and description on the left, page actions on the right — with the title scaled to the viewport and the duplicated bottom margin removed.
    • Removed the remaining legacy `mobile:` and `laptop:` breakpoint overrides in favour of standard Tailwind breakpoints, so layouts respond at predictable widths on phones, tablets and laptops.
    • The control centre fetches the execution feed separately from the headline summary, so changing a feed filter no longer reloads the fleet metrics and charts.
    • Interactive controls that only carried a tooltip now have accessible names: each active filter chip reads as `Remove filter: …`, the directory `Clear all` and feed `Clear` controls are labelled, and the theme toggle announces the theme it switches to.
    • The k6 load test authenticates once and exercises the operator read path — pipelines, executions and the dashboard summary — with failure-rate and latency thresholds, replacing a loop that hit an unauthenticated endpoint.
    • Landing-page copy now describes what the platform actually does — file and REST ingestion, declarative transformation steps, Parquet datasets, CSV and JSON export, and workspace-level isolation — replacing claims for a 150+ connector catalogue, change data capture, SCIM and SAML SSO, VPC peering and SOC 2 certification that did not match the product or its own documentation.

    Fixed

    • The analytics dataset header read a `total_rows` field the API does not return, which broke the production build of that route. It now reports the rows actually loaded.
    • `Forget password?` on the sign-in screen pointed at the contact page. It now opens the recovery flow.
    • `formatShortDate` formats date-only values in UTC, so chart axes and tooltips no longer slip a day for viewers behind UTC.
    • The recent-activity and pipeline-status cards used a light grey border that stayed light in dark mode; both now use the border token.
    • The candlestick chart forced a light ApexCharts tooltip, so it stayed bright in dark mode; the canvas options now follow the active theme.
    • The data management notice used a hardcoded amber palette with `dark:` overrides instead of the warning token.
    • The chart card skeleton was a fixed 240px while the chart itself rendered at 280px, so cards jumped when data arrived. The placeholder now uses the same canvas scale as the chart, and the compact dashboard widgets pass their exact height.
    • The landing product showcase referenced five `/landing/*.png` screenshots that were never added, so its app window rendered broken images. Each surface now renders a preview built from the design system, like the rest of the page.
    • The showcase's mock browser bar displayed an unrelated third-party domain; it now shows the product's own origin.
    • The route middleware redirected the generated metadata image to the sign-in page for logged-out visitors, because that route is served without a file extension; it is now excluded alongside `robots.txt` and `sitemap.xml`, so link previews resolve.

    Docs

    • The API reference documents both password recovery endpoints, their payloads and the expiry rules, and the user guide gained a password and recovery section.
    • The platform administration guide now describes the panels the control centre actually renders — platform status, attention and adoption, and execution activity — instead of retired placeholders.
    • New `Account Directory` administration page covering filters, columns, account status, primary roles and operator actions, and the workspace directory page now documents its real columns, status values and filters.
    • The API reference gained a directory query-parameter reference for the workspace, account and activity endpoints, and documents the `plan_mix` and `attention` blocks returned by `GET /api/admin/overview/`.

    Removed

    • Removed `/cookies` and `/about` from the route configuration; neither had a page behind it, and cleared three leftover build-trace scripts from the repository root.
    • Deleted fifteen module files that no route or component imported: the retired `platform/*` panel set, the duplicate `admin/*` widgets, `StatsCard`, `StatusBadge`, `TableComponent`, `SecuritySettings`, `integrations` and a stale mock fixture. Everything they rendered is already covered by the live components.
  7. v1.0.1

    hardening

    October 10, 2026

    Launch hardening

    Pre-launch correctness and integrity pass. Every chart now plots the field the API actually returns, report exports carry the rows on screen, plan activation is enforced server-side, workspace settings are live instead of mocked, and the reporting layer runs a constant number of queries per request.

    Added

    • Reporting window selector now filters real data: `/api/reports/tenant-reports/?range=7d|30d|90d` shortens or lengthens every trend series.
    • Platform operators can provision a plan from the workspace directory. Paid plans are provisioned here and nowhere else.
    • Workspace rename and delete endpoints, with owner/admin authorization on update and an owner-only rule for deletion.

    Security

    • Paid plan activation is refused for self-service callers with HTTP 402. Previously any workspace member could grant themselves the Enterprise plan for free.
    • Any workspace member could rename or deactivate the workspace they belonged to. Profile changes are now restricted to owners and workspace admins.
    • Workspace resolution in `get_current_tenant_id` now applies the same staff bypass as the tenancy middleware and always returns an integer id, removing a string/int mismatch between the header path and the fallback path.

    Performance

    • Report aggregation collapsed from per-row queries to one aggregate per metric: execution trends, data-source distribution, pipeline performance and the dashboard overview each run a fixed number of statements regardless of workspace size.

    Changed

    • Workspace navigation gained Team and Workspace entries. Active highlighting uses longest-prefix matching so a nested route never lights up two items.
    • Billing tab shows the live plan and renewal date, and offers a working return to the free plan, instead of a fabricated invoice table and a dead payment-method row.

    Fixed

    • Pipeline performance chart plotted a `failed` field the API never sends (it returns `errors`), so the failure series was always empty. The series, its legend and its colour are now correct and visually distinct.
    • Report exports downloaded randomly generated numbers labelled as pipeline trends. Each report now exports the exact rows it is rendering.
    • Dashboard header search input contained a stray backtick inside its class list, emitting an invalid utility class and conflicting padding.
    • Workspace preferences was a static mock with hardcoded values and non-functional buttons. It now loads the live workspace profile, saves name, type and description, and deletes the workspace with an owner-only guard.
    • The Display tab theme switch toggled a CSS class the theme system no longer reads, so the choice was silently discarded. It now drives the shared theme context.
    • Duplicate state spreads and a misspelled save confirmation were removed from the settings screen.
    • Dashboard summary returned a server error when any execution had the `cancelled` status, because the status map only knew four of the five choices.
    • Notification settings built Tailwind class names by interpolation, so the generated classes never existed and the icons rendered unstyled. Replaced with a static tone map.
    • Several panels paired a green light-mode surface with a blue dark-mode surface; they now use one semantic token each.
    • The pricing plan catalogue returned JSON-object features and omitted the fields the pricing cards read, which could crash the onboarding page. The API now returns a normalised plan contract.

    Docs

    • API reference documents the workspace invite, plan and reporting-window contracts; the administration guide covers plan provisioning and the audit boundary.
    • Regression suite grew from 19 to 34 tests, covering workspace permissions, plan provisioning, entitlement gating, reporting windows and cancelled executions.

    Removed

    • Debug print statements removed from the reporting aggregation layer.
  8. v1.0.0

    major

    October 10, 2026

    General availability

    First production release. Ingestion, declarative transformation, materialized datasets, the chart engine and the platform control centre are feature complete, hardened and documented end to end.

    Added

    • Platform control centre with fleet-wide tenant, user, pipeline and execution telemetry.
    • Superuser administration API (`/api/admin/`) exposing overview metrics, tenant directory, user directory and the cross-tenant activity feed.
    • Tenant directory and user directory screens wired to live data, replacing the previous placeholder tables.
    • Case study page describing the architecture, the engineering decisions behind it and the measured outcomes.
    • Product changelog page with per-release change classification (added, changed, fixed, security, performance, docs).
    • Comprehensive documentation set: introduction, quickstart, core concepts, eight workflow guides, three administration guides, five API reference pages, troubleshooting and release notes.
    • Client-side documentation search, grouped sidebar navigation, on-page table of contents and previous/next paging.
    • Copy-to-clipboard support on every code sample and on-page review dates on every documentation page.
    • Shared chart theme module so every Recharts and ApexCharts surface renders from one palette, one tooltip style and one axis style.
    • Live chart preview inside the chart configuration modal for bar, line, pie, donut and candlestick charts.

    Security

    • Authenticated users can no longer enumerate other workspaces through the tenant endpoints; listing and detail access is scoped to workspace membership.
    • Pipeline execution and dataset row endpoints verify workspace ownership before running, closing an object-reference vulnerability.
    • Reports querysets are filtered by the resolved workspace instead of returning every tenant's executions.
    • Registration and login are rate limited, mitigating credential stuffing.
    • Production settings enforce secure cookies, HSTS, no-sniff, referrer policy and clickjacking protection.

    Performance

    • Dataset row reads stream Parquet batches and stop as soon as the requested window is filled.

    Changed

    • Environment configuration is fully variable driven: secret key, allowed hosts, CORS/CSRF origins, database backend and cache are all injectable.
    • Boolean settings fail fast with a clear error instead of being silently coerced to `False`.
    • Documentation, dashboard and administrative surfaces share one compact spacing scale and one token vocabulary.

    Fixed

    • Pipeline API no longer fails with a server error: the serializer referenced a non-existent `username` field on the custom email-based user model.
    • Pipelines now store an ordered list of transformation steps, matching the contract the interface and the documentation describe.
    • Transformation engine rewritten to accept dictionary steps, operator aliases, text operators and nullable column types.
    • Design tokens `muted`, `card`, `input`, `ring`, `success`, `warning` and `error` are now defined, fixing roughly 160 utility classes that resolved to nothing.
    • Dark mode is driven by the `data-theme` attribute that the application actually toggles, instead of a media query that never matched.
    • Checkbox component is a properly controlled input and no longer ignores its `checked` prop.
    • Custom report builder renders real datasets with RFC 4180 compliant CSV escaping and working JSON export.
    • Chart rows are single-column on small screens instead of relying on fixed horizontal padding that clipped on tablets.
    • Footer and documentation links were repointed at the real routes; every internal link now resolves to a page that exists.
    • Chart configuration errors are reported against the field that actually failed; validation messages previously described a different field.
    • Pie and donut charts aggregate anything beyond eight slices into a single `Other` bucket instead of rendering unreadable labels.
    • Candlestick charts sort by date and parse numeric strings, so out-of-order or string-typed feeds render correctly.

    Docs

    • Every feature area documented with worked examples, error references and API contracts.
  9. v0.9.0

    hardening

    September 22, 2026

    Hardening and analytics rebuild

    Internal preview. Charts received dedicated engineering investment — a shared theme, empty and loading states, responsive layouts and a real report builder.

    Added

    • Shared chart primitives: theme tokens, formatted axes, gradient fills and consistent tooltips.
    • Chart cards with skeleton loading states and descriptive empty states.
    • Responsive dashboards that reflow from a four column grid on desktop to a single column on phones.

    Performance

    • Reduced chart re-render work by memoising derived series.

    Fixed

    • Candlestick charts previously threw when the source dataset lacked an open/close column pair.
  10. v0.8.0

    preview

    August 30, 2026

    Multi-tenant foundations

    Internal preview. Introduced workspaces, role-based membership, cookie-based JWT sessions and the data source catalogue.

    Added

    • Workspace (tenant) model with owner, membership roles and per-workspace header resolution.
    • Cookie-based JWT authentication with silent refresh and server-side logout.
    • Data source catalogue for CSV, JSON and REST endpoints with sample extraction.
    • Pipeline scheduling with hourly, daily, weekly and monthly frequencies.
    • Execution history with per-run logs streamed over WebSocket.