PR1 audit: server-code sharing scope for `apps/studio` split
PR1 audit: server-code sharing scope for `apps/studio` split
PR1 audit: server-code sharing scope for apps/studio split
Classifies the 9 apps/web/src/server/* files the studio split plan
(.agents/plans/2026-08-19-split-studio-app.md) lists as studio-shared, per
that plan’s PR1 scope. (a) = already a thin createServerFn wrapper around
package logic, studio writes its own thin wrapper importing the package
directly. (b) = real logic embedded in apps/web/src/server, needs
extraction to a package first.
(b) — needs extraction
server/auth/handlers.ts(443 lines).handleGetSessionis the one
function every other file’srequireUser()helper depends on (billing-fn,
delete-account-fn, redeem-fn, agencies-gate all reimplement the identical
requireUserby calling it). It plussession.ts’s
readSessionCookie/verifySessionTokenneed to move to a shared package
(e.g. a newsession-check.tsin@brioso/auth-core, which already owns
otp/rate-limit) so both apps call one real session-check instead of
duplicating cookie/token logic. The rest of the file (OTP issue/verify,
login/logout) is web-only — marketing’s login flow, not studio’s — and
stays inapps/web.server/account/delete-account.ts(106 lines, sibling of the audited
delete-account-fn.tswrapper, not itself in the named 9 but is the real
logic backing it). DB transaction + Stripe cancel — extract to a package
(e.g.@brioso/billingalready owns Stripe checkout/portal — this fits
there, or a new@brioso/accountif billing shouldn’t own deletion).
delete-account-fn.tsitself is (a) once this + auth extraction land.server/catalog/queries.ts(395 lines) — but studio uses only one
type import (CatalogTrackRowWithPrompt, viaInspirationTracks.tsx)
and, per the file’s own comment,listRandomPublished()(studio’s
inspiration rail). Grepped studio’s actual imports — confirmed no other
function from this file is reached fromcomponents/studio/*,
routes/studio/*,routes/_authed/studio/*, orlib/studio/*. The other
6 exports (listPublishedByGenre/Mood/Subgenre/Instrument/UseCase,
listPublishedLeafCounts,listPublishedTaxonomyLeaves) back marketing’s
genre/mood/subgenre pages and sitemap — never touched by studio. Do not
move the whole file. Extract onlylistRandomPublished+
CatalogTrackRowWithPrompt+getTrackBySlug(its one dependency chain)
to a shared package — likelypackages/catalog(currently pure
taxonomy/domain, no DB access; this would be its first DB-touching export,
flag that as a package-shape change worth a second look) or a new
packages/catalog-queries. Leave the rest inapps/web.
(a) — already thin, write parallel wrappers
server/billing/billing-fn.ts— delegates to@brioso/billing/*
directly; only couples tohandlers.tsviarequireUser. Thin once auth
extraction lands.server/db/client.ts(24 lines) — trivial@brioso/db+
@brioso/configwiring, no package needed.apps/studiowrites its own
copy verbatim (app-local, not shared logic).server/jobs/playback-token-fn.ts/share-kit-link-fn.ts— both
10-line passthroughs. Their real logic (playback-token.ts5 lines,
share-kit-link.ts8 lines — not in the named 9, checked directly) is
already trivial; no extraction needed, studio can either import a moved
copy or these can be promoted into@brioso/jobsoutright given how small
they are.server/entitlements/redeem-fn.ts— delegates to
@brioso/entitlements/redeemdirectly; only couples tohandlers.tsvia
requireUser.server/flags/agencies-gate.ts— delegates to@brioso/obs-js’s
flag(); couples tohandlers.tsviarequireUserand to
~/server/obs/flagSubject.ts(42 lines, not in the named 9 — small, check
at PR4 time whether it needs to move too or studio can duplicate it).
Verdict for PR4
Not “new packages/server-shared” — narrower: extend @brioso/auth-core
with the session-check (handles 5 of 9 files’ only real coupling), extract
delete-account.ts’s transaction logic to @brioso/billing (or a new
small package), and pull 3 functions (not the whole file) out of
catalog/queries.ts. Everything else is parallel thin wrappers, no new
package.