Headline
The whole member directory, three clicks instead of three hundred.
v6.2.0 ships bulk invite + bulk tier override on the member directory, an append-only visitor audit log, and a 40% faster table renderer that gets you through 5,000-row directories without staring at a spinner. Two operators with 8+ locations beta-tested this build for three weeks; the load-bearing changes here are theirs.
Section 1
Highlights
The bulk operations and audit logs in this release came out of a single sentence from an operator with 12 locations: “I have 4,800 members across three countries and I still onboard them in a CSV.” That's the gap we wanted to close.
- Bulk invite — paste a list of emails (or upload a CSV), assign tier + location, send. Up to 500 invites per batch.
- Bulk tier override — select N members, change their tier in one step. Honours your renewal-policy: pro-rate, end-of-cycle, or immediate.
- Visitor audit log — every QR scan, host approval, and override is now an append-only row, retained for 2 years by default.
- Table renderer v2 — 40% faster first paint on directory pages with 5,000+ rows. Same renderer powers visitors, bookings, and invoices.
Section 2
Members & Companies
The member directory grew up. The old design assumed you onboarded members one-at-a-time through the wizard — fine for a single location with 60 members, not fine for a chain with 1,500. Two new bulk surfaces ship in this release.
Bulk invite
Paste or drop a CSV with up to 500 rows. We dedupe against existing members by email, validate the rows in-browser, and queue invites in batches of 25 to keep within Resend's per-second budget. Failed rows surface inline with a reason — no more “the upload worked but two of them didn't get the email.”
email,first_name,last_name,tier,location_slug,starts_on anya@example.com,Anya,Park,resident,north-quay,2026-05-12 theo@example.com,Theo,Park,flex,north-quay,2026-05-12 sara@example.com,Sara,Liu,resident,karaka-bay,2026-05-19
Bulk tier override
Select N members from the directory, click Change tier, and pick the target tier. The override sheet asks how you want renewals handled. The three modes match what operators told us they actually want:
- Pro-rate — charges (or credits) the difference today, member jumps to the new tier immediately. The most common choice for upgrades.
- End of cycle— schedules the change for the member's next renewal date. The most common choice for downgrades.
- Immediate, no proration— flips the tier now, current cycle stays at the old rate. The most common choice for “we just promoted Sara, backdated to last Monday.”
Per-member tier override (single-row case)
The same three modes are now also available on a single member's detail page. Previously the only choice was immediate. Pro-rate is the new default, since that matches what 73% of single-row overrides ended up being manually adjusted to in the old flow.
Section 3
Visitors
Visitor management got an append-only audit log. Every scan, override, and host approval is recorded with the actor, timestamp, location, and the device the action originated from. Compliance asks have been steady from enterprise tenants since v6.0; this closes the most common one.
What gets logged
- QR code generated (with TTL + host)
- QR scanned at a kiosk (with kiosk ID + location)
- Manual override by a host or operator
- Pre-approved visitor admitted via dynamic NFC pass
- Visitor check-out (auto on TTL expiry or manual)
The log is append-only. Rows can be redacted (GDPR right-to-erasure) but never edited. The redaction creates its own row, preserving the audit trail of who redacted what and when.
CREATE TABLE public.visitor_audit_log (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id uuid NOT NULL REFERENCES public.companies(id),
visitor_id uuid REFERENCES public.visitors(id),
action text NOT NULL, -- 'qr.generated' | 'qr.scanned' | ...
actor_user_id uuid REFERENCES auth.users(id),
actor_role text NOT NULL, -- 'host' | 'operator' | 'kiosk' | 'cron'
device_id text,
location_id uuid REFERENCES public.locations(id),
occurred_at timestamptz NOT NULL DEFAULT now(),
metadata jsonb NOT NULL DEFAULT '{}'::jsonb
);
-- Append-only enforcement: RLS denies UPDATE + DELETE.
-- A redaction is a fresh row with action='redacted' pointing at the target id.Section 4
Performance
The big one: the table renderer in member directory, visitor list, booking list, and invoice list all share a new virtualisation kernel. First paint drops 40% on the worst-case tenants (5,000+ rows), and we no longer trigger a layout thrash when sorting by a derived column.
- Member directory: first paint down from 1,250ms to 740ms on a 5k-row tenant (4G profile).
- Visitor list: sort + filter no longer re-mounts the whole grid; interactive within 60ms after a column header click.
- Invoice list: covering index on (tenant_id, status, due_date) knocks the server-side query from 180ms to 14ms p95.
- Bulk invite throughput: 500 invites queue + dispatch in ~12s end-to-end (was ~50s with the old per-row API call).
Before v6.2.0 TTFB 220ms FCP 820ms LCP 1250ms TBT 420ms After v6.2.0 TTFB 210ms FCP 480ms ⟶ -42% LCP 740ms ⟶ -41% TBT 180ms ⟶ -57%
Section 5
Fixes & polish
- Token ledger off-by-one — a member who used exactly their monthly token allowance to the second saw a 1-token negative balance for one render cycle. Cosmetic-only, but ugly. Fixed.
- Calendar timezone on edit — editing a booking from a different browser timezone occasionally shifted the start by one hour. The fix moves the parse into the worker and clamps to the tenant timezone.
- Stripe Connect onboarding return URL— operators returning from Stripe's Express dashboard landed on an empty state for ~3 seconds while the webhook caught up. Now uses the plan-gate grace window so the dashboard renders the new state immediately.
- Resend webhook retries— duplicate “delivered” events from Resend no longer create duplicate rows in our email_deliveries table. Idempotency key is now the Resend message id.
- Empty-state on member portal— a member with zero bookings now sees a friendly “book your first space” card instead of a 0px-tall empty list.
Section 6
Migration notes
One note for integrators, one for operators who run custom imports.
1. visitor_audit_log is new
The table is created by migration 20260505000000_visitor_audit_log.sql and populated forward from the migration timestamp. We do not back-fill historical events — they were not retained at the row level before this release. If you have a compliance retention need that requires earlier data, contact support; we have a Sentry replay we can correlate against for limited ranges.
litehq audit visitor --tenant north-quay --since 2026-05-05
────────────────────────────────────
1,847 audit events between 2026-05-05 and today.
Export to JSON: litehq audit visitor --export json > out.json2. CSV import column rename
The bulk-invite CSV expects location_slug (was location) and starts_on (was start_date). The old column names continue to work for the rest of 2026 with a deprecation warning surfaced inline on the upload sheet. Update your templates at your convenience.
Security note: Bulk invite rate-limits at 2,500 invites per 24h per tenant via the distributed Postgres limiter. Operators with bona-fide larger imports can request a temporary ceiling raise through support.
Found this useful?
Share the release notes with your team.
One short link per release, hosted publicly. No login wall.