# Dial handover

## What this repo is now

Dial is an invitee-first, single-host scheduling seed at the `runtime-spine-public-port` maturity. It now has a real Cloudflare Worker, Hono API, Vite/React/TanStack Router UI, typed runtime configuration, Drizzle repositories, and immutable D1 migrations. `pnpm dev` migrates an isolated or default local D1 database and seeds the development sample through the same services and repositories the product uses.

Operators sign in with opaque, hashed, single-use magic links and D1-backed sessions. Development exposes the latest link only through a loopback-only sink. The persisted spine covers the allow-listed operator, singleton host profile, event types, invitee questions, recurring availability, and date-specific open/closed overrides. Operators can edit those records, but publication remains blocked until the calendar, mail, and conferencing requirements are genuinely healthy.

The deployment has two real faces:

- `/` is the configured owner front door. Its identity, description, theme, privacy link, and optional Powered by credit come from buyer-owned public config. Its booking CTA is derived from the persisted profile handle rather than duplicated in config.
- `/app` is the session-protected operator workspace. It includes the dashboard, profile editor and preview, event inventory/editor/preview, recurring availability and date overrides, honest booking/request empty states, settings health, responsive navigation, and sign-out.
- `/book/<handle>` is the public persisted host profile and lists only published, listed events. The sample event is deliberately a draft, so the catalog state truthfully says that no sessions are open.
- `/book/<handle>/<slug>` presents only a published event. Until calendar reads exist, its availability endpoint fails closed with `temporarily_unavailable` and no slots. Draft, disabled, renamed, deleted, unknown, nonce-less details/confirmation, manage, and embed URLs return branded recovery with the correct 404 status and no record data.

The public API uses a browser client that omits credentials on automatic reads. Public profile and event DTOs exclude internal IDs/timestamps, private location values, and operator-only scheduling fields. The local privacy notice is a declared public surface. The incomplete embed returns 404 with `frame-ancestors 'none'` and `X-Frame-Options: DENY`; it does not pretend that the contracted origin-gated script exists.

`CONTRACT.md` remains the product authority. `seed.json` records the current maturity and an operating-cost model re-derived against the Workers Free plan. `DIVERGENCE.md` still records the four deliberate baseline supersessions. `BASELINE.md`, `AGENTS.md`, and `AGENTS.consumer.md` remain unchanged.

## Required TanStack Start migration before further implementation

**8 September 2026 — owner direction.** TanStack Start is the adopted default, but the runtime described above remains the current Vite/React/TanStack Router implementation. On resuming Dial, the **FIRST** step is to bring this committed main-branch note into any parked implementation worktree and convert through the accepted recipe before further product work: local evidence `/Users/zemaj/.orchestrator/evidence/runeditrun/default-seed-on-tanstack-start-with-the-migration-recipe/MIGRATION.md`; portable author reference [TanStack Start migration recipe](https://github.com/runeditrun/seed-spec/blob/main/TANSTACK-START-MIGRATION.md).

The conversion must preserve Dial's contract, opaque native magic-link and D1-session authentication, public booking and recovery status/404 behaviour, declared Hono API and public HTTP routes, D1 migrations and projections, and future Queue/Cron/email handlers. Hono remains the owner of declared HTTP contract surfaces; the custom Worker entry retains events and named exports; Start takes selected React document routes and server functions only. Public React documents need SSR HTML or an explicitly allowed invariant prerender; every private server function must re-check the native session before reading data, and `/app` navigation must hydrate and stay client-side. The recipe is accepted through local workerd validation only and makes no remote-deployment claim.

7 September 2026 — `seed.json` was migrated to the shared `category`/`spine`/`replaces` manifest schema in `seeds/base/schema/SEED.schema.json`: `slug` was dropped as a duplicate of `id`, externals gained `required` and renamed `whyUnavoidable`/`dataSent`/`adapter` to `why`/`data`/`adapters`, env `description` became `why` with each deferred variable's status folded into that sentence, and `customFields.entities` became a flat array; `scripts/validate-manifest-contract.mjs` was updated to match the new shape.

Run it on a clean clone with:

```bash
pnpm install
pnpm dev
```

Current verification is:

```bash
pnpm verify
```

That gate runs ESLint and accessibility-oriented JSX rules, architectural import/binding boundaries, manifest/contract structural validation, TypeScript, real local-Worker tests, an isolated D1 replay from migration `0001` through `0003`, and the production build. It currently runs 11 runtime/bootstrap/public-client tests. At this maturity it claims structural integrity, not satisfaction of the full scheduling contract; complete clause-tagged behavioural coverage becomes mandatory only at a contract-verified maturity.

Final acceptance also exercised the isolated local deployment in the in-app browser at 1440×1024 and 390×844: public landing/profile, mobile menu, loopback magic-link sign-in, protected event preview, duration editing at the five-minute lower bound, recurring/date-override controls, local privacy, and recovery URLs. HTTP checks confirmed the public 200s, draft/manage/embed 404s, anti-framing headers, and the unauthenticated `/app` redirect.

Current catalog and key-state images are under `visual/`, with the landing first. The old `artifacts/` images remain prototype provenance, not current product screenshots.

## Rename

6 September 2026 — renamed from Openlane to Dial (the repository is now `runeditrun/scheduling-dial`) under the naming rule of a common object, the product's feeling, and an echo of Calendly: a dial you turn to your time. The screenshots in `visual/` were re-captured from the running local deployment under the new brand.

## What was ported and what was designed fresh

The prototype supplied the calm owner/host tone, public profile hierarchy, session summary, choose-time framing, explicit progress, alternative-request concept, recovery intent, responsive spacing, and interaction-size target. The port preserved those decisions while removing fixture slots, simulated side effects, fake success states, and provider claims.

The following surfaces had insufficient or no trustworthy prototype provenance and were designed fresh using the same visual language:

- owner landing and local privacy notice;
- magic-link sign-in and responsive operator shell;
- operator dashboard, event inventory/editor/readiness, profile editor/preview, settings health, and honest booking/request workspaces;
- recurring-availability editor and D1-backed date-specific override editor;
- draft event preview, unavailable-provider state, and branded 404 recovery.

The UI direction is deliberately owner-first and quiet: warm neutral surfaces, deep green action colour, restrained cards, dense but readable operator layouts, and no third-party font or script dependency. The public theme tokens are live, including colour and radius.

## Fabrications removed by the port

- No ratings, scores, ranking badges, marketplace proof, or invitee-generated reviews are shown.
- “No booking fees,” provider-calendar buttons, invented reply windows, and unconditional reschedule promises are gone.
- No hard-coded sample slot, fake week, simulated calendar connection, or fabricated meeting link is rendered.
- Public pages use only operator-authored persisted profile/event projections; the private location value stays server-side before confirmation.
- Details, confirmation, manage, draft event, disabled request, and embed paths recover without invented booking or invitee data.
- Alternative-request submission is visibly disabled until storage and delivery exist. Clipboard failure is reported rather than converted into success.
- Product naming is consistently “Product kickoff”; the former “Project kickoff” mismatch is gone.
- Operator empty states describe current records and provider health, not the implementation project.

## Product decisions worth preserving

- Explain the host and session before asking for invitee details.
- Keep the public journey explicit: choose time → details → confirmation, with route recovery instead of phantom defaults.
- Render invitee-facing time in the selected IANA zone and show the host's local time alongside details and receipts.
- Put a small, explainable recommendation set above complete availability; recommendations may rank only already-valid slots.
- Fail closed when any required calendar cannot be read. An empty/unverified calendar is never availability.
- Keep “None of these work?” first-class. A request is not a booking and never reserves time.
- Keep confirmation useful on its own: event, both local times, duration, host, real join state, local `.ics`, and private manage link.
- Keep public pages third-party-free and controls at least 44px at phone widths.

## Backend-pass debt

### Operator and configuration

Allow-list administration, passkeys, avatar upload/R2 delivery, blocking rules, attendance, operator-created bookings, erasure workflows, and complete adapter settings are not built. Basic magic-link auth, profile editing, event editing, readiness reporting, recurring schedules, and date overrides are real.

### Availability and booking

There is no provider-backed availability engine, UTC slot generation, timezone/DST conversion, recommendation ranking, conflict diagnostics, nonce, selection persistence, booking record, atomic claim, occurrence history, reschedule, cancellation, or manage-token capability. Stored recurring windows and overrides are not yet consumed by a slot engine. The public event surface therefore offers no slot.

### Calendars, meetings, and delivery

There is no OAuth/state flow, calendar selection or sync, Google Meet provisioning, mail sender, webhook verification, `.ics`, Queue outbox, retry/reconciliation worker, reminder Cron, or operator-visible delivery failure recovery. Health currently checks D1 only; it cannot claim R2/Queue/provider reachability.

### Alternative requests and embeds

The request composer has date/range/identity/timezone validation and truthful disabled delivery, but stores nothing and has no operator inbox lifecycle. The contracted asynchronous inline/modal embed script, allowed-origin setting, and embed parity do not exist; `/embed/...` is intentionally unavailable and anti-framed.

### Operations, data, and release

Setup/deploy/smoke, export/import, release metadata, provider secrets, R2/Queues/Cron provisioning, load-derived limits, full automated accessibility audit, shared baseline pack, and a real deployed-preview journey remain. The current sample/data path is local development only.

## Suggested backend build order

1. Add calendar and mail adapter configuration, OAuth/state handling, connection checks, health, and operator controls.
2. Build a pure availability engine over the existing recurring windows and overrides, with IANA/DST, increment, notice, horizon, buffer, host-day-limit, internal-conflict, external-busy, diagnostics, and deterministic-recommendation tests.
3. Connect verified week availability and timezone interaction to the current public event UI.
4. Add booking/nonce persistence, atomic claim, answer validation, receipt, and manage-token recovery.
5. Add idempotent calendar/meeting/mail outbox delivery, `.ics`, reconciliation, typed failures, retries, and reminders.
6. Implement reschedule/cancel occurrence transitions and operator booking/attendance paths.
7. Persist alternative requests and build their operator inbox, routing, acknowledgement, and lifecycle notifications.
8. Build the real origin-gated embed script, avatar media path, operator administration, blocking, and erasure.
9. Finish setup/deploy/export/import, load limits, accessibility and deployed-preview verification, release metadata, and full clause-tagged contract/baseline coverage.

## Adversarial cases the backend pass must not miss

- Two invitees claim the last slot; a client retries after losing the response.
- A required calendar is revoked or changes between page load and confirmation.
- A calendar write or meeting creation succeeds but its response is lost.
- Calendar and mail webhooks arrive duplicated or out of order.
- DST skips or repeats a host/invitee wall-clock time, including across reschedule.
- Buffers meet an external busy interval exactly at a boundary.
- Cancellation races rescheduling, or a manage token is replayed or rotated.
- A reminder job survives a later reschedule or cancellation.
- Confirmation mail is delayed or bounced while the internal booking remains valid.
- An invalid public URL attempts to expose profile, event, booking, or calendar data.
- A push channel lapses silently and stale busy data would otherwise offer a conflict.
- An operator overrides public availability onto a time that overlaps a confirmed booking.
- Rate limiting fires between choosing and confirming without losing invitee answers.
