# Pencil handover

Pencil is a contract-drafted seed with a native, responsive thin spine. It is a self-hosted feedback-to-action product for one business: operators build a short adaptive flow, collect completed responses through invitations, a public link, or an embed, use AI to surface evidence-linked findings, and turn one recommendation into an owned action that can be measured and closed.

`CONTRACT.md` is the product authority. `seed.json` is an intentionally early manifest whose limits, cost, and extension points still need validation. The accepted responsive design runs remain visual provenance; the running app is the native implementation described below.

## Rename

6 September 2026 — renamed from Flowly to Pencil (id `flowly` → `pencil`, repository `runeditrun/forms-pencil`): a common object you fill a form in with, matching the product's hand-written, one-question-at-a-time feeling and echoing Typeform.

## Manifest migration

7 September 2026 — `seed.json` migrated to the shared `schema/SEED.schema.json` shape: `capabilities` became the ordered `{id, clauses, description?}` array, `identity` became `description`, `displayName` was dropped, externals gained `adapters`/`required`/`why`/`data` in place of `adapter`/`optional`/`replaceable`/`whyUnavoidable`/`dataSent`, env `purpose` became `why`, `accessibility.surfaces` became `includes`, `customFields` became a flat array, and `deploy.publicPaths` became one flat array using the schema's trailing-slash-means-prefix convention, with `/` kept as a literal, always-exact root and the reasoning moved into `deploy.notes`. `supersedesBaseline` was dropped as redundant with `CONTRACT.md`'s own "Supersedes `BASE-SECRET-002`" statement on WS-003; `bindings` and `implementation` were dropped as having no home in the schema, the former belonging in Wrangler config and the latter already covered by this file's "What exists" and "Ported UI, fresh-design surfaces, and remaining gaps" sections.

## What exists

- One native Cloudflare Worker + Hono, Vite + React, TanStack Router, Tailwind, and Drizzle-on-D1 application.
- The owner landing at `/`; public respondent routes at `/f/:slug` and `/embed/:slug`; local sign-in request, sent, invalid, and local-inbox documents at `/signin`; and session-gated operator routes at `/app`, `/app/flows/:flowId/builder`, and `/app/flows/:flowId/results`.
- A real local D1 spine: seeded workspace, operator, flow, draft/published revisions, steps, choices, respondent sessions, atomic idempotent completed responses, answer rows, and factual revision counters.
- Native local magic-link/session auth. `MAIL_PROVIDER=local` works only with `PENCIL_LOCAL_DEVELOPMENT=true` on a loopback request, stores test links in local D1, and issues a seven-day HttpOnly, SameSite=Lax session cookie. The shipping Wrangler template contains none of those local values. Any other provider fails explicitly until a real adapter is installed; there is no password, bypass, or production fallback to the local inbox.
- API-driven UI only: the dashboard, builder draft/prompt/required save with optimistic-version conflicts, public flow, and results all read or mutate that D1 spine. Public answers stay in browser memory until the final idempotent submission; the first interaction creates the non-personal response session.
- Dark editorial responsive re-authoring using locally bundled DM Sans and DM Serif Display plus Lucide icons. There are no Google Font, Cloudflare Insights, SVG-art, generated iframe, or third-party runtime requests.
- Current visual evidence in `visual/`: `01-landing-desktop.png`, `02-respondent-mobile.png`, `03-dashboard-desktop.png`, `04-builder-desktop.png`, and `05-results-desktop.png`.
- One hundred and four contract IDs still have tagged `contract.todo(...)` placeholders in `tests/contract/pencil.contract.test.ts`. They remain pending rather than falsely satisfied.

Run the current state with:

```sh
pnpm install
pnpm dev
pnpm verify
```

`pnpm verify` currently runs real TypeScript, unit, and production-build checks. It does not yet run seed-spec, Worker-runtime integration, migration-from-previous-tag, load, preview-deploy, or complete backend E2E coverage.

## Required TanStack Start migration before further implementation

**8 September 2026 — owner direction.** TanStack Start is the adopted default, but the runtime described above remains Pencil's current Vite/React/TanStack Router implementation. On resuming Pencil, 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 Pencil's contract, native local magic-link/D1-session boundary, public respondent and embed route/404 behaviour, declared Hono HTTP routes, D1 migrations and response guarantees, and future mail/AI/task Queue/Cron/event 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.

## Contract decisions

- Pencil is a feedback loop, not a generic form suite: Ask → Signal → Act → Measure → Close.
- Flow revisions are immutable once published. Live edits create a new draft and cannot rewrite in-flight respondents or historical answers.
- Preview is isolated from real responses and effects. Publishing validates every reachable path.
- Public and invited identities are explicit. Anonymous product records store no contact or raw IP identity; invited responses are identified and must say so.
- Only final submission creates response data. It is atomic and idempotent; abandoned sessions never become hidden partial responses.
- After first interaction, an expiring non-personal session record supplies revision pinning and completion metrics without retaining partial answers.
- Mail, AI, and external task work are durable effects with visible failure and bounded retry behavior.
- AI reports operate on a fixed response set, link findings and exact quotes to evidence, suppress small groups, and fail visibly instead of inventing output.
- Recommendations become local actions first. External task creation is operator-confirmed and idempotent, and measurement verdicts remain human decisions with no extension seam.
- Mail is required, not optional. Magic-link sign-in is delivered by `mail.sender.v1`, so a deployment without mail is a deployment nobody can sign in to (OPER-003). AI and the task destination stay optional.
- Public collection defends itself with primitives the deployment owns — honeypot, minimum fill time, session-bound token, rate limits — because `BASE-PUBLIC-001` rules out any hosted challenge. A suspected submission is quarantined for operator review, never discarded; only a filled honeypot is dropped (RESP-015 to RESP-018).
- Results are a first-class, AI-free surface. Completion rate, drop-off by question, device split, and answer distributions come from responses and from non-identifying per-revision counters (RESP-019), so they survive session expiry and work with no AI provider configured.
- Every displayed percentage states its numerator and denominator (RSLT-003). Multi-select distributions may exceed 100% and say so.
- One IANA workspace time zone resolves deadlines, sending hours, and digests (WS-001, WS-002).
- Eight policies, at the `AGENTS.md` ceiling. Three drafted seams were removed: `action.measurement.v1` sat on an absolute guarantee, `operator.management.v1` was a permissions system in a no-roles product, and `flow.closure.v1`'s default contradicted its own clause.

`DIVERGENCE.md` records where Pencil left the planted documents. One baseline clause is superseded: `WS-003` supersedes `BASE-SECRET-002`, because that clause has no account of an optional external and Pencil must start with `ai.provider.v1` and `task.destination.v1` absent. `seed.json → deploy.publicPaths` is also split into `exact` and `prefix` lists, because `BASE-ACCESS-003` gives no syntax for the difference and a bare `"/"` read as a prefix would make the whole deployment public.

## Ported UI, fresh-design surfaces, and remaining gaps

The accepted desktop and compact layouts were ported as a single Aurora Coffee fixture at every viewport. `/` is the configured public respondent welcome for the same published flow and admission state as `/f/:slug`, with only the owner-configured business label, supporting line, four-color public theme, secondary sign-in link, and optional credit around it. The dashboard, builder macro-layout, public flow, and results macro-layout preserve the dark editorial system while responding independently on compact screens. The respondent flow begins unanswered; it has no operator navigation and no “In-app” distribution label.

The local-inbox sign-in document, invalid-link state, auth/session middleware behaviour, honest empty/unconfigured data states, builder conflict notice, and factual results wording were designed fresh from the contract and the ported visual system. They have no prototype authority. Their design goal is to state only real local behaviour: there are no fabricated themes, quotes, recommendations, task destinations, channels, statuses, or inert success CTAs.

The privacy notice is intentionally narrow: it says that submitted answers are visible to the configured business and are not sent to unconfigured AI or task providers. It does not claim the still-unimplemented invited/identified semantics. An optional email answer remains an answer, not a contact, but complete RESP-005/006 disclosure and identity handling are still owed.

The following contract behaviour is not implemented and must not be inferred from the current UI: production mail delivery (`mail.sender.v1`), operator bootstrap/provisioning, logout/revocation, flow creation, branching, preview isolation, publish validation, closed/archive-specific public documents, invitations, reminders, contacts, delivery, abuse defences/quarantine, answer-distribution and drop-off reporting, export/import, AI analysis, evidence links, local actions, external task delivery, notification, R2/Queue integration, and extension mounts. Missing/closed/archived flows currently share the same explicit unavailable document, so HOME-001 remains a tagged todo. The visible AI and task panels are explicit unavailable states, not placeholders for results.

The present D1 write path atomically commits one response, its answers, the session completion, and revision counters in one batch. A response session has one durable completion: a retry with the original idempotency key returns the same response ID, while a different key is refused. Operator flow reads and writes are scoped to the authenticated session's workspace. These guarantees cover the thin spine only; the broader admission, abuse, outbox, and expiry clauses remain pending.

The current thin spine can run on Cloudflare Free at local or modest early-production usage. The real first Paid boundaries are measured usage: 100,001 dynamic Worker requests in a UTC day or sustained work beyond the Free CPU limit, 5,000,001 D1 rows read or 100,001 rows written in a UTC day, a D1 database above 500 MB, or 10,001 Queue operations in a UTC day. A normal Queue delivery is three operations, so 3,334 deliveries cross that boundary. The declared capacity targets exceed the Free plan and therefore carry a $5/month minimum estimate before usage overages; they are not load-test results. R2 Standard has its own monthly free tier. Sources: [Workers pricing](https://developers.cloudflare.com/workers/platform/pricing/), [Workers limits](https://developers.cloudflare.com/workers/platform/limits/), [D1 pricing](https://developers.cloudflare.com/d1/platform/pricing/), [D1 limits](https://developers.cloudflare.com/d1/platform/limits/), [Queues pricing](https://developers.cloudflare.com/queues/platform/pricing/), and [R2 pricing](https://developers.cloudflare.com/r2/pricing/).

The next backend pass should first replace local mail with a required production adapter and add startup validation, then complete publication/branching and respondent abuse protection, followed by distribution, operator-independent exports, and the AI/action layers. Replace each contract todo with Worker-runtime and browser coverage as behaviour lands; add migration-from-previous-tag, accessibility, load, preview-deploy, and `/health` gates before release. Re-run direct and embedded public flows at desktop and mobile after every visual change and refresh `visual/` from the live app.
