docs/STACK.md
Stack
One tool per job. Versions live in the manifests and the lockfile. Prerelease integrations get checked against installed code, never against memory.
- UI: Solid 2 and Solid Router. Reads go through the Effect read adapter, in the browser only. No Effect Atom, no query cache.
- Build: Vite+ over a pnpm workspace. The Cloudflare Vite plugin runs SSR in workerd.
- Styling: Panda CSS with
strictTokens,css()/cva()/cx()fromstyled-system. Bespoke keyframes and reveal hooks stay in plain CSS. - Components: native elements first. Kobalte for complex accessible widgets, once its Solid 2 support is verified.
- Icons: Phosphor Bold through
unplugin-icons. The brand and notebook drawings stay custom. - Forms: native controls. TanStack Form only when a real screen needs it and a Solid 2 adapter works.
- Public config: T3 Env with Effect Schema. Secrets and bindings stay on the server.
- Backend: Effect 4 for workflows, typed errors and services. Pure math stays plain functions.
- Protocol: Effect RPC, schemas in
packages/contracts. Plain HTTP for Better Auth, webhooks, health and downloads. - Worker adapter: Alchemy's Effect Worker runtime. Init builds storage, link tokens and auth once per isolate. Domain services are built per request (STRUCTURE.md).
- Database: D1 through Drizzle, wrapped in the
Databaseservice. Alchemy applies migrations. No read replication: the API Worker uses Smart Placement (#174), which runs it next to the D1 primary, so a replica would sit unused (#203). - Auth:
@alchemy.run/better-authon the Drizzle adapter, over the lazy D1 handle. - Upload transport: Uppy, straight from the browser to private R2 multipart. A pnpm patch makes its
ListPartsfollow pagination. - Browser recovery metadata: idb-keyval.
- Part hashing for resume verification: hash-wasm MD5, in a module worker.
- Multipart signing: the
Storageservice signs Distilled S3 requests against the R2 endpoint, using a bucket-scoped API token Alchemy mints per stage. - Billing: Polar through
@distilled.cloud/polar. The plan catalog ispackages/contracts/src/billing.ts. The D1subscriptionrow caches Polar's subscription list and is rebuilt from Polar on every webhook and by the sweeper for rows past their period end; no row means Free. A past-due subscription keeps its plan while Polar retries. The staging test login gets acompStudio row that Polar never overwrites. Production bills the live organization, every other stage the sandbox one.PAID_PLANS_OPENkeeps a stage off Polar entirely until billing opens; production waits on it (PRODUCT.md). Alchemy providers ininfra/polar-provider.tscreate the products (shared by all sandbox stages) and each stage's webhook endpoint (PR #85). - Infrastructure: Alchemy v2 in
infra/alchemy.run.ts, on the app's Effect version. - Local URLs: Portless.
- Lint and format: oxlint with type-aware rules and Solid diagnostics, oxfmt. Rules live in
lint.config.ts. - Later, when real work needs it: a maintenance Worker or Queues for cleanup, KV for cache only, Electron with a separate Bun transport process and SQLite for desktop.
- Observability, built: Axiom for traces, through Alchemy's
Axiom.Telemetry, in thetranzfer-cp1torg. Production and staging each declare a traces dataset and an ingest-only token inresources.ts(tranzfer-<stage>-traces,-ingest). There is no logs dataset: the free plan allows three datasets, and Effect records eachEffect.log*line as an event on its span. PR previews reference staging's and export into them, told apart by thealchemy.stageresource attribute, so they create and delete nothing in Axiom.infrastructure/telemetry.tsprovides the telemetry to the API Worker. Alchemy builds the Effect OTLP tracer per request and cron run and flushes them from the request scope withctx.waitUntil, so a request never waits on Axiom. EveryEffect.fnname is a span, next to Effect'shttp.server,RpcServer.*andsql.executespans. Dev stages ship nothing, sovp run devneeds no Axiom account. Production and staging deploys needAXIOM_TOKENandAXIOM_ORG_ID(.env.example); previews need no Axiom credentials, but staging must have deployed first. - Resource: every API span's resource has
service.version(the deployed commit, bound asGIT_SHAbyinfra/alchemy.run.ts),deployment.environment.name(production or staging) andalchemy.stage. The relay stamps the same on browser spans, with the tab's own build from thex-tranzfer-buildheader when it is a full commit hash. - Redaction: Effect records the full URL and query string on HTTP spans, and no headers unless a
TracerHeaderFilterasks for them. The tracer wrapper intelemetry.tsdropsclient.addressand hidesurl.queryandurl.full, so an OAuth code or a signature never reaches Axiom. Domain attributes are ids, counts and sizes. File names, emails, link tokens and signed URLs stay out. - Browser spans:
BrowserTracinginapps/web/src/apiis Effect's own OTLP tracer overfetch, so the OpenTelemetry SDK pitfalls (node exporter build in workerd, batch against simple processors) do not apply. It posts JSON to/api/telemetry/traceson the same origin. The web Worker forwards that to the API Worker, which checks the origin, caps the body at 256 KiB, accepts onlyapplication/json, replaces the service name and forwards to the traces dataset with the ingest token. The browser never holds a credential. RPC calls sendtraceparent, so a click and its API work are one trace. The upload lifecycle is one span per delivery and per file, with events for offline pauses, retries and resume verification; parts get no span.Uploads.startis a span that ends at once with the delivery id, so a tab closed mid-upload still leaves a record. An error screen showsRefplus the first 8 characters of the trace id of itsWeb.errorBoundaryspan, andappErrorappends one to "Something went wrong"; support finds the trace withtrace_id startswith '<ref>'. - Product analytics, built: PostHog (project
tranzfer, US cloud), server-side over direct HTTP, neverposthog-node.apps/api/src/analytics.tslists every event and its properties; dev stages send nothing. The browser runsposthog-js(apps/web/src/api/posthog.ts) ontranzfer.apphosts only, through the web Worker's/ingestproxy, for pageviews, session replay, exceptions and web vitals.before_sendand the recorder's URL and attribute masks strip link tokens, invite codes and signed URLs; feature flags stay off because the/flagsbody carries the first URL unscrubbed. - Error capture, built: PostHog error tracking, from browser exception autocapture, replaces the planned Sentry (PR #222). No e2e test asserts spans against a local OTLP collector yet.
Upload, recovery, integrity and abuse rules live in RELIABILITY.md. Code placement lives in STRUCTURE.md.