Release notes

Changelog

Every notable change since v0.1, in reverse chronological order. Each release is anchored — link to a specific version with /landing/changelog/#v0-13-0.

v0.1.4

Webhook target checks, enumeration-safe sign-up, API validation, and organisation and dashboard access fixes.

**Webhook target checks, enumeration-safe sign-up, API validation, and

organisation and dashboard access fixes.**

Upgrading needs no migration. Two behaviour changes to be aware of:

webhooks pointing at private or internal addresses stop being delivered

(each attempt is logged as failed), and new sign-ups are no longer

signed in until they click the verification link.

Security

  • Webhooks: target URLs must be http(s) and resolve only to public addresses. Loopback, private networks (RFC 1918, fc00::/7), link-local (incl. 169.254.169.254), CGNAT, multicast, reserved and IPv4-in-IPv6 forms are refused, in any notation (decimal, octal, hex), and cloud metadata endpoints are always refused. The check runs when a webhook is saved (Settings pane toast, API 400) and again before every delivery; a blocked delivery is logged as failed and nothing is sent. Requests go to the exact address that passed the check (DNS-rebinding safe, TLS still verified against the hostname), ignore proxy settings, and never follow redirects (a 3xx is logged as a failed delivery). New setting WEBHOOK_ALLOW_PRIVATE_TARGETS (env var, default off) allows private targets for local testing only (apps/api/url_safety.py).
  • Sign-up: the registration form no longer says "A user with that email already exists." Signing up with a registered address creates nothing and emails the owner ("someone tried to sign up with your address; sign in or reset your password"); the visitor sees the same "Check your email" page as a new sign-up (/accounts/register/done/). New accounts are signed in by the verification link instead of right after sign-up. The password-reset confirmation page now reads "If an account exists for the email address you entered…".
  • Organisations: only owners can grant the owner role, by role change or invitation; admins can no longer promote themselves. The last owner can't step down until someone else is an owner.
  • Organisations: pending invitations on the Members page are listed for owners and admins only.
  • Staff-only pages: the CRM, eCommerce and SaaS dashboards, the charts showcase, the onboarding wizard and the forms, widgets, datatable, API-docs and maps showcase pages are staff-only on the server, not just hidden in the sidebar (anonymous → login, non-staff → 403). New NavItemAccessMixin / @nav_access_required enforce a page's NavItem.requires_staff flag. The marketing pages and the coming-soon, maintenance and 503 pages stay public by design. Give is_staff to any account that should keep these pages.

Fixed

  • API: POST /api/v1/products/ without category_id, or with an unknown one, returns 400 {"detail": "category_id: …"} instead of a 500. Products created through the API get a unique slug from their name (the second one used to fail with a 500). Model rules (choices, lengths, non-negative stock, decimal size, valid email) are checked on product create, customer create/PATCH and webhook create, and PATCHing a customer field to null is a 400.
  • Docs: webhook delivery was documented as "retried with exponential backoff"; it is one attempt per event and webhook (5-second timeout), every attempt in the delivery log. The API docs, README and Settings copy now say so and explain how to add retries with a task queue.
  • Confirmations and auto-submit under the CSP: inline on*= handlers never ran under the nonce-only Content-Security-Policy, so delete/void confirmations were skipped (forms submitted straight away), the language picker and organisation role select did nothing, and the toast demo and offline "Try again" were dead. Templates now use data-confirm, data-autosubmit and data-reload, handled by delegated listeners in static/js/app.js.
  • MultiSelect widget: chips and options render again. Widget JSON in Alpine x-data attributes (and the json_dumps filter) is no longer marked safe, so a label with a quote can't break the attribute.
  • Table search fires on any input (paste, autofill), not only on key presses.
  • Command palette: label matches rank above keyword-only matches, so "ord" opens Orders rather than the eCommerce dashboard.
  • E2E suite: the cache is cleared between tests so the login throttle no longer fails the second half of the run, and tests that had drifted from the UI and seed data (datatable counts, CSV download, archived customer, mail recipient combobox, Spanish sidebar label) are updated.
  • Version synced to 0.1.4 in pyproject.toml, package.json, and the lockfiles.
v0.1.3

Organisation roles, store edits, uploads, and a few smaller access refinements.

**Organisation roles, store edits, uploads, and a few smaller access

refinements.**

Upgrading needs no migration. If you already have uploads, move them

into the new media/ folder (see "Uploads" below).

Changed

  • Organisations: settings, invite, change role, remove member, and cancel invitation check your role in the organisation you're managing (the one in the URL), not the one currently active in your session. HasRoleMixin reads self.membership when the view has resolved a target organisation, and request.organization_role now follows set_active_organization() (apps/organizations/).
  • Orders and Products: anyone signed in can still list and open them; creating, editing, and bulk actions (mark paid/shipped, cancel, publish, archive) are staff-only. TableView has a new bulk_actions_require_staff flag. In the API, product create/delete and order status/delete need a staff-owned token; reads are unchanged.
  • Uploads: MEDIA_ROOT is now <project>/media/ and MEDIA_URL is /media/ (neither was set before, so uploads were written to the project root and image URLs didn't load). /media/ serves avatars, customer avatars, product images, and org logos to signed-in users only (apps/core/media.py); Files app uploads are only available through their owner-checked download link. The nginx recipe no longer aliases /media/. Existing installs: move avatars/, customers/, products/, org_logos/, and user_files/ from the project root into media/, and remove the location /media/ alias from your nginx vhost.
  • Health check: /__health/ reports which checks failed without including error text; details go to the apps.core.health log.
  • Metrics: with METRICS_ENABLED, /__metrics/ needs Authorization: Bearer $METRICS_TOKEN (new setting). It is now served at /__metrics/ as documented, rather than /__metrics/metrics.
  • Webhooks: invoice.* events go only to webhooks owned by active staff accounts, and only staff can subscribe to them. Webhooks of deactivated accounts receive nothing.
  • Team profiles: project names, customers, tasks, project activity, and project teammates on /people/<username>/ tabs are shown to staff only, like the Projects pages.
  • Show-once secrets: a new API token, webhook secret, or set of 2FA recovery codes leaves the session when first shown, and expires after five minutes if it's never shown (apps/core/one_time.py, ONE_TIME_SECRET_TTL).
  • Version synced to 0.1.3 in pyproject.toml, package.json, and the lockfiles.
v0.1.2

Tighter access to personal data for non-staff accounts.

Tighter access to personal data for non-staff accounts.

Any signed-in account (including a self-registered one) could see some

details that the rest of the dashboard keeps to staff. This release

lines those surfaces up with the existing staff checks. Upgrading needs

no migration.

Changed

  • Activity timeline (/activity/) is staff-only on the server, not just hidden in the sidebar. Non-staff get a 403 for the page, its filters, and its CSV/XLSX/PDF exports (apps/activity/views.py).
  • API: the Customers and Invoices endpoints need a token owned by a staff account (new StaffKeyAuth in apps/api/auth.py), matching the staff-only Customers and Invoices pages. Tokens stop working when their owner is deactivated. Products, Orders, and your own Notifications and Webhooks are unchanged.
  • Team directory (/people/): email addresses are shown only to staff and on your own profile, and searching by email is staff-only. Names, titles, roles, and bios are unchanged.
  • Orders: the Overview dashboard's "Recent orders" widget and the Orders list show the customer's email address to staff only; the customer name still shows for everyone (apps/customers/permissions.py).
  • Activity and audit metadata: record() and record_audit() replace the value of any password, hash, token, API key, secret, OTP/recovery code, authorization, or env-style key with [redacted] before saving (apps/core/redaction.py). The shipped code never passed such values; this keeps them out if you extend the logging.
  • Version synced to 0.1.2 in pyproject.toml, package.json, and the lockfiles.
v0.1.1

Higher-contrast dark mode.

Higher-contrast dark mode.

Changed

  • Reworked the .dark palette in static_src/css/input.css with bigger lightness steps. Background L 0.145 → 0.075, card 0.17 → 0.19, popover → 0.21, secondary / muted / accent 0.215 → 0.235, border / input 0.265 → 0.28. Sidebar 0.205 → 0.11 (now darker than background, matching the Apex Next.js family), sidebar-accent 0.265 → 0.20, sidebar-border 0.265 → 0.26. Zenith's distinctive purple sidebar-primary and near-white primary preserved. Light mode untouched.
  • Compiled static/css/app.css regenerated via npm run build.
v0.1.0

Phase 1 — Skeleton fork. Forked [dashboardpack-apex-django](https://github.com/puikinsh/dashboardpack-apex-django) as the starting point for the Zenith Dashboard Django Edition. Backend, 27 apps, full test suite, and architecture carry over verbatim from apex; visual presentation rebrands to Zenith's pure achromatic OKLCh palette ("shadcn default, elevated").

Phase 1 — Skeleton fork. Forked

dashboardpack-apex-django

as the starting point for the Zenith Dashboard Django Edition.

Backend, 27 apps, full test suite, and architecture carry over

verbatim from apex; visual presentation rebrands to Zenith's

pure achromatic OKLCh palette ("shadcn default, elevated").

Added

  • Phase 0 recon doc cataloguing Zenith's 67 pages, 44 UI primitives, 10 chart types, and 6 color presets — input to the 15-phase port plan (docs/superpowers/specs/2026-05-14-phase0-zenith-recon.md).
  • Phase 1 design doc covering the rename strategy and token swap (docs/superpowers/specs/2026-05-14-phase1-skeleton-design.md).

Changed

  • Renamed Django package apex → zenith across the codebase (settings, ASGI/WSGI entry points, deploy configs).
  • Project metadata, README, CLAUDE.md, CUSTOMIZE.md rebranded.
  • Alpine.js factory names (apexShell → zenithShell and ~25 others) and apex-* CSS classes renamed to zenith-*.
  • HTTP webhook signing headers (Apex-Signature, Apex-Event, Apex-Webhook) renamed to Zenith-*.
  • API token prefix apex_ → zenith_.
  • Demo credentials: demo / ZenithShowcase!2026 (was ApexShowcase!2026).
  • OKLCh design tokens replaced with Zenith's achromatic (zero-chroma) palette — pure greyscale neutral scale, light sidebar, colourful chart 1–5 accents preserved.
  • LocalStorage keys: apex-theme, apex-density, etc. → zenith-theme, zenith-density, etc.

Preserved

  • ApexCharts / apexcharts JS library references (third-party charting library at apexcharts.com).
  • The full test suite (710+ unit tests) — runs green against the renamed codebase.