Skip to content

Resources · Changelog

v6.2.0 · Member directory + bulk operations

Released 2026-05-05 · 14 changes · 5 contributors · ~10 minute read

Stablev6.2.0ShareRSS

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.
Member directory with the new bulk-action bar — select rows, pick an action, confirm in a single sheet.

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.”

CSV format (header row required)
text
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.”
Bulk tier override sheet — preview of what will change, including any tax impact on the next invoice.

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.

visitor_audit_log schema
sql
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.
Audit log surface — searchable by visitor, host, date range, or action type.

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).
Lighthouse — directory route, 5k-row fixture
text
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%
Sentry performance dashboard — before/after the renderer swap, showing the LCP step-change.

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
bash
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.json

2. 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.