System picker lists the account's License Keys labelled by System Name
(fallback: key). Schedule type selector (Daily|Weekly) with a confirmation
before switching a populated schedule (daily->weekly prefills all seven
days; otherwise clears). Weekly shows a per-day editor. Blocks use
TimeInput, allow adjacent/midnight-crossing ranges (labelled 'ends next
day'), block empty blocks client-side, default dynamic audio on, and show
title/category/group only. Adds gap helper text and an informational
kiosk-timezone note (browser fallback; no external/paid dependency).
Implements the shared validateSchedule validator (daily + weekly, midnight
spillover, Sunday->Monday wrap, adjacency, empty-block rejection), a
per-License-Key schedule service (get/set type, CRUD blocks, weekly
prefill with confirmation), the content-hash version/ETag serializer, the
new GET /api/v2/licenses/[licenseKey]/schedule (200/304/404, type null),
and additive evolution of the legacy GET /api/v2/schedule. Adds validator,
serializer (against docs/fixtures), endpoint ETag/304, and service tests.
Adds Schedule.licenseKeyId (unique, nullable) and Schedule.type, makes the
legacy userId optional/non-unique, adds Block.day and flips Block
dynamicAudio default to true. The migration backfills each existing
per-user schedule onto that account's oldest License Key as a daily
schedule, preserves unmappable schedules (licenseKeyId NULL) with a
NOTICE, and is reversible. Tested up/down against a scratch schema.
Add PUT /api/v2/licenses/[licenseKey]/system-name (200/400 invalid_name/
404 unknown_license/409 name_taken, idempotent) and extend POST
/api/v2/license with an optional systemName and additive systemNameStatus
(ok/unchanged/conflict/invalid). Validation always succeeds; an omitted
field never changes or clears a name and empty/whitespace is invalid.
Adds server and helper tests covering trimming, bounds, case-insensitive
uniqueness, cross-account reuse, 404, and idempotency.
Adds systemName (as typed, trimmed) and systemNameLower (case-folded) to
LicenseKey plus a unique index on (userId, systemNameLower) so names are
case-insensitively unique per account when non-null. Additive and
reversible; migration tested up/down. Regenerates the Prisma client.
Use TimeInput for Block start/end and show the day timeline axis with
AM/PM, so no 24-hour time is displayed in the schedule UI. New Blocks now
default dynamicAudio to true in the modal and server-side when the field
is absent; existing stored Blocks are untouched.
TimeInput displays and accepts AM/PM times, offers the 5-minute options as
suggestions, and allows typing any minute 00-59; its value model is a 24h
HH:MM string. Adds isomorphic parseTimeInput/hmToMinutes/fiveMinuteOptions
helpers and unit + component tests for them.
Remove the generic $lib/svelte-admin CRUD layer and AdminTable.svelte; each
admin route now owns its own form/modal logic. Add DataTable.svelte,
superform-errors.ts, and profile-specific modal/field components.
Flatten proclamation categories/groups and drop subscription levels; add the
v2 groups API; update v1/v2 endpoints, serializers, auth, and regenerated
Prisma client with the new migrations.
Document the overhaul plan in docs/ADMIN_OVERHAUL.md.
- Replace raw Resend text emails with sendMail using Resend templates
- automated-user-registration template: firstName, tempPassword,
verifyUrl, loginUrl
- license-key-delivery template: firstName, license_key
- Remove requestPasswordReset call (temp password is the one-time
credential; verification link handles email confirmation)
- Add .catch() to Better Auth email callbacks to prevent unhandled
rejections from missing templates crashing the server
- Change /api/v1/license from GET with query params to POST with JSON body
- Request body uses license_key and email_address
- Response returns key, email_address, subscription_level
- Add active check (reject deactivated license keys)
- Exclude /api/v1/automations/ from API key auth (GHL webhook)
- Add copyableFields prop to AdminTable with clipboard icon
- Enable copy button on license key field in license-keys admin page
- Add ApiKey Prisma model (keyHash, expiresAt, revokedAt)
- Protect /api/v1/* routes with Authorization: Bearer header check in hooks
- create rotateApiKey() utility: normal (48h grace) and emergency (immediate)
- Add Super-only /admin/api-keys page for key rotation with one-time reveal
- Fix roleId check by using parent layout's DB-fetched adminUser
- Add API Keys link to admin sidebar
- Add sortOrder column to ProclamationCategory with migration
- Implement drag-and-drop reorder in AdminTable with persist-to-DB
- Refactor AdminTable to use $derived + dragOverride (no $effect sync)
- Replace window.location.reload with invalidateAll for reorder
- Convert license key create to modal with subscription level dropdown
- Add LicenseKeyForm.svelte following the CategoryForm pattern
- Add replace action (delete + generate new key with same level)
- Remove deactivateOld checkbox; each key has its own level
- Use use:enhance with update() to refresh list after create/replace
- Registry: Remove 'parent.name' from listFields (all shown records
are top-level with no parent), remove 'parent: true' from include
since unused
- +page.server.ts: Custom load filters to parentId=null (top-level
only); custom upsert syncs children (create/update/delete) and
handles P2002 duplicate-name errors; custom delete cascades to
children first to avoid FK constraint violations
- CategoryForm.svelte: Replace parentId select dropdown with dynamic
list of sub-category inputs. Each row has a name field and Remove
button. Pre-populates from record.children when editing existing
categories. Add Sub-category button appends new rows
- +page.svelte: Remove unused relationOptionsData prop from
CategoryForm; rename submit button label from 'Update' to 'Save'
- prisma: Add ProclamationCategorySubscriptionLevel join table
migration and regenerate client
- General: Update TipTap mock, Toast, license-keys page, proclamations
server/route tests, README, AGENTS.md, and other admin forms
- Add per-route form components: UserRoleForm, UserForm,
ProclamationForm (with category cascade), CategoryForm,
FreqForm, LevelForm
- Update all admin +page.svelte files to use AdminTable + AdminModal
+ their specific form component instead of AdminPage
- Update profiles page to inline fields instead of using AdminForm
- Add custom Proclamation server action with category fallback logic
(categoryId falls back to parentCategoryId when no sub-category)
- Remove cascade/were/fieldLabels logic from generic crud.ts
- Remove outdated cascade/where tests from crud.spec.ts
- Remove fieldLabels from AdminModelConfig type
- Import superValidate from sveltekit-superforms/server instead of default
path to avoid pulling SuperDebug.svelte into SSR
- Upgrade @prisma/client to match prisma CLI version (7.2.0 -> 7.8.0)
so generated client's query_compiler_fast_bg files exist at runtime
- Add regression test verifying superValidate works in server context
- Add regression test verifying generated Prisma client compiler imports
reference real files
- Add Superforms + Zod 4 for all admin form validation
- Replace address isDefault with three separate boolean defaults (Mailing, Billing, Shipping)
- Update profiles page with three-checkbox address form and toast improvements
- Convert all admin page server actions to pass request directly to crud
- Export superFormErrorsToFieldErrors helper for profiles server actions
- Regenerate Prisma client for new schema fields
- Add type (Mailing/Billing/Shipping) and isDefault fields to Address
schema with migration. Unique default enforced server-side.
- Rewrite profiles page with inline address creation form and
editable user name/email within the profile modal.
- Add upsertAddress and updateUser form actions.
- Fix form redirect by adding use:enhance to AdminForm.
- Disable Update button in AdminForm until a field is changed
(prevents accidental submissions).