Skip to content

chore: consolidate waveflow-web into the monorepo under web/ - #41

Merged
InstaZDLL merged 88 commits into
mainfrom
feat/monorepo-merge
Jun 11, 2026
Merged

chore: consolidate waveflow-web into the monorepo under web/#41
InstaZDLL merged 88 commits into
mainfrom
feat/monorepo-merge

Conversation

@InstaZDLL

@InstaZDLL InstaZDLL commented Jun 11, 2026

Copy link
Copy Markdown
Owner

Why

The Rust server (this repo) and the TanStack Start web client (InstaZDLL/waveflow-web) ship in lockstep: the web hosts the Better Auth instance whose JWKS the server reads, the JWT audience / issuer is one contract, every schema field has a TypeScript mirror, and dev requires both running side by side. Every meaningful change so far has had to land as 2 PRs across 2 repos and be reviewed in the right order. Splitting them was a sunk cost.

The next two chunks of work make that cost worse:

  • Sync v2 — the desktop QA caught that the existing flow only emits ops on changes, so an existing library never reaches the server. Fixing that means a backfill protocol that touches the desktop, the server, and the web display state at once.
  • Web UI refonte — replacing the TanStack Start starter with a real Spotify-style player + library + playlist surface. Almost every screen reads from a server endpoint.

A monorepo lets one PR cover the contract change end-to-end. The desktop stays in its own repo (InstaZDLL/WaveFlow, GPL-3, local-only software, separate release cadence).

What

Subtree-merges InstaZDLL/waveflow-web@main under web/ with full history preserved (commit Add 'web/' from commit 'a85cad0...'). The Rust crate root is untouched. Cargo never recurses into web/ because there's no Cargo.toml there.

/                # axum + sqlx + utoipa Rust service (unchanged)
/src/, /tests/   # server only
/migrations/     # Postgres sqlx migrations
/web/            # React 19 + TanStack Start + Better Auth
/web/db/         # Better Auth schema (hand-written)
/web/packages/   # @waveflow/design-tokens (workspace dep)

CI re-split

Old New
.github/workflows/ci.yml (rust) ci-rust.yml, path-filtered to Cargo / src / migrations / tests
web/.github/workflows/ci.yml (TS) ci-web.yml, gated by paths: ['web/**'] and working-directory: web
.github/workflows/codeql.yml now path-filtered to Rust changes only
.github/workflows/dco.yml kept (server's stricter author-email check)
web/.github/workflows/dco.yml deleted (duplicate)
web/.github/workflows/label-pr.yml adopted at the root, points at the new merged labeler

Dependabot

One file at .github/dependabot.yml: cargo at /, npm at /web, github-actions once. The waveflow-core ignore and the kysely 0.29 hold-back are both carried over.

Labels

Top-level scope: server / scope: web to make the PR list scannable. The finer scopes (scope: api, scope: db, scope: auth, scope: sync, scope: streaming, scope: artwork, scope: routes, scope: components, scope: design-tokens) are rewritten against the new path tree.

Other moves

  • web/.github/ISSUE_TEMPLATE/.github/ISSUE_TEMPLATE/ (server didn't have one).
  • web/.github/pull_request_template.md.github/pull_request_template.md.
  • README.md rewritten to describe the monorepo + repo layout.
  • CLAUDE.md now points to web/CLAUDE.md for half-specific guidance.
  • web/CLAUDE.md added — covers TanStack server-fns, Better Auth plumbing, design-tokens package.

Out of scope

  • release-please config. Server has a 0.0.0 placeholder and web has a 0.0.0 placeholder — neither has emitted a release yet, so there's no live config to multi-component. We'll add release-please-config.json + .release-please-manifest.json in a follow-up when the first version is cut.
  • Archive waveflow-web repo — depends on this PR landing. Follow-up: archive, README redirects here.
  • Sync v2 + web UI refonte. These are the work this merge unblocks; they ride in subsequent PRs.

Test plan

  • cargo check --all-targets --all-features passes locally.
  • cargo clippy --all-targets --all-features -- -D warnings clean.
  • Subtree merge preserved web history (git log --follow web/package.json reaches the original web repo commits).
  • CI matrix on this PR: ci-rust skips (no Rust paths changed), ci-web runs (touches web/**), codeql skips, dco runs.
  • Visual smoke against the dev-stack post-merge (server + web from the same checkout, Postgres natif).

Companion changes

The dev-stack README at InstaZDLL/waveflow-dev-stack is updated locally to point at ..\waveflow-server\web\.env instead of ..\waveflow-web\.env. Push that as a separate PR after this lands.

Summary by CodeRabbit

  • Nouvelles fonctionnalités

    • Client web WaveFlow complet : lecteur audio persistant, barre de lecture, overlay « Now Playing », panneau de file d'attente.
    • Gestion des playlists : créer, éditer, supprimer, vue détail et partage public par lien.
    • Navigation library → albums → artistes, lecture par piste, queue et historique.
    • Thèmes personnalisables avec picker, persistance et style SSR.
    • Authentification : email + OAuth (Google/Apple) intégrés.
  • Documentation

    • README, guides web/CONTRIBUTING et instructions de développement mis à jour.

InstaZDLL and others added 30 commits May 30, 2026 21:42
First commit on the waveflow-web repo. Bootstraps Phase 1.c.1 of
RFC-001 — a React 19 + TanStack Start frontend that will integrate
Better Auth (1.c.2) and call into waveflow-server's tenant-scoped
CRUD over a Bearer JWT (1.c.3).

Tooling:
- TanStack Start scaffold (Vite + Nitro server bundling, file-based
  routing). Default home / about pages stay for now — they get
  replaced by the real shell in 1.c.3.
- Tailwind CSS 4 + Lucide icons (carried by the scaffold).
- React Testing Library + Vitest + jsdom for the test surface.
- ESLint flat config + Prettier 3 for style. The TanStack-generated
  src/routeTree.gen.ts is gitignored and regenerated per build.
- Husky 9 + commitlint with the desktop + waveflow-server rule set
  (header <= 100, kebab-case scopes, lowercase subject).

License + governance:
- LICENSE: AGPL-3.0-only with the project's copyright header.
  Matches waveflow-server — the web client is part of the
  SaaS-hosted backend story, so a fork of the hosted product has
  to publish its client-side modifications too.
- CONTRIBUTING.md: DCO sign-off (git commit -s), commit message
  conventions, code-style + PR expectations.
- .coderabbit.yaml: assertive review profile in French, path-scoped
  instructions for src/, src/routes/, src/routes/api/, src/auth/,
  .github/workflows/, vite.config.ts and package.json. Ignores
  generated paths (bun.lock, .output, .nitro, routeTree.gen).
- .github/ISSUE_TEMPLATE/{bug,feature,config}.yml + a clean
  pull_request_template.md.
- .github/workflows/ci.yml — Bun setup, frozen lockfile, then
  prettier check / lint / build / typecheck / test. Build runs
  before typecheck because the TanStack plugin generates the
  routeTree at build time.
- .github/workflows/dco.yml — verifies every PR commit carries a
  Signed-off-by trailer between base..head.
- .github/workflows/label-pr.yml + .github/labeler.yml — auto
  scope:* / type:* / size:* labels.
- .github/dependabot.yml — weekly npm + GitHub Actions sweeps.
  Groups patch+minor into one PR; TanStack ecosystem bumps grouped
  together since their version trains land in lockstep.

Verified locally:
- bun install OK (installs the husky commit-msg hook via prepare)
- bun run build OK (Vite + Nitro bundling)
- bun run typecheck OK (after build, so routeTree.gen.ts exists)
- bun run lint OK
- bun run format:check OK

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
Bumps the github-actions-all group with 2 updates: [actions/checkout](https://github.com/actions/checkout) and [actions/labeler](https://github.com/actions/labeler).


Updates `actions/checkout` from 4 to 6
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](actions/checkout@v4...v6)

Updates `actions/labeler` from 5 to 6
- [Release notes](https://github.com/actions/labeler/releases)
- [Commits](actions/labeler@v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions-all
- dependency-name: actions/labeler
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions-all
...

Signed-off-by: dependabot[bot] <support@github.com>
…-actions-all-e3b082cf2d

chore: Bump the github-actions-all group with 2 updates
First sub-PR of 1.c.2. Installs better-auth + the catch-all
handler at `/api/auth/$.ts`; ships the initial Postgres schema as
a hand-written migration so contributors can read the auth tables
without bootstrapping a DB just to introspect.

Module surface (`src/lib/`):
- `db.ts` — Kysely + node-postgres pool. Fails loud at boot on
  missing DATABASE_URL; pool size capped via BETTER_AUTH_DB_MAX
  (default 10).
- `auth.ts` — Better Auth server instance with email/password
  enabled. Requires BETTER_AUTH_SECRET + BETTER_AUTH_URL at boot,
  no silent dev fallback. Email verification is OFF for the 1.c.2
  transition — it lands alongside the email-sender plumbing in a
  follow-up. Session TTL 7d, refresh-on-use every 1d.
- `auth-client.ts` — `createAuthClient()` re-exports the React
  hooks (`useSession`, `signIn`, `signUp`, `signOut`) for component
  consumers.

Route:
- `src/routes/api/auth/$.ts` — TanStack Start splat route that
  forwards GET/POST/PATCH/DELETE to `auth.handler(request)`. Better
  Auth owns the URL → operation mapping internally.

Database:
- `db/migrations/0001_better_auth_initial.sql` — initial schema:
  `user`, `session`, `account`, `verification`. Mirrors Better
  Auth's documented shape. Hand-written so a contributor can read
  the tables without standing up a DB to run the CLI. The `jwks`
  table lands in 1.c.2c with the JWT plugin.
- `db/README.md` — bootstrap steps (docker run + psql -f loop).
  A `bun run db:migrate` wrapper lands in 1.c.2b.

Bootstrap docs:
- `.env.example` with DATABASE_URL, BETTER_AUTH_SECRET,
  BETTER_AUTH_URL, BETTER_AUTH_DB_MAX (optional).
- `.gitignore` extended to cover `.env.local` / `.env.*.local`.
- `README.md` Development section gains the `cp .env.example .env`
  step + a link to db/README.md.

Deps:
- runtime: better-auth 1.6.12, @better-auth/core 1.6.12 (pinned to
  match — bun's resolver picked an old 1.4.21 by default and the
  resulting export mismatch killed every build until the explicit
  pin landed), kysely 0.28.17 (0.29 moved DEFAULT_MIGRATION_TABLE
  to a subpath that better-auth's CLI can't follow yet), pg 8.21.
- dev: @types/pg, @better-auth/cli (for `bunx better-auth generate`
  on follow-up schema bumps).

What this is NOT (deferred):
- Sign-up + sign-in UI routes → 1.c.2b.
- JWT plugin + JWKS endpoint → 1.c.2c.
- Bridge from Better Auth's user.id to waveflow-server's
  users.external_id → 1.c.3.

Verified locally:
- bun install OK
- bun run build OK (Vite + Nitro produces the catch-all route)
- bun run typecheck OK
- bun run lint OK
- bun run format:check OK

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
The scaffold installed vitest + @testing-library + jsdom but didn't
ship a config. Vitest 4 defaults to exit code 1 on zero test files
AND tries to transform react/index.js as ESM without the React
plugin — both fired on the first CI run.

- vitest.config.ts wires the same Vite + React plugin chain as the
  runtime build so React modules get the JSX transform Vitest needs.
  environment jsdom for component tests. The passWithNoTests flag
  keeps CI green during the bootstrap window before any test files
  land — the flag becomes a no-op once src test files show up.
- testTimeout bumped to 10s — same value the scaffold's prior
  convention used; a cold Testing Library wait can tip the 5s
  default on a CI runner.

Verified locally: bun run test exits 0 with the no-test-files
message instead of the ESM ReferenceError + exit 1.

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
Absorbs the eight dependabot major-version PRs (#2-#10) into this
branch so the bootstrap state is on the latest of every dep we
ship. Closing the standalone bot PRs in favour of this one keeps
the review surface single.

Bumps (all major):
- lucide-react 0.545 -> 1.17 (default import API unchanged; no
  call site adjustments needed)
- eslint-plugin-react-hooks 5 -> 7 (new `set-state-in-effect`
  rule fires on ThemeToggle's mount-time setState — fixed by
  switching to lazy initial state instead of effect-driven init)
- @commitlint/cli + config-conventional 19 -> 21 (no rule change
  needed; existing .commitlintrc.cjs stays compatible)
- globals 15 -> 17 (additive — browser/node entries unchanged)
- @types/node 22 -> 25 (type defs only; tsc clean)
- jsdom 28 -> 29 (vitest env; no test files yet so no behaviour
  surface to break)

Held back (with rationale):
- eslint 9 -> 10 + @eslint/js 9 -> 10: eslint-plugin-react still
  caps at peer ^9.7 and throws `contextOrFilename.getFilename is
  not a function` under v10. Reverting to v9 until eslint-plugin-react
  ships v10 support — re-enable when upstream catches up.

ThemeToggle refactor:
- Mount-time `setMode(getInitialMode())` inside useEffect was the
  scaffold pattern; v7's react-hooks/set-state-in-effect rule
  correctly flags it. Now `useState(getInitialMode)` is the lazy
  initialiser, the useEffect only calls applyThemeMode (which
  must stay in an effect because it touches `document` and would
  break SSR otherwise). exhaustive-deps disabled on that effect
  with rationale.

Validated:
- bun run format:check OK
- bun run lint OK
- bun run typecheck OK
- bun run build OK
- bun run test OK (no test files; passWithNoTests carry-over from
  the previous commit)

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
Three of the five inline findings applied; one skipped with
rationale; one partially applied with upstream-lag rationale.

Applied:

- package.json: pin better-auth to exact 1.6.12 (not ^1.6.12) to
  stay in lockstep with the already-pinned @better-auth/core
  1.6.12. The runtime is hard-coupled — bun's resolver pulled a
  mismatched core version on first install and killed every build
  until we pinned core; the same can happen the day patch 1.6.13
  ships if better-auth drifts. Both exact, dependabot will bump
  them together.
- src/lib/db.ts: register pool.on('error', ...) so an idle pg
  client that disconnects async doesn't crash the Nitro process
  with an unhandled exception. node-postgres docs explicitly call
  this out as required behaviour.
- README.md: the cp .env.example line now mentions BETTER_AUTH_URL
  alongside DATABASE_URL and BETTER_AUTH_SECRET. The variable was
  already in .env.example and required by src/lib/auth.ts at boot,
  the onboarding sentence just hadn't named it.
- src/components/ThemeToggle.tsx: replace the mount-only useEffect
  with useLayoutEffect([mode]) so theme application runs before
  paint and routes every mode change through one path. toggleMode
  no longer calls applyThemeMode directly — the effect owns
  application. The eslint-disable comment for exhaustive-deps is
  gone (genuinely depending on [mode] now). Also switches the
  matchMedia listener effect to useLayoutEffect for consistency.

Skipped:

- src/lib/auth.ts: CR suggested swapping `database: { db, type }`
  for `database: { adapter: createKyselyAdapter(db), type }`.
  Verified against the BetterAuthOptions type def in
  @better-auth/core/dist/types/init-options.d.mts: the
  `{ db: Kysely<any>, type: KyselyDatabaseType, ... }` form is an
  officially-supported variant of the database union. Separately,
  createKyselyAdapter's signature is `(config: BetterAuthOptions)
  => Promise<...>` (takes the whole options object, not a Kysely
  instance), so the literal suggested call wouldn't compile. The
  current form is the canonical pattern for "I have a Kysely
  instance, build the adapter for me".

Partially applied (upstream lag):

- @better-auth/cli: CR wanted alignment with the runtime's 1.6.x.
  Verified `bun pm view @better-auth/cli versions` — the CLI's
  latest stable is 1.4.22, no 1.6.x release exists yet. Bumped to
  ^1.4.22 (was ^1.4.21) to pick up the tiny patch. Re-evaluate
  the pin the day the CLI ships a 1.6.x.

Validated:
- bun run format:check OK
- bun run lint OK
- bun run typecheck OK
- bun run build OK
- bun run test OK

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
feat(auth): wire better-auth core + handler mount (phase 1.c.2a)
The first batch of waveflow-web labels shipped as `scope:routes`,
`type:feature`, `size:l` etc. — no space after the colon, and
defaulted to plain grey because GitHub auto-creates missing labels
without colours. The desktop's WaveFlow repo uses `scope: backend`,
`type: feat`, `size: l` (space, short tokens, coloured palette).
Aligning the two so a contributor browsing both sees a consistent
sidebar.

Changes:
- .github/labeler.yml — quote the scope keys to insert the space
  (`'scope: routes'` etc.). Also extend the `scope: auth` paths
  to include the new `src/lib/auth*.ts` + `src/routes/api/auth/**`
  added in #11.
- .github/workflows/label-pr.yml — bump every `type:*` mapping in
  the `type_labels` JSON to `type: <short>` (and `feature` → `feat`
  to match the desktop). Same for the `size:*` labels on the
  pr-size-labeler step.
- .github/dependabot.yml — drop the per-ecosystem `npm` /
  `github-actions` labels (didn't exist on the repo, would have
  auto-created as gray strays) in favour of the unified
  `scope: deps` (npm bumps) and `scope: tooling` (GitHub Actions
  bumps) that already exist with proper colours.

The GitHub labels themselves were recreated out-of-band via gh CLI
with colours mirroring the desktop palette (type: feat = 1d76db,
size: l = f9d0c4, scope: deps = 0366d6, etc.). That work isn't
file-tracked, so the next time we bootstrap a new repo the cleanest
move is to ship a `.github/labels.yml` + crazy-max/ghaction-github-labeler
to source-control the colours. Out of scope here.

Validated:
- bun run format:check OK
- bun run lint OK

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
pull_request_target runs in the base-repo context with write
permissions on issues + pull-requests, so a compromised upstream
could rewrite a mutable tag (v6, v1, ...) to siphon the workflow
token. Pin all three third-party actions to the commit sha of
their current release:

- actions/labeler@v6.1.0 -> f27b6088...
- bcoe/conventional-release-labels@v1.3.1 -> 886f6967...
- codelytv/pr-size-labeler@v1.10.4 -> 095a41fc...

Mirrors the desktop repo's label-pr.yml convention. Dependabot
will keep the comment-pinned version current going forward.

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
…esktop

chore(tooling): align label naming with desktop convention
Bumps the npm-patch-and-minor group with 1 update: [kysely](https://github.com/kysely-org/kysely).


Updates `kysely` from 0.28.17 to 0.29.2
- [Release notes](https://github.com/kysely-org/kysely/releases)
- [Commits](kysely-org/kysely@v0.28.17...v0.29.2)

---
updated-dependencies:
- dependency-name: kysely
  dependency-version: 0.29.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-patch-and-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Wires the Better Auth handler mounted in 1.c.2a to a real UI:

- src/routes/sign-up.tsx and src/routes/sign-in.tsx: email +
  password forms calling authClient.signUp.email / signIn.email,
  with client-side validation (12-128 char password, basic email
  shape) before the network call. On success we navigate home; on
  remote failure we surface the message inline and stay put.
- Header swaps to a session-aware chip + sign-out button once a
  session is active, and shows sign-in / sign-up links otherwise.
- scripts/db-migrate.ts: idempotent runner driven from .env.
  Records applied filenames in _applied_migrations (filename PK,
  applied_at default now), wraps each file in a transaction
  together with the bookkeeping insert, and strips any outer
  begin/commit a file ships with so the runner's tx wraps the
  whole apply cleanly.
- Vitest now resolves the @/ alias the same way the runtime build
  does, and the new sign-up.test.tsx covers the validation and
  success / error paths via mocked authClient + router.

Tested with bun run format:check / lint / typecheck / build /
test - all green.

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
- scripts/db-migrate.ts:
  - BEGIN/COMMIT stripping now accepts leading comment + blank
    lines so 0001's 13-line preamble doesn't keep the inner
    BEGIN, which would have nested-tx-and-closed the runner's
    outer transaction.
  - ROLLBACK runs in its own try/catch — a failing rollback
    no longer shadows the original error the user needs to see.
  - New -- no-transaction marker (scanned in the first 20 lines)
    bypasses the runner's tx wrapper for files that need
    CREATE INDEX CONCURRENTLY etc. Bookkeeping row still lands
    after the body so re-runs don't duplicate.
- src/components/Header.tsx: render nothing while useSession()
  is pending, so the sign-in / sign-up links don't flash for a
  signed-in user on first paint.
- src/routes/sign-in.tsx + sign-up.tsx: trim the email before
  validating and before the network call, and wrap the
  authClient call in try/finally so setLoading(false) runs
  even if the call throws (transport-level failure leaves the
  button stuck on 'Signing in…' otherwise).

Skipped: pg advisory lock. This is a dev-time bootstrap runner
that one developer runs at a time, the _applied_migrations
filename PK already serializes parallel runs cleanly (second
runner hits unique-violation, tx rolls back), and production
migrations should go through the deploy pipeline.

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
A network-level failure on authClient.signOut() used to leave the
user stranded on the current view with no feedback. Wrap the call
in try/catch so the error is logged for diagnostics, then always
navigate to /sign-in - the server has almost certainly cleared its
side of the session anyway, and the redirect forces the next page
load to re-evaluate auth state from scratch instead of relying on
stale client state.

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
feat(auth): add sign-up/sign-in routes and db migrate runner
Mounts Better Auth's jwt() plugin on the auth server so
waveflow-server can verify bearer tokens against a JWKS endpoint
instead of sharing a static secret.

- src/lib/auth.ts: jwt() plugin in the plugins array. Key pair
  config ES256 (P-256 ECDSA SHA-256) - small, widely
  interoperable, and what the jsonwebtoken crate on
  waveflow-server speaks natively. EdDSA / Ed25519 is the Better
  Auth default but a niche pick that some JWKS tooling still
  mishandles. Issuer = BETTER_AUTH_URL, audience defaults to
  waveflow-server (override WAVEFLOW_JWT_AUDIENCE), 15-minute
  expiry.
- db/migrations/0002_jwt_plugin.sql: jwks table per Better Auth's
  schema (id PK, publicKey, privateKey, createdAt, expiresAt) +
  two indexes for the get-latest and get-active-keys queries. No
  BEGIN/COMMIT wrapper - the db:migrate runner owns the tx now.
- .env.example: WAVEFLOW_JWT_AUDIENCE override commented in.
- README.md + db/README.md: JWKS endpoint URL + ES256 algorithm
  + smoke test curl, jwks table description.

The plugin lazy-generates the first key pair on the first sign
attempt, so a fresh install does not need any seeding. Rotation
stays disabled until we have an operational story for it.

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
feat(auth): add jwt plugin and jwks endpoint
…ch-and-minor-3194a310b6

chore: Bump kysely from 0.28.17 to 0.29.2 in the npm-patch-and-minor group
Phase 1.c.3b. The web client now talks to waveflow-server's
/api/v1/profiles surface end-to-end:

- src/lib/server/waveflow-server.ts: pure fetcher that attaches an
  Authorization: Bearer header to a URL composed against
  WAVEFLOW_SERVER_URL. Lives under lib/server/ so it never leaks
  into the browser chunk. Stringifies JSON bodies, throws a typed
  WaveflowServerError carrying status + body on non-2xx, handles
  204 and empty bodies cleanly.
- src/server-fns/profiles.ts: TanStack server functions that mint
  a fresh JWT off the active Better Auth session via
  auth.api.getToken({ headers }) and forward to waveflow-server.
  Browser callers see a plain RPC; the JWT never crosses the wire.
- src/routes/profiles.tsx: first wired UI route. Redirects to
  /sign-in for unauthenticated users (after useSession resolves
  to avoid a spurious bounce), then lists profiles in a card
  grid. Empty state hints the user to use the desktop app or the
  API to create the first one.
- src/components/Header.tsx: Profiles nav link.
- .env.example: WAVEFLOW_SERVER_URL=http://localhost:4000 with a
  comment about why :4000 (so it doesn't collide with our :3000).
- README: new section explaining the server-fn proxy + how to run
  both servers locally.
- src/lib/server/waveflow-server.test.ts: 8 vitest cases covering
  missing env, JSON parse, trailing slashes, body serialization,
  non-2xx error mapping, 204 and empty bodies, and the error type.

This is the client-side half of the cross-repo bridge designed in
PR #16 (lazy auto-provisioning) - the first authenticated request
from a fresh Better Auth signup transparently creates the
waveflow-server users row without a separate onboarding hop.

Tested via bun run format:check / lint / typecheck / build /
test - all green (12/12 tests, including the original 4 for
sign-up).

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
- package.json + bun.lock: roll kysely back to ~0.28.17. The
  dependabot patch+minor PR #13 bumped to ^0.29 without testing
  the build - 0.29 moved DEFAULT_MIGRATION_LOCK_TABLE to the
  kysely/migration subpath and the @better-auth/kysely-adapter
  bundle still imports it from the root, breaking the Nitro
  build with MISSING_EXPORT. Holding at 0.28 until upstream
  catches up.
- .github/dependabot.yml: explicit ignore on kysely>=0.29 so a
  future patch+minor run doesn't re-trigger the same regression.
  Comment links the root cause so the next contributor knows
  when it's safe to lift.
- src/routes/profiles.tsx: move auth + fetch out of useEffect
  into TanStack's beforeLoad + loader. beforeLoad throws
  redirect({to:'/sign-in'}) for unauthenticated visitors
  before the component renders (no flash of empty state).
  loader awaits listProfiles() and returns a tagged-union
  LoaderData so error paths render cleanly. Component reads
  Route.useLoaderData() instead of managing local state.
- src/server-fns/session.ts: new getCurrentSession() server fn
  that resolves the Better Auth session from request cookies
  and returns just {id, email, name} - keeps the wire payload
  minimal so beforeLoad doesn't drag the whole session row
  across the boundary.
- src/server-fns/profiles.ts: stop leaking raw waveflow-server
  error details to the client. Map status -> safe message
  (401 -> 'Session expired', 403 -> 'Access denied',
  5xx -> 'Service unavailable'), log full err server-side
  via console.error for diagnostics.

Tested: format / lint / build / typecheck / test all green
(12/12 tests, build no longer errors).

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
mint-token was called outside the try block, so a better-auth
auth-api-get-token failure (db unreachable, jwks broken, no
session) would propagate raw to the loader and surface its
internal error message in the ui.

move the mint-token call inside the try, introduce a typed
not-signed-in error sentinel so the catch can distinguish it
from backend errors, and map:
- not-signed-in -> session expired prompt
- waveflow-server error -> safe-message-for-status (existing)
- anything else (better-auth raw, network) -> generic message

full err still hits console.error server-side for diagnostics.

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
Better Auth's auth-api-get-token throws an api-error with
status: UNAUTHORIZED when no session is on the request, NOT a
{ token: undefined } payload. The previous null-check would
therefore never fire; the raw api-error would propagate, be
caught by the generic catch in list-profiles, and surface as
'Could not reach waveflow-server' instead of the correct
'Session expired' prompt.

Wrap auth-api-get-token in try/catch, intercept the
unauthorized api-error and rethrow as not-signed-in-error.
Keep the null-check defensive in case a future release flips
to return-null semantics.

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
feat(web): wire /profiles to waveflow-server via tanstack server fn
Phase 1.e.2 — second half of the streaming bridge. Browser plays
tracks served by waveflow-server's signed URL endpoint.

Architecture:
- Browser hits a TanStack server fn (getStreamUrl) that mints a
  JWT off the Better Auth session and calls waveflow-server's
  POST /tracks/{id}/stream-url. The server fn rewrites the
  server-relative URL into an absolute one and returns it.
- Browser plays the audio via a plain <audio src=...> element.
  Media elements skip CORS preflight unless crossorigin is set,
  so the cross-host hit works in dev without CORS config on
  waveflow-server.

New code:
- src/server-fns/_internal.ts: extracted NotSignedInError +
  mintToken + withSafeErrors envelope so every server fn lands
  the same client-safe message mapping (401 / 403 / 404 / 5xx).
- src/server-fns/libraries.ts: listLibraries(profileId).
- src/server-fns/tracks.ts: listTracks({profileId, libraryId}).
- src/server-fns/stream.ts: getStreamUrl({profileId, libraryId,
  trackId}). Returns {url, expiresAt} with the absolute URL.
- src/components/Player.tsx: sticky bottom bar with play / pause,
  seek slider, position / duration readout. <audio key by track id>
  so swaps remount cleanly. Caller keys the Player by current?.id
  (or 'idle') so internal state resets without setState-in-effect
  (React hooks v7 lint rule).
- src/routes/profiles..tsx: per-profile library picker
  via TanStack beforeLoad + loader, same gating as /profiles.
- src/routes/profiles..libraries..tsx: tracks
  table with play buttons + the Player.

Bonus: src/server-fns/profiles.ts shrinks to ~30 LOC now that the
shared envelope lives in _internal.ts.

README updated with the new navigation flow and the rationale for
why <audio> works cross-origin without server-side CORS.

Tested: format / lint / typecheck / build / test all green
(12/12 unchanged; the new server fns + player follow the proven
pattern, no dedicated tests this round).

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
5 valid findings applied:

- src/server-fns/_internal.ts: new asPathId helper that coerces +
  validates path-parameter integers (positive, finite, MAX_SAFE).
  TanStack Start deserialises server-fn payloads as JSON but a
  hand-crafted string payload from a malicious client could still
  reach the URL interpolation; running the parse + range check
  server-side bakes the guarantee in.
- src/server-fns/{libraries,tracks,stream}.ts: inputValidators now
  invoke asPathId on every id segment. Pass-through validators
  replaced with explicit positive-integer checks. listTracks +
  getStreamUrl validate every id in the params object.
- src/server-fns/stream.ts: added a defensive assertion that
  minted.url starts with '/'. waveflow-server is the only writer
  and the path is hand-formatted, so this can't fire today, but a
  future server-side refactor returning an absolute URL would now
  surface as a clear error instead of double-prefixing silently.
- src/components/Player.tsx: canplay listener path was missing the
  already-ready case. After a fast remount (or a browser-cache hit),
  readyState can already be >= HAVE_FUTURE_DATA before we attach the
  listener, and the event would never fire. Check readyState first
  and call the handler directly when applicable.
- src/routes/_authed.profiles..libraries..tsx:
  added a useRef-backed monotonic seq counter on play(). Concurrent
  getStreamUrl calls (fast double-click, spam) used to race; the
  older response could land last and overwrite the newer track.
  The new logic drops responses with a stale seq.

Refactor: pathless _authed layout dedupes auth gating.
- src/routes/_authed.tsx: new pathless layout owns the
  beforeLoad -> getCurrentSession -> redirect dance. Children
  inherit the guard.
- Renamed the three protected routes to _authed.profiles.tsx,
  _authed.profiles.$profileId.tsx, _authed.profiles.$profileId
  .libraries.$libraryId.tsx. The underscore prefix is a TanStack
  file-router convention: the segment contributes nothing to the
  URL but groups children under a shared parent. Per-route
  beforeLoad bodies dropped.

Skipped one finding with reason:
- CR proposed normalising minted.url for absolute / no-leading-slash
  cases. The server-side format!() always produces a leading '/' and
  is never absolute. Adding the suggested normalisation would handle
  a case that can't happen today; the explicit assertion above guards
  the same regression more cheaply.

Tested: format / lint / typecheck / build / test all green
(12/12 tests unchanged).

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
feat(player): minimal browser player wired to waveflow-server stream
Phase 1.f.desktop.1b — bridges the WaveFlow desktop's planned
tiny_http loopback listener (mirroring the existing
`commands::spotify` pattern) to Better Auth's JWT minting:

1. Desktop binds 127.0.0.1:PORT/cb, generates a random `state`, opens
   the browser to `/desktop-login?cb=…&state=…`.
2. `resolveDesktopLogin` server fn validates `cb` (loopback only),
   checks for a Better Auth session, and mints a fresh JWT via
   `auth.api.getToken`.
3. Browser is server-side-redirected (302) to `<cb>?token=…&state=…`.
   Desktop listener validates `state` and stores the JWT.

Security boundary lives in `parseLoopback`:
- `protocol === 'http:'` (loopback doesn't get TLS, the desktop
  listener is plain).
- `hostname ∈ { '127.0.0.1', 'localhost', '[::1]' }` — no external
  hosts. A malicious link with `cb=http://attacker.com:49388/cb`
  would let a phishing site exfiltrate the JWT, so the validator
  rejects everything that isn't unambiguously loopback.
- `port ∈ [1024, 65535]` — non-privileged.
- Case-insensitive hostname comparison.

Sign-in route gains a `continue` search param so the no-session path
can resume the OAuth flow after login. `safeContinueTarget`
whitelists `/desktop-login` to keep it from becoming an
open-redirect.

Live tests: 13 cases on `parseLoopback` covering every accept +
reject branch (privileged ports, IP-confusing unicode hostnames,
non-http schemes, external hosts, etc.).

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
@coderabbitai on PR #18 surfaced two findings:

1. `safeContinueTarget` accepted `/desktop-login/../admin` and similar
   path-traversal payloads because `startsWith` ran on the raw input.
   The browser would normalise the URL after navigation and land the
   user on `/admin`, defeating the prefix gate. Fix: parse against a
   dummy base, reject any value whose normalised `origin` isn't the
   base (catches absolute / protocol-relative URLs), then check the
   normalised pathname. Adds 18 test cases covering path traversal,
   trailing-slash edge cases, host-injection variants
   (`https://attacker.com`, `//attacker.com`, `javascript:`,
   `data:` schemes), and the legitimate happy path.

2. `bun run format:check` failed on `desktop-login.tsx` (one stray
   line over `printWidth`). Ran `prettier --write` across the three
   files this PR touches — no behaviour change, just formatting.

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
Subtree-merges the waveflow-web React app under web/ so the JWT
contract, sync wire shape and schema parity can be reasoned about
in a single repo. The Rust service stays at the repo root unchanged;
the Tauri desktop app keeps living in InstaZDLL/WaveFlow on a
separate cadence.

Why now: the next big chunk of work — sync v2 (backfill + status UI)
and a web-UI refonte — touches both halves in lockstep. Splitting
PRs across two repos was costing one extra coordination round per
change before; the dev-stack itself documents a clone-clone-glue
ritual no contributor should have to remember.

Layout

  /                # axum + sqlx Rust service, unchanged
  /src/, /tests/   # server only
  /migrations/     # Postgres schema (sqlx)
  /web/            # React + TanStack Start + Better Auth
  /web/db/         # Better Auth schema (hand-written)
  /web/packages/   # @waveflow/design-tokens (workspace dep)

CI re-split

  .github/workflows/ci-rust.yml   # was ci.yml, now path-filtered
  .github/workflows/ci-web.yml    # moved from web/, gated to web/**
  .github/workflows/codeql.yml    # path-filtered to Rust changes
  .github/workflows/dco.yml       # unchanged (server's stricter check)
  .github/workflows/label-pr.yml  # adopted from web/

Dependabot now configures cargo at /, npm at /web, and a single
github-actions entry. The labeler config gains top-level
scope:server / scope:web labels plus the existing finer scopes
remapped under the new path tree.

Cargo workspace untouched — web/ has no Cargo.toml so the Rust
toolchain ignores it. `cargo check --all-targets --all-features`
still passes.

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
@InstaZDLL InstaZDLL self-assigned this Jun 11, 2026
Comment thread .github/workflows/ci-web.yml Fixed
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 12ffb55a-4274-4bb6-87c9-162b02ff22fc

📥 Commits

Reviewing files that changed from the base of the PR and between 91a3701 and 0cfc9f6.

📒 Files selected for processing (1)
  • .github/workflows/ci-web.yml

📝 Walkthrough

Walkthrough

PR ajoutant le sous-projet web complet : configs CI/outillage, templates, migrations Better Auth, server-fns (API bridge vers waveflow-server), theming, auth client, player/context, composants UI, routes/pages et une large suite de tests.

Changes

Monorepo web et fonctionnalités front

Layer / File(s) Summary
Configuration et documentation
.github/*, README.md, CLAUDE.md, web/.coderabbit.yaml, web/*config*, web/README.md, web/CONTRIBUTING.md, web/LICENSE, web/scripts/db-migrate.ts, web/db/migrations/*
Ajout/ajustement des templates issues/PR, Dependabot/labeler, workflows CI pour Rust et Web, docs monorepo/web, licence AGPL web, runner de migrations et migrations Better Auth.
Server functions et intégration API
web/src/lib/server/waveflow-server.ts, web/src/server-fns/*
Ajout des helpers waveflowFetch/waveflowFetchPublic et des server-fns : profiles, libraries, tracks, albums, artists, playlists, providers, session, share, stream, theme, desktop-login, avec validation/gestion d’erreurs (mintToken, withSafeErrors, asPathId).
Theme system & shell
web/packages/design-tokens/*, web/src/components/Theme*, web/src/components/ThemeStyle.tsx, web/src/routes/__root.tsx, web/src/styles.css
Système de thèmes (palettes, presets, apply/theme CSS), SSR injection ThemeStyle, ThemeProvider/Picker/Toggle et styles globaux.
Auth client & Better Auth infra
web/src/lib/auth.ts, web/src/lib/db.ts, web/src/lib/auth-client.ts, web/src/routes/api/auth/$, web/.env.example
Configuration Better Auth server/client, validation d’env, Kysely/pg pool pour plugin Better Auth, route catch-all /api/auth/*.
Player context & audio UI
web/src/lib/player-context.tsx, web/src/components/PlayerBar.tsx, web/src/components/QueuePanel.tsx, web/src/components/NowPlayingOverlay.tsx, web/src/components/PlayableTrackList.tsx
Contexte de lecture (minting d’URL signée, queue/history, protections contre races), composants UI persistants de lecture et interactions (seek, volume, queue).
Pages / Routes métier
web/src/routes/* (profiles, libraries, albums, artists, tracks, playlists, sign-in/sign-up, desktop-login, p.$token, index, about)
Ajout des routes TanStack file routes et loaders SSR, views pour profils/bibliothèques/albums/artists/playlists, pages publiques de partage et auth flows (avec input validation et erreurs non bloquantes quand approprié).
Tests & outil de dev
web/src/**/*.test.*, web/vitest.config.ts, web/eslint.config.js, web/.commitlintrc.cjs, web/.husky/*
Large batterie de tests Vitest/RTL couvrant server-fns, hooks, provider, composants et routes; config Vitest/ESLint/Prettier/Commitlint et hooks husky.

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant WebApp as Waveflow Web (Nitro)
  participant WaveflowServer
  Browser->>WebApp: SSR request / route loaders (getStoredThemeId, getCurrentSession)
  WebApp->>WebApp: resolve theme, inject ThemeStyle, hydrate ThemeProvider
  WebApp->>WaveflowServer: waveflowFetch (mintToken + API calls)
  WaveflowServer-->>WebApp: JSON responses (tracks, playlists, stream-url)
  Browser->>WebApp: client actions (playTrack -> server-fn getStreamUrl)
  WebApp->>WaveflowServer: POST /stream-url (via mint token)
  WaveflowServer-->>WebApp: relative streaming URL
  WebApp->>Browser: absolute streaming URL returned to client
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~90+ minutes

"Un monorepo s’illumine,
thèmes, sessions et pistes s’alignent.
🎧🌓
Les routes s’ouvrent, les cartes répondent,
et les playlists prennent le monde."

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/monorepo-merge

CodeQL's `actions/unpinned-tag` flagged the `@v2` tag on
`oven-sh/setup-bun` in `ci-web.yml`. Match the rest of the repo's
workflows (`actions/checkout`, `dtolnay/rust-toolchain`,
`Swatinem/rust-cache`) which pin every third-party action to its
release commit SHA: an upstream maintainer (or a compromised account)
that retargets the `v2` tag can no longer execute code in CI.

Pinned to `0c5077e5...` (tag `v2.2.0`, the head `v2` currently
points at). Dependabot will bump it via the github-actions
ecosystem rule already configured in `.github/dependabot.yml`.

Signed-off-by: InstaZDLL <github.105mh@8shield.net>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 23

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/src/routes/sign-in.test.ts (1)

1-63: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Nom de fichier de test non conforme, avec risque d’inclusion dans l’arbre de routes.

Ce fichier est en .test.ts sous src/routes. Renomme-le en .test.tsx (et applique la convention de préfixe attendue) pour garantir son exclusion du route tree.

As per coding guidelines: web/src/routes/**/*.test.{ts,tsx} — “Test files in src/routes/ must end in .test.tsx to be excluded from the route tree via the - prefix convention”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/routes/sign-in.test.ts` around lines 1 - 63, Rename the test file so
it is excluded from the route tree: move/rename web/src/routes/sign-in.test.ts
to web/src/routes/-sign-in.test.tsx (change extension to .tsx and add the
leading '-' route-exclusion prefix), and update any imports or references if
needed; the tests and references to safeContinueTarget in sign-in.test.ts should
continue to work after the rename.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/ISSUE_TEMPLATE/config.yml:
- Around line 4-8: Les liens de contact dans la configuration d'issue template
pointent toujours vers l'ancien dépôt; mettez à jour les valeurs des champs url
pour l'entrée générique (le champ avec "about: For open-ended questions...") et
pour l'entrée nommée "Security vulnerabilities" afin qu'ils pointent vers les
pages Discussions et Security Advisories du dépôt monorepo courant (remplacer
les occurrences de "https://github.com/InstaZDLL/waveflow-web/..." par les URL
correctes du monorepo), en conservant les champs name et about existants
("Security vulnerabilities") pour que le comportement reste identique.

In @.github/labeler.yml:
- Around line 55-56: Les glob actuels 'web/src/routes/sign-in/**' et
'web/src/routes/sign-up/**' ne matchent que des fichiers/dossiers sous un
répertoire sign-in/ ou sign-up/, pas des fichiers comme sign-in.tsx ou
sign-up.tsx ; remplace ces deux patterns par des patterns qui couvrent à la fois
le fichier au niveau du dossier et son éventuel sous-dossier, par exemple
'web/src/routes/sign-in{,/**}' et 'web/src/routes/sign-up{,/**}', afin que les
PR touchant sign-in.tsx / sign-up.tsx reçoivent bien le label scope: auth.

In @.github/pull_request_template.md:
- Line 1: Remplace le header de niveau 2 "## Summary" par un header de niveau 1
en tête du fichier (remplacer "## Summary" par "# Summary") afin de satisfaire
la règle de lint Markdown MD041; cherche la chaîne exacte "## Summary" dans
.github/pull_request_template.md et mets simplement un seul dièse (#) devant le
mot Summary.

In @.github/workflows/ci-web.yml:
- Around line 37-42: La step utilisant actions/checkout (uses:
actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd) doit ajouter
persist-credentials: false to prevent the workflow from persisting the GitHub
token in .git/config; update the Checkout step's with: block to include
persist-credentials: false (alongside the existing fetch-depth: 1) so
credentials are not left in the repository config.

In @.github/workflows/label-pr.yml:
- Around line 3-5: Ajouter un bloc de concurrency au niveau racine du workflow
pour éviter les courses entre runs de labeling : créer une clé concurrency avec
group construite sur l'identifiant de la PR (par ex. utiliser
github.event.pull_request.number ou github.ref) et cancel-in-progress: true afin
qu'un seul job de labeling traite la PR à la fois; placez ce bloc à côté de la
clé on/pull_request_target dans le fichier du workflow pour protéger les actions
de labeling concurrentes.
- Around line 7-9: Remove the top-level permissions block and instead add a
permissions: { contents: read, pull-requests: write } entry to each job that
needs write access (jobs named scope, type, and size) so only those jobs get
pull-requests: write; keep other jobs with minimal permissions (e.g., contents:
read) as appropriate and ensure the workflow still triggers correctly for
pull_request_target events.

In `@web/.gitignore`:
- Around line 6-8: The .gitignore currently lists .env, .env.local and
.env.*.local but still allows files like .env.production or .env.staging to be
committed; update web/.gitignore to broaden the pattern to cover all env
variants (e.g., add a rule matching *.env* or *.env.*) so any file named with
.env suffix is ignored, and if you need to keep a checked-in template (like
.env.example) explicitly whitelist it with a negation rule (!.env.example).
Ensure the change is made in web/.gitignore and preserves existing intent.

In `@web/package.json`:
- Around line 9-13: Update package.json fields that still reference the old
repository: change the "repository.url" value and the "bugs" value so they point
to the consolidated monorepo URL "https://github.com/InstaZDLL/waveflow-server"
instead of "https://github.com/InstaZDLL/waveflow-web"; specifically edit the
"repository" object (repository.url) and the "bugs" property to the new URL.

In `@web/public/manifest.json`:
- Around line 2-3: Remplacez les valeurs placeholder dans le fichier
manifest.json pour éviter le mauvais branding : mettez à jour les champs
"short_name" et "name" (actuellement "TanStack App" et "Create TanStack App
Sample") avec le nom réel de votre application (et, si nécessaire, une version
courte pour "short_name"); vérifiez aussi que ces champs ne sont pas écrasés par
un script de build ou d'injection d'environnement afin qu'ils soient inclus
correctement dans l'app installée.

In `@web/README.md`:
- Line 46: The README references a non-existent route file
`src/routes/profiles.tsx` for the /profiles route; update the link so it points
to the correct route implementation (or remove the broken link) — locate the
implementation that backs the /profiles route (e.g., the route component that
interacts with `src/server-fns/profiles.ts`) and replace the obsolete
`src/routes/profiles.tsx` reference in the README with that actual file path or
a descriptive, accurate reference to the /profiles route.

In `@web/scripts/db-migrate.ts`:
- Line 38: The MIGRATIONS_DIR constant in web/scripts/db-migrate.ts currently
uses process.cwd() which can point to the repo root instead of the web package;
change MIGRATIONS_DIR to resolve relative to this module so it always targets
web/db/migrations (e.g. compute the directory from the current file’s location
via __dirname or import.meta.url and then join that with '..', 'db',
'migrations'); update the MIGRATIONS_DIR definition to use that resolved path so
migrations are loaded from the web package regardless of where the process is
started.

In `@web/src/components/Footer.tsx`:
- Line 23: La mention de licence affichée dans le composant Footer (Footer.tsx)
indique "AGPL-3.0" alors que le repo utilise "AGPL-3.0-only": ouvrez le
composant Footer (ou cherchez la chaîne ". Open source under AGPL-3.0.") et
remplacez la chaîne par la formulation exacte "AGPL-3.0-only" afin d'aligner
l'affichage avec la licence effective du projet; conservez la ponctuation et
l'espacement existants et exécutez un build / vérification statique pour valider
le changement.

In `@web/src/components/PlayerBar.tsx`:
- Around line 219-231: The onPointerCancel handler currently clears seekScrub
without committing the user's scrub position; update the handler referenced
(onPointerCancel) to mirror onPointerUp by checking if seekScrub !== null and
calling player.seek(seekScrub) before calling setSeekScrub(null) so canceled
pointer drags still apply the chosen seek position.

In `@web/src/components/ThemeToggle.tsx`:
- Around line 10-13: Wrap all direct localStorage operations in try/catch to
prevent exceptions from breaking the ThemeToggle render/interaction: when
reading, replace the bare window.localStorage.getItem('theme') call (the stored
variable) with a safe accessor that returns a sane default (e.g. 'auto' or
undefined) on error; when writing, guard window.localStorage.setItem('theme',
...) inside try/catch in the theme update handler (e.g. the toggle/onChange or
setTheme code path) so failures are swallowed or logged but do not throw. Locate
references to stored, window.localStorage.getItem('theme') and
window.localStorage.setItem('theme', ...) inside the ThemeToggle component and
add error handling around them.

In `@web/src/lib/db.ts`:
- Around line 25-30: The current parsing of BETTER_AUTH_DB_MAX using
Number.parseInt allows values like "10foo" to become 10; update the validation
in web/src/lib/db.ts so BETTER_AUTH_DB_MAX is first checked against a strict
digits-only pattern (e.g. /^\d+$/) before converting, or use Number(value) and
verify Number.isInteger and >0; then set the max variable only after that strict
validation and throw the existing error message if the check fails (refer to
BETTER_AUTH_DB_MAX and the max variable in the file).

In `@web/src/lib/use-focus-trap.ts`:
- Around line 67-72: Lorsque items.length === 0 in use-focus-trap, calling
container.focus() can silently fail if the container has no tabindex; make the
"no focusable" fallback reliable by ensuring the container is focusable before
calling focus: detect whether the container already has a tabindex (or is
naturally focusable), if not set a temporary tabindex="-1", call
container.focus(), then remove the temporary tabindex if it was added; keep the
existing event.preventDefault() behavior and update the logic inside the
items.length === 0 branch (referencing container and the items-length check in
use-focus-trap) so focus never leaks when the container initially isn’t
focusable.

In `@web/src/routes/_authed.profiles`.$profileId.libraries.$libraryId.artists.tsx:
- Around line 17-21: The catch block in the loader that currently returns {
kind: 'error', message: err instanceof Error ? err.message : 'Failed to load
artists.' } exposes raw exception text to the UI; change it to return a stable,
generic user-facing message (e.g., 'Failed to load artists.') instead of
err.message, and move the detailed err logging to server-side logs
(console.error or the existing logger) so error details are not returned to the
client; apply the same change for the analogous catch/return in the playlists
loader file.

In `@web/src/routes/_authed.profiles`.$profileId.playlists.test.tsx:
- Around line 25-27: The mock Link uses the type React.PropsWithChildren without
importing React, breaking typechecking; either import React or, better, import
PropsWithChildren and/or ReactNode from 'react' and update the signature to use
those names (e.g., import { PropsWithChildren } from 'react' and declare Link:
({ children, ...rest }: PropsWithChildren<Record<string, unknown>>) => ...), or
replace the type with an inline type like ({ children, ...rest }: { children?:
React.ReactNode; [key: string]: unknown }) => ...; update the Link mock
accordingly so the used types are imported from 'react' or inlined.

In `@web/src/routes/_authed.profiles.tsx`:
- Around line 57-59: The component renders p.last_used_at with new
Date(...).toLocaleDateString() which yields non-deterministic SSR vs client
output; instead compute a stable formatted string in the route loader (e.g. use
Intl.DateTimeFormat with an explicit locale and timeZone) and attach it to each
profile as something like last_used_at_formatted, then update the JSX in
_authed.profiles.tsx to render p.last_used_at_formatted instead of calling
toLocaleDateString in the component; ensure you reference the existing
p.last_used_at when creating the formatted value and use a consistent
locale/timeZone in the formatter.

In `@web/src/routes/sign-up.test.tsx`:
- Around line 1-103: Add unit tests to sign-up.test.tsx that cover the missing
edge cases: add a test asserting the form rejects an empty display name
(exercise the validation branch in SignUp for the name field), a test for the
maximum password length using the MAX_PASSWORD limit (construct a too-long
password and expect a validation alert and no call to signUpEmail), and tests
verifying trimming behavior by submitting name and email with surrounding
whitespace (expect the values passed to signUpEmail to be trimmed and navigation
on success); use the existing fillForm helper, the mocked signUpEmail and
navigate, and the SignUp render to keep consistency with the current suite.

In `@web/src/server-fns/session.ts`:
- Around line 21-24: Wrap the call to auth.api.getSession in a try/catch inside
the server function in session.ts (where session is obtained) and explicitly
handle the unauthenticated case: if the caught error is an APIError with
code/message indicating 'UNAUTHORIZED' (or similar) return null, otherwise
rethrow the error; keep the existing early-return when session?.user is absent.
Ensure you reference the auth.api.getSession invocation and the session variable
in your change.

In `@web/src/styles.css`:
- Around line 5-7: Le bloc `@theme` dans web/src/styles.css doit passer en mode
inline et consommer les tokens OKLCH du package design-tokens : remplacez la
déclaration `@theme` { ... } par la syntaxe inline (par ex. `@theme` "--name" { ...
} ou l'équivalent attendu par votre build) et retirez les redéfinitions locales
en rgba dans :root pour --surface / --surface-strong ; au lieu de cela importez
ou remappez les variables depuis web/packages/design-tokens (ajoutez les
variables --accent-* et utilisez oklch(...) pour les couleurs de surface et
accent) en conservant --font-sans tel quel pour éviter de toucher à la pile de
polices.

In `@web/vitest.config.ts`:
- Line 30: The Vitest config currently sets passWithNoTests: true which can mask
test discovery failures; update the configuration in web/vitest.config.ts to
disable this by removing the setting or changing passWithNoTests to false (keep
the existing include patterns intact) so CI fails when no tests are found.

---

Outside diff comments:
In `@web/src/routes/sign-in.test.ts`:
- Around line 1-63: Rename the test file so it is excluded from the route tree:
move/rename web/src/routes/sign-in.test.ts to web/src/routes/-sign-in.test.tsx
(change extension to .tsx and add the leading '-' route-exclusion prefix), and
update any imports or references if needed; the tests and references to
safeContinueTarget in sign-in.test.ts should continue to work after the rename.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0c0597e1-b8b8-411f-95c7-15e116f9dd31

📥 Commits

Reviewing files that changed from the base of the PR and between 52ca01f and 91a3701.

⛔ Files ignored due to path filters (4)
  • web/bun.lock is excluded by !**/*.lock
  • web/public/favicon.ico is excluded by !**/*.ico
  • web/public/logo192.png is excluded by !**/*.png
  • web/public/logo512.png is excluded by !**/*.png
📒 Files selected for processing (124)
  • .github/ISSUE_TEMPLATE/bug.yml
  • .github/ISSUE_TEMPLATE/config.yml
  • .github/ISSUE_TEMPLATE/feature.yml
  • .github/dependabot.yml
  • .github/labeler.yml
  • .github/pull_request_template.md
  • .github/workflows/ci-rust.yml
  • .github/workflows/ci-web.yml
  • .github/workflows/codeql.yml
  • .github/workflows/label-pr.yml
  • CLAUDE.md
  • README.md
  • web/.coderabbit.yaml
  • web/.commitlintrc.cjs
  • web/.env.example
  • web/.gitignore
  • web/.husky/commit-msg
  • web/.prettierignore
  • web/.prettierrc.json
  • web/.vscode/settings.json
  • web/CLAUDE.md
  • web/CONTRIBUTING.md
  • web/LICENSE
  • web/README.md
  • web/db/README.md
  • web/db/migrations/0001_better_auth_initial.sql
  • web/db/migrations/0002_jwt_plugin.sql
  • web/eslint.config.js
  • web/package.json
  • web/packages/design-tokens/package.json
  • web/packages/design-tokens/src/apply.test.ts
  • web/packages/design-tokens/src/apply.ts
  • web/packages/design-tokens/src/index.ts
  • web/packages/design-tokens/src/palettes.ts
  • web/packages/design-tokens/src/themes.test.ts
  • web/packages/design-tokens/src/themes.ts
  • web/packages/design-tokens/tsconfig.json
  • web/public/manifest.json
  • web/public/robots.txt
  • web/scripts/db-migrate.ts
  • web/src/components/DeletePlaylistDialog.test.tsx
  • web/src/components/DeletePlaylistDialog.tsx
  • web/src/components/Footer.tsx
  • web/src/components/Header.tsx
  • web/src/components/NowPlayingOverlay.test.tsx
  • web/src/components/NowPlayingOverlay.tsx
  • web/src/components/OAuthButtons.tsx
  • web/src/components/PlayableTrackList.test.tsx
  • web/src/components/PlayableTrackList.tsx
  • web/src/components/PlayerBar.tsx
  • web/src/components/PlaylistFormDialog.test.tsx
  • web/src/components/PlaylistFormDialog.tsx
  • web/src/components/QueuePanel.test.tsx
  • web/src/components/QueuePanel.tsx
  • web/src/components/ThemePicker.test.tsx
  • web/src/components/ThemePicker.tsx
  • web/src/components/ThemeProvider.test.tsx
  • web/src/components/ThemeProvider.tsx
  • web/src/components/ThemeStyle.tsx
  • web/src/components/ThemeToggle.tsx
  • web/src/components/TrackFilterBar.test.tsx
  • web/src/components/TrackFilterBar.tsx
  • web/src/components/WaveflowLogo.tsx
  • web/src/lib/auth-client.ts
  • web/src/lib/auth.ts
  • web/src/lib/db.ts
  • web/src/lib/format-time.ts
  • web/src/lib/player-context.test.tsx
  • web/src/lib/player-context.tsx
  • web/src/lib/server/waveflow-server.test.ts
  • web/src/lib/server/waveflow-server.ts
  • web/src/lib/share-format.test.ts
  • web/src/lib/share-format.ts
  • web/src/lib/use-focus-trap.test.tsx
  • web/src/lib/use-focus-trap.ts
  • web/src/router.tsx
  • web/src/routes/__root.tsx
  • web/src/routes/_authed.profiles.$profileId.libraries.$libraryId.albums.$albumId.test.tsx
  • web/src/routes/_authed.profiles.$profileId.libraries.$libraryId.albums.$albumId.tsx
  • web/src/routes/_authed.profiles.$profileId.libraries.$libraryId.albums.test.tsx
  • web/src/routes/_authed.profiles.$profileId.libraries.$libraryId.albums.tsx
  • web/src/routes/_authed.profiles.$profileId.libraries.$libraryId.artists.$artistId.test.tsx
  • web/src/routes/_authed.profiles.$profileId.libraries.$libraryId.artists.$artistId.tsx
  • web/src/routes/_authed.profiles.$profileId.libraries.$libraryId.artists.test.tsx
  • web/src/routes/_authed.profiles.$profileId.libraries.$libraryId.artists.tsx
  • web/src/routes/_authed.profiles.$profileId.libraries.$libraryId.tsx
  • web/src/routes/_authed.profiles.$profileId.playlists.$playlistId.test.tsx
  • web/src/routes/_authed.profiles.$profileId.playlists.$playlistId.tsx
  • web/src/routes/_authed.profiles.$profileId.playlists.test.tsx
  • web/src/routes/_authed.profiles.$profileId.playlists.tsx
  • web/src/routes/_authed.profiles.$profileId.tsx
  • web/src/routes/_authed.profiles.tsx
  • web/src/routes/_authed.settings.test.tsx
  • web/src/routes/_authed.settings.tsx
  • web/src/routes/_authed.tsx
  • web/src/routes/about.tsx
  • web/src/routes/api/auth/$.ts
  • web/src/routes/desktop-login.tsx
  • web/src/routes/index.tsx
  • web/src/routes/p.$token.tsx
  • web/src/routes/sign-in.test.ts
  • web/src/routes/sign-in.tsx
  • web/src/routes/sign-up.test.tsx
  • web/src/routes/sign-up.tsx
  • web/src/server-fns/_internal.ts
  • web/src/server-fns/albums.ts
  • web/src/server-fns/artists.ts
  • web/src/server-fns/desktop-login.test.ts
  • web/src/server-fns/desktop-login.ts
  • web/src/server-fns/libraries.ts
  • web/src/server-fns/playlists.ts
  • web/src/server-fns/profiles.ts
  • web/src/server-fns/providers.test.ts
  • web/src/server-fns/providers.ts
  • web/src/server-fns/session.ts
  • web/src/server-fns/share.test.ts
  • web/src/server-fns/share.ts
  • web/src/server-fns/stream.ts
  • web/src/server-fns/theme.ts
  • web/src/server-fns/tracks.ts
  • web/src/styles.css
  • web/tsconfig.json
  • web/vite.config.ts
  • web/vitest.config.ts

Comment on lines +4 to +8
url: https://github.com/InstaZDLL/waveflow-web/discussions
about: For open-ended questions or proposals that aren't yet bugs or feature requests.
- name: Security vulnerabilities
url: https://github.com/InstaZDLL/waveflow-web/security/advisories/new
about: Please use GitHub's private vulnerability reporting — don't open a public issue.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Liens de contact/sécurité pointent encore vers l’ancien dépôt.

Les URLs en Line 4 et Line 7 ciblent waveflow-web au lieu du dépôt monorepo courant, ce qui peut détourner les signalements (notamment sécurité) vers le mauvais projet.

Correctif proposé
-    url: https://github.com/InstaZDLL/waveflow-web/discussions
+    url: https://github.com/InstaZDLL/waveflow-server/discussions
@@
-    url: https://github.com/InstaZDLL/waveflow-web/security/advisories/new
+    url: https://github.com/InstaZDLL/waveflow-server/security/advisories/new
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
url: https://github.com/InstaZDLL/waveflow-web/discussions
about: For open-ended questions or proposals that aren't yet bugs or feature requests.
- name: Security vulnerabilities
url: https://github.com/InstaZDLL/waveflow-web/security/advisories/new
about: Please use GitHub's private vulnerability reporting — don't open a public issue.
url: https://github.com/InstaZDLL/waveflow-server/discussions
about: For open-ended questions or proposals that aren't yet bugs or feature requests.
- name: Security vulnerabilities
url: https://github.com/InstaZDLL/waveflow-server/security/advisories/new
about: Please use GitHub's private vulnerability reporting — don't open a public issue.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/ISSUE_TEMPLATE/config.yml around lines 4 - 8, Les liens de contact
dans la configuration d'issue template pointent toujours vers l'ancien dépôt;
mettez à jour les valeurs des champs url pour l'entrée générique (le champ avec
"about: For open-ended questions...") et pour l'entrée nommée "Security
vulnerabilities" afin qu'ils pointent vers les pages Discussions et Security
Advisories du dépôt monorepo courant (remplacer les occurrences de
"https://github.com/InstaZDLL/waveflow-web/..." par les URL correctes du
monorepo), en conservant les champs name et about existants ("Security
vulnerabilities") pour que le comportement reste identique.

Comment thread .github/labeler.yml
Comment on lines +55 to +56
- 'web/src/routes/sign-in/**'
- 'web/src/routes/sign-up/**'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Glob invalide pour les routes d’auth (label non appliqué).

En Line 55-56, sign-in/** et sign-up/** ne matchent pas des fichiers comme sign-in.tsx / sign-up.tsx. Résultat : les PR auth peuvent ne pas recevoir scope: auth.

Correctif proposé
-          - 'web/src/routes/sign-in/**'
-          - 'web/src/routes/sign-up/**'
+          - 'web/src/routes/sign-in.*'
+          - 'web/src/routes/sign-up.*'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- 'web/src/routes/sign-in/**'
- 'web/src/routes/sign-up/**'
- 'web/src/routes/sign-in.*'
- 'web/src/routes/sign-up.*'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/labeler.yml around lines 55 - 56, Les glob actuels
'web/src/routes/sign-in/**' et 'web/src/routes/sign-up/**' ne matchent que des
fichiers/dossiers sous un répertoire sign-in/ ou sign-up/, pas des fichiers
comme sign-in.tsx ou sign-up.tsx ; remplace ces deux patterns par des patterns
qui couvrent à la fois le fichier au niveau du dossier et son éventuel
sous-dossier, par exemple 'web/src/routes/sign-in{,/**}' et
'web/src/routes/sign-up{,/**}', afin que les PR touchant sign-in.tsx /
sign-up.tsx reçoivent bien le label scope: auth.

@@ -0,0 +1,27 @@
## Summary

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Premier titre non conforme au lint Markdown (MD041).

En Line 1, le fichier démarre par un ## au lieu d’un H1. Ajoute un titre de niveau 1 en tête pour supprimer l’avertissement.

Correctif proposé
+# Pull Request
+
 ## Summary
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## Summary
# Pull Request
## Summary
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 1-1: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/pull_request_template.md at line 1, Remplace le header de niveau 2
"## Summary" par un header de niveau 1 en tête du fichier (remplacer "##
Summary" par "# Summary") afin de satisfaire la règle de lint Markdown MD041;
cherche la chaîne exacte "## Summary" dans .github/pull_request_template.md et
mets simplement un seul dièse (#) devant le mot Summary.

Source: Linters/SAST tools

Comment on lines +37 to +42
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
# `actions/checkout` shallow-clones by default; fine for
# this job — we don't grep git history.
fetch-depth: 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Ajouter persist-credentials: false au checkout pour éviter la fuite de credentials.

L'action actions/checkout persiste par défaut le token GitHub dans .git/config, ce qui peut permettre à un script malveillant (ou une dépendance compromise) d'exfiltrer le token via git push. Les autres workflows du repo ajoutent cette protection.

🔒 Correctif proposé
       - name: Checkout
         uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
         with:
+          persist-credentials: false
           # `actions/checkout` shallow-clones by default; fine for
           # this job — we don't grep git history.
           fetch-depth: 1
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 37-42: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci-web.yml around lines 37 - 42, La step utilisant
actions/checkout (uses:
actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd) doit ajouter
persist-credentials: false to prevent the workflow from persisting the GitHub
token in .git/config; update the Checkout step's with: block to include
persist-credentials: false (alongside the existing fetch-depth: 1) so
credentials are not left in the repository config.

Source: Linters/SAST tools

Comment on lines +3 to +5
on:
pull_request_target:
types: [opened, reopened, synchronize, edited]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Ajouter un groupe concurrency pour éviter les races entre labels.

Si plusieurs commits sont poussés rapidement sur une PR, les jobs de labeling peuvent s'exécuter en parallèle et créer des incohérences (un job lit l'ancien état pendant qu'un autre écrit). Un groupe de concurrence avec cancel-in-progress: true garantit qu'un seul run traite la PR à la fois.

⚙️ Correctif proposé
 on:
   pull_request_target:
     types: [opened, reopened, synchronize, edited]

+concurrency:
+  group: label-pr-${{ github.workflow }}-${{ github.event.pull_request.number }}
+  cancel-in-progress: true
+
 permissions:
   contents: read
   pull-requests: write
🧰 Tools
🪛 zizmor (1.25.2)

[error] 3-5: use of fundamentally insecure workflow trigger (dangerous-triggers): pull_request_target is almost always used insecurely

(dangerous-triggers)


[warning] 3-5: insufficient job-level concurrency limits (concurrency-limits): workflow is missing concurrency setting

(concurrency-limits)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/label-pr.yml around lines 3 - 5, Ajouter un bloc de
concurrency au niveau racine du workflow pour éviter les courses entre runs de
labeling : créer une clé concurrency avec group construite sur l'identifiant de
la PR (par ex. utiliser github.event.pull_request.number ou github.ref) et
cancel-in-progress: true afin qu'un seul job de labeling traite la PR à la fois;
placez ce bloc à côté de la clé on/pull_request_target dans le fichier du
workflow pour protéger les actions de labeling concurrentes.

Source: Linters/SAST tools

Comment on lines +57 to +59
<p className="mt-1 text-xs text-[var(--sea-ink-soft)]">
Last used {new Date(p.last_used_at).toLocaleDateString()}
</p>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="web/src/routes/_authed.profiles.tsx"
if [ ! -f "$FILE" ]; then
  echo "Missing file: $FILE"
  exit 1
fi

echo "---- Context around lines 45-80 in $FILE ----"
nl -ba "$FILE" | sed -n '45,80p'

Repository: InstaZDLL/waveflow-server

Length of output: 207


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find where `last_used_at` is shaped/parsed in the web codebase
rg -n "last_used_at" web/src/routes web/src -S || true

Repository: InstaZDLL/waveflow-server

Length of output: 418


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="web/src/routes/_authed.profiles.tsx"
echo "---- Lines 45-80 ($FILE) ----"
awk 'NR>=45 && NR<=80 {printf "%d:%s\n", NR, $0}' "$FILE"

Repository: InstaZDLL/waveflow-server

Length of output: 1083


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the server shaping of last_used_at
echo "---- web/src/server-fns/profiles.ts ----"
awk 'NR>=1 && NR<=120 {printf "%d:%s\n", NR, $0}' web/src/server-fns/profiles.ts

echo "---- web/src/server-fns/libraries.ts ----"
awk 'NR>=1 && NR<=120 {printf "%d:%s\n", NR, $0}' web/src/server-fns/libraries.ts

Repository: InstaZDLL/waveflow-server

Length of output: 2394


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check for other date formatting with locale/timezone fixes
rg -n "toLocaleDateString\(" web/src -S || true
rg -n "Intl\.DateTimeFormat" web/src -S || true
rg -n "timeZone|timezone|date-fns|dayjs|luxon" web/src -S || true

Repository: InstaZDLL/waveflow-server

Length of output: 417


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Determine whether route is server-rendered (signals via router config / ssr entrypoints)
# Keep it light: just look for TanStack SSR-related keywords.
rg -n "createFileRoute|beforeLoad|loader|hydrate|SSR|server-side|renderToString" web/src -S || true

Repository: InstaZDLL/waveflow-server

Length of output: 20459


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="web/src/routes/_authed.profiles.tsx"
echo "---- Top of $FILE ----"
sed -n '1,120p' "$FILE"

Repository: InstaZDLL/waveflow-server

Length of output: 2584


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check loader usage for _authed.profiles route
rg -n "export const Route = createFileRoute\\('/_authed/profiles'\\)" -n web/src/routes/_authed.profiles.tsx
rg -n "loader:" web/src/routes/_authed.profiles.tsx

Repository: InstaZDLL/waveflow-server

Length of output: 178


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look for a shared date formatting helper (maybe already exists)
rg -n "format\\(date\\)|DateTimeFormat\\(|toLocaleDateString\\(" web/src -S || true

Repository: InstaZDLL/waveflow-server

Length of output: 308


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,120p' web/src/routes/__root.tsx

Repository: InstaZDLL/waveflow-server

Length of output: 3737


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,140p' web/src/routes/_authed.tsx

Repository: InstaZDLL/waveflow-server

Length of output: 1083


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look for SSR/React Start entrypoints to understand loader execution timing
rg -n "react-start|renderToString|hydrate|ssr|Nitro|createServerFn" web/src -S || true

Repository: InstaZDLL/waveflow-server

Length of output: 5134


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check if there's already a shared date formatting helper used elsewhere
rg -n "format\\(" web/src -S | rg -n "Date|date|Intl" || true

Repository: InstaZDLL/waveflow-server

Length of output: 193


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,220p' web/src/routes/_authed.settings.tsx

Repository: InstaZDLL/waveflow-server

Length of output: 5053


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,140p' web/src/routes/_authed.settings.test.tsx

Repository: InstaZDLL/waveflow-server

Length of output: 4446


SSR/client : rendu de date non déterministe

web/src/routes/_authed.profiles.tsx (ligne 58) utilise new Date(p.last_used_at).toLocaleDateString() sans locale ni fuseau explicites : côté SSR ça dépend de l’Intl/Timezone du serveur, côté client du navigateur, ce qui peut produire un jour différent (surtout autour de minuit). Formate la date de façon stable (locale + timeZone explicites) ou calcule la chaîne formatée dans le loader et rends-la telle quelle.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/routes/_authed.profiles.tsx` around lines 57 - 59, The component
renders p.last_used_at with new Date(...).toLocaleDateString() which yields
non-deterministic SSR vs client output; instead compute a stable formatted
string in the route loader (e.g. use Intl.DateTimeFormat with an explicit locale
and timeZone) and attach it to each profile as something like
last_used_at_formatted, then update the JSX in _authed.profiles.tsx to render
p.last_used_at_formatted instead of calling toLocaleDateString in the component;
ensure you reference the existing p.last_used_at when creating the formatted
value and use a consistent locale/timeZone in the formatter.

Comment on lines +1 to +103
// Client-side validation tests for the sign-up form. We don't hit
// Better Auth here — the form should refuse to submit when the
// inputs don't pass the locally enforced rules, so the network call
// never happens in those cases. The mocked `signUp.email` asserts
// that: it is never invoked when validation short-circuits, and a
// successful call triggers the post-submit navigate-home.
//
// We mock `@tanstack/react-router` rather than rendering inside the
// real router so the test stays a unit test on the form behavior.

import { describe, expect, it, vi, beforeEach } from 'vitest'
import { render, screen, fireEvent, waitFor } from '@testing-library/react'

const signUpEmail = vi.fn()
const navigate = vi.fn()

vi.mock('@/lib/auth-client', () => ({
authClient: {
signUp: {
email: (...args: unknown[]) => signUpEmail(...args),
},
},
}))

// `Route.useLoaderData` is consumed by the component since the
// OAuth providers flag landed — extend the mocked file route so it
// returns a stub instead of `undefined.useLoaderData`. Email-only
// keeps the OAuth section out of the rendered tree, which is what
// the existing assertions on labels + the single "Sign up" button
// expect.
vi.mock('@tanstack/react-router', () => ({
createFileRoute: () => (config: unknown) => ({
...(config as Record<string, unknown>),
useLoaderData: () => ({ email: true, google: false, apple: false }),
}),
useNavigate: () => navigate,
Link: ({ children, ...rest }: React.PropsWithChildren<Record<string, unknown>>) => (
<a {...rest}>{children}</a>
),
}))

const { SignUp } = await import('./sign-up')

beforeEach(() => {
signUpEmail.mockReset()
navigate.mockReset()
})

function fillForm({ name, email, password }: { name: string; email: string; password: string }) {
fireEvent.change(screen.getByLabelText(/display name/i), { target: { value: name } })
fireEvent.change(screen.getByLabelText(/email/i), { target: { value: email } })
fireEvent.change(screen.getByLabelText(/^password/i), { target: { value: password } })
}

describe('sign-up form', () => {
it('blocks submit when the password is too short', async () => {
render(<SignUp />)
fillForm({ name: 'Daisy', email: 'daisy@example.com', password: 'short' })
fireEvent.click(screen.getByRole('button', { name: /sign up/i }))

const alert = await screen.findByRole('alert')
expect(alert.textContent).toMatch(/at least 12 characters/i)
expect(signUpEmail).not.toHaveBeenCalled()
})

it('blocks submit when the email lacks an @', async () => {
render(<SignUp />)
fillForm({ name: 'Daisy', email: 'not-an-email', password: 'correct-horse-battery' })
fireEvent.click(screen.getByRole('button', { name: /sign up/i }))

const alert = await screen.findByRole('alert')
expect(alert.textContent).toMatch(/valid email/i)
expect(signUpEmail).not.toHaveBeenCalled()
})

it('surfaces a server error and stays on the form', async () => {
signUpEmail.mockResolvedValueOnce({
data: null,
error: { message: 'Email already in use' },
})
render(<SignUp />)
fillForm({ name: 'Daisy', email: 'daisy@example.com', password: 'correct-horse-battery' })
fireEvent.click(screen.getByRole('button', { name: /sign up/i }))

const alert = await screen.findByRole('alert')
expect(alert.textContent).toMatch(/email already in use/i)
expect(navigate).not.toHaveBeenCalled()
})

it('navigates home on a successful sign-up', async () => {
signUpEmail.mockResolvedValueOnce({ data: { user: { id: 'u_1' } }, error: null })
render(<SignUp />)
fillForm({ name: 'Daisy', email: 'daisy@example.com', password: 'correct-horse-battery' })
fireEvent.click(screen.getByRole('button', { name: /sign up/i }))

await waitFor(() => expect(navigate).toHaveBeenCalledWith({ to: '/' }))
expect(signUpEmail).toHaveBeenCalledWith({
email: 'daisy@example.com',
password: 'correct-horse-battery',
name: 'Daisy',
})
})
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Envisager des tests supplémentaires pour la couverture complète.

Les tests actuels couvrent bien les cas principaux, mais manquent :

  • Validation du nom vide (ligne 33 de sign-up.tsx)
  • Limite supérieure du mot de passe (MAX_PASSWORD)
  • Comportement du trim sur email/name avec espaces

Ces cas sont mineurs mais aideraient à prévenir les régressions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/routes/sign-up.test.tsx` around lines 1 - 103, Add unit tests to
sign-up.test.tsx that cover the missing edge cases: add a test asserting the
form rejects an empty display name (exercise the validation branch in SignUp for
the name field), a test for the maximum password length using the MAX_PASSWORD
limit (construct a too-long password and expect a validation alert and no call
to signUpEmail), and tests verifying trimming behavior by submitting name and
email with surrounding whitespace (expect the values passed to signUpEmail to be
trimmed and navigation on success); use the existing fillForm helper, the mocked
signUpEmail and navigate, and the SignUp render to keep consistency with the
current suite.

Comment on lines +21 to +24
const session = await auth.api.getSession({
headers: new Headers(headers as HeadersInit),
})
if (!session?.user) return null

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Gérer explicitement le cas non authentifié sur getSession.

Line 21 appelle auth.api.getSession sans gérer APIError('UNAUTHORIZED'). Sur un visiteur non connecté, ce server-fn peut remonter une erreur au lieu de renvoyer null, ce qui casse le flux d’auth côté route.

Correctif proposé
 import { createServerFn } from '`@tanstack/react-start`'
 import { getRequestHeaders } from '`@tanstack/react-start/server`'
+import { APIError } from 'better-auth/api'
 import { auth } from '`@/lib/auth`'
@@
 export const getCurrentSession = createServerFn({ method: 'GET' }).handler(
   async (): Promise<SessionSummary | null> => {
     const headers = getRequestHeaders()
-    const session = await auth.api.getSession({
-      headers: new Headers(headers as HeadersInit),
-    })
+    let session
+    try {
+      session = await auth.api.getSession({
+        headers: new Headers(headers as HeadersInit),
+      })
+    } catch (err) {
+      if (err instanceof APIError && err.status === 'UNAUTHORIZED') {
+        return null
+      }
+      throw err
+    }
     if (!session?.user) return null
     return {
       id: session.user.id,
       email: session.user.email,
       name: session.user.name,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/server-fns/session.ts` around lines 21 - 24, Wrap the call to
auth.api.getSession in a try/catch inside the server function in session.ts
(where session is obtained) and explicitly handle the unauthenticated case: if
the caught error is an APIError with code/message indicating 'UNAUTHORIZED' (or
similar) return null, otherwise rethrow the error; keep the existing
early-return when session?.user is absent. Ensure you reference the
auth.api.getSession invocation and the session variable in your change.

Comment thread web/src/styles.css
Comment on lines +5 to +7
@theme {
--font-sans: 'Manrope', ui-sans-serif, system-ui, sans-serif;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Vérification read-only du contrat Tailwind v4 + design tokens
rg -n "`@theme`( inline)?|--(accent|surface)|oklch" web/src/styles.css
rg -n "oklch|accent|surface" web/packages/design-tokens/src

Repository: InstaZDLL/waveflow-server

Length of output: 14969


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant top portion of the CSS (including `@theme` and palette declarations)
sed -n '1,120p' web/src/styles.css

# Check whether the file references the design-token variables that the guideline expects
rg -n "var\(--(accent|color-surface|color-surface-|surface)([-\w]*)?\)" web/src/styles.css || true
rg -n "--(accent|color-surface|color-surface-dark|color-surface-light|surface(-strong)?)\b" web/src/styles.css || true

# Check whether `@theme` inline exists anywhere in the file
rg -n "`@theme`\s*\{\s*|`@theme`\s+inline|`@theme`\(inline\)" web/src/styles.css || true

# Show where surface-strong is defined/used
rg -n "surface-strong|--surface-strong" web/src/styles.css -n -C 2 || true

Repository: InstaZDLL/waveflow-server

Length of output: 5318


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) présence exacte de `@theme` inline
rg -n "`@theme`\s*(\(|\{)|`@theme`\s+inline|`@theme`\s*\\(\\s*inline\\s*\\)" web/src/styles.css || true

# 2) présence de variables accent/ surface attendues par le contrat design-tokens
rg -n -- '--accent-' web/src/styles.css || true
rg -n -- '--color-surface-' web/src/styles.css || true

# 3) présence de valeurs OKLCH (pas juste oklab dans color-mix)
rg -n "oklch\(" web/src/styles.css || true

# 4) état actuel: présence de --surface / --surface-strong en rgba/hex
rg -n "--surface(-strong)?\s*:\s*(#|rgba?\()" web/src/styles.css || true

Repository: InstaZDLL/waveflow-server

Length of output: 538


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Montrer tous les usages de `@theme` dans le fichier (pour trancher sur "inline")
rg -n "`@theme`" web/src/styles.css || true

# 2) Rechercher explicitement les variables attendues par les tokens (si remappage Tailwind v4)
rg -n -e "--accent-" web/src/styles.css || true
rg -n -e "--color-surface-" web/src/styles.css || true

# 3) Vérifier si le CSS utilise directement des valeurs OKLCH (pas seulement oklab dans color-mix)
rg -n "oklch\(" web/src/styles.css || true

# 4) Vérifier que les surfaces sont bien en rgba/hex localement (pour appuyer le diagnostic de non-remapping)
rg -n -e "--surface(-strong)?\s*:\s*(#|rgba?\()" web/src/styles.css || true

Repository: InstaZDLL/waveflow-server

Length of output: 348


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# présence de `@theme` inline
rg -n "`@theme`\s*inline|`@theme`\s*\(\s*inline" web/src/styles.css || true

# présence de valeurs OKLCH dans ce fichier
rg -n "oklch\(" web/src/styles.css || true

# présence de variables accent attendues par le contrat tokens (si remappage OKLCH)
rg -n -e "--accent-" web/src/styles.css || true

Repository: InstaZDLL/waveflow-server

Length of output: 51


Aligne le thème Tailwind sur les design tokens OKLCH (web)

  • Dans web/src/styles.css, le bloc @theme n’est pas en mode inline (seulement @theme { ... } autour de la ligne 5).
  • Les couleurs accent/surface ne sont pas remappées depuis web/packages/design-tokens : --surface / --surface-strong sont redéfinies localement en rgba(...) (dans :root), et le fichier ne contient ni --accent-* ni oklch(...).

[scratchpad_end]

🧰 Tools
🪛 Biome (2.4.16)

[error] 5-7: Tailwind-specific syntax is disabled.

(parse)

🪛 Stylelint (17.12.0)

[error] 5-5: Unexpected unknown at-rule "@theme" (scss/at-rule-no-unknown)

(scss/at-rule-no-unknown)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/styles.css` around lines 5 - 7, Le bloc `@theme` dans
web/src/styles.css doit passer en mode inline et consommer les tokens OKLCH du
package design-tokens : remplacez la déclaration `@theme` { ... } par la syntaxe
inline (par ex. `@theme` "--name" { ... } ou l'équivalent attendu par votre build)
et retirez les redéfinitions locales en rgba dans :root pour --surface /
--surface-strong ; au lieu de cela importez ou remappez les variables depuis
web/packages/design-tokens (ajoutez les variables --accent-* et utilisez
oklch(...) pour les couleurs de surface et accent) en conservant --font-sans tel
quel pour éviter de toucher à la pile de polices.

Source: Coding guidelines

Comment thread web/vitest.config.ts
test: {
environment: 'jsdom',
globals: true,
passWithNoTests: true,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Vérifie que l'option est activée
rg -n "passWithNoTests" web/vitest.config.ts

# 2) Vérifie qu'il existe déjà des tests dans les patterns configurés
fd -HI -e ts -e tsx ".test" web/src web/packages | sed -n '1,20p'
fd -HI -e ts -e tsx ".test" web/src web/packages | wc -l

Repository: InstaZDLL/waveflow-server

Length of output: 1240


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Lire la config Vitest pertinente
sed -n '1,120p' web/vitest.config.ts

# Chercher include/exclude explicites
rg -n "include|exclude|test|passWithNoTests" web/vitest.config.ts

Repository: InstaZDLL/waveflow-server

Length of output: 2620


Désactive passWithNoTests dans web/vitest.config.ts
passWithNoTests: true peut laisser Vitest passer en CI avec 0 tests si la découverte échoue (renommage/mauvais glob/changement de structure). Des suites existent déjà et correspondent aux include, donc le flag n’apporte plus de bénéfice.

Correctif proposé
-    passWithNoTests: true,
+    passWithNoTests: false,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
passWithNoTests: true,
passWithNoTests: false,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/vitest.config.ts` at line 30, The Vitest config currently sets
passWithNoTests: true which can mask test discovery failures; update the
configuration in web/vitest.config.ts to disable this by removing the setting or
changing passWithNoTests to false (keep the existing include patterns intact) so
CI fails when no tests are found.

@InstaZDLL
InstaZDLL merged commit 2ed9448 into main Jun 11, 2026
6 of 9 checks passed
@InstaZDLL
InstaZDLL deleted the feat/monorepo-merge branch June 11, 2026 22:56
@github-actions github-actions Bot added ignore-for-release Excluded from release notes scope: deps Dependencies scope: web Embedded web player (React) scope: auth Authentication, tokens, sessions scope: routes Web routes scope: components Web UI components scope: design-tokens Design tokens / theming scope: tooling Build / dev tooling scope: docs Docs, README, assets size: xl > 500 lines and removed ignore-for-release Excluded from release notes labels Jun 11, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Jun 12, 2026
InstaZDLL added a commit that referenced this pull request Jun 15, 2026
P2 batch from CodeRabbit on PR #41 / issue #43. 4 of the 5 P2
items applied; the 5th (styles.css OKLCH unification) is
deferred — see § below.

## Changes

- `web/src/components/PlayerBar.tsx` — `onPointerCancel` now
  commits the pending `seekScrub` instead of dropping it. The
  user already expressed intent by dragging; a system-initiated
  cancel (notification focus steal, gesture-to-scroll promotion)
  shouldn't lose that input.
- `web/src/lib/use-focus-trap.ts` — when `items.length === 0`,
  stamp a transient `tabindex="-1"` on the container before
  `.focus()` so `HTMLElement.focus()` actually runs (it silently
  no-ops on a non-tabbable element, which would let Tab fall
  through to the page underneath). Restore the prior attribute
  state after focus so the DOM contract stays intact for
  subsequent renders.
- `web/src/routes/_authed.profiles.tsx` — pre-format
  `last_used_at` in the loader via
  `Intl.DateTimeFormat('en-US', { timeZone: 'UTC' })`. Drops the
  SSR ↔ client divergence that `new Date(...).toLocaleDateString()`
  produced (Node defaults to system locale + TZ, browser uses the
  user's — React then logs a hydration mismatch and briefly
  flickers the wrong format). The loader also now logs raw err
  + surfaces a generic message, matching the artists / playlists
  loader fix from the P1 batch.
- `web/src/components/Footer.tsx` — display string was
  "AGPL-3.0", the licence file is "AGPL-3.0-only". Mirror the
  SPDX identifier.

## Deferred — styles.css OKLCH unification

The CodeRabbit finding asks the `@theme` block + `:root` fallback
in `styles.css` to consume the OKLCH tokens from
`@waveflow/design-tokens` instead of redefining `--surface`,
`--surface-strong`, `--line`, etc. in `rgba(...)` locally.

In practice this is a real refactor, not a one-liner:

- `design-tokens` currently emits the four base surface HEX
  values (`--color-surface-{dark,light}{,-elevated}`). The
  translucent overlay variables (`--surface`, `--line`, `--chip-bg`,
  …) are NOT in the design-tokens public surface today.
- Pulling them in needs one of:
  - Extend `design-tokens` to emit translucent overlays per
    preset (Sprint-level work; touches every preset definition).
  - Or rewrite `styles.css` to derive them via
    `oklch(from var(--color-surface-light) l c h / 0.72)` —
    works in Chromium 125+, Firefox 128+, Safari 17.2+; OK
    against the WaveFlow web's target matrix but worth a
    deliberate design decision.

Logging this for a follow-up PR rather than slipping a
half-finished refactor through the P2 batch. The other 4 P2
items ship now.

## Validation

- `bun run typecheck` clean.
- `bun run lint` clean.
- `bun run test` — 233 pass across 26 files.
- `prettier --check` on the 4 modified files clean.

## Refs

Closes 4/24 items of #43 (11/24 cumulative with P1 batch in #60).
P3+P4 ride in a separate PR.

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
InstaZDLL added a commit that referenced this pull request Jun 15, 2026
7 findings from CodeRabbit on PR #41 / issue #43 — the P3 tooling
batch (6 items) + the single P4 labeler item, bundled because
they all touch dev / CI infrastructure rather than runtime code.

## Changes

- `web/scripts/db-migrate.ts` — resolve `MIGRATIONS_DIR` relative
  to the script via `fileURLToPath(import.meta.url)` instead of
  `process.cwd()`. After the monorepo merge the script can be
  invoked from the repo root (`bun --cwd=web run db:migrate`);
  the previous CWD anchor broke silently because it pointed at
  `<repo>/db/migrations`, which doesn't exist.
- `web/vitest.config.ts` — `passWithNoTests: false`. The flag
  was a bootstrap-era convenience while the suite was empty; now
  that 230+ tests are landing, a typo in the `include` glob (or
  an accidental file move that breaks discovery) would silently
  pass CI under `true`. Failing loud is the right signal.
- `web/src/routes/sign-up.test.tsx` — three new cases:
  - empty / whitespace-only display name → blocked
  - password at MAX_PASSWORD + 1 (129 chars) → blocked
  - surrounding whitespace on name + email → trimmed before
    `signUp.email` call
- Six route-adjacent test files (`sign-up.test.tsx`,
  `_authed.profiles.$profileId.libraries.$libraryId.albums*`,
  `…artists*`, `…playlists.test.tsx`,
  `…playlists.$playlistId.test.tsx`) — switch
  `React.PropsWithChildren` (which referenced the global
  `React` namespace without an `import * as React`) to a typed
  `PropsWithChildren` imported via `import type { PropsWithChildren }
  from 'react'`. The tests passed before only because the JSX
  transform happens to expose `React` ambient; the explicit
  import locks the contract and is what every other test file
  in the suite already does.
- `web/src/routes/sign-in.test.ts` → `-sign-in.test.ts` —
  TanStack Router's `-`-prefix convention excludes the file
  from the auto-generated route tree. Without the rename the
  router plugin had been incidentally treating
  `sign-in.test.ts` as a candidate route module on every dev
  reload. Kept `.ts` (not `.tsx`) because the test doesn't
  render JSX (`Link: () => null` mock).
- `.github/pull_request_template.md` — first heading `## Summary`
  → `# Summary`. MD041 (first-line-heading) wants H1; the
  template was the only file in the repo tripping it.
- `.github/labeler.yml` — `web/src/routes/sign-in/**` /
  `…sign-up/**` only matched DIRECTORIES; the actual routes are
  `sign-in.tsx` / `sign-up.tsx` files at the root of
  `routes/`. Switch to `sign-in{,/**}` / `sign-up{,/**}` so a
  PR touching either form lights up the auth label.

## Validation

- `bun run typecheck` clean.
- `bun run lint` clean.
- `bun run test` — 236 pass across 26 files (was 233, +3 new
  sign-up boundary cases).
- `prettier --check` on the modified files clean.

## Refs

Closes 7/24 items of #43. Cumulative: 18/24 across the three
batched PRs (#60 P1 + #61 P2 + this).

The 4 P0 items + 1 deferred P2 (styles.css OKLCH unification)
remain. P0 was tagged in the issue as "ship with the archive
PR" — verifying status as a follow-up.

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
InstaZDLL added a commit that referenced this pull request Jun 15, 2026
7 findings from CodeRabbit on PR #41 / issue #43 — the P3 tooling
batch (6 items) + the single P4 labeler item, bundled because
they all touch dev / CI infrastructure rather than runtime code.

## Changes

- `web/scripts/db-migrate.ts` — resolve `MIGRATIONS_DIR` relative
  to the script via `fileURLToPath(import.meta.url)` instead of
  `process.cwd()`. After the monorepo merge the script can be
  invoked from the repo root (`bun --cwd=web run db:migrate`);
  the previous CWD anchor broke silently because it pointed at
  `<repo>/db/migrations`, which doesn't exist.
- `web/vitest.config.ts` — `passWithNoTests: false`. The flag
  was a bootstrap-era convenience while the suite was empty; now
  that 230+ tests are landing, a typo in the `include` glob (or
  an accidental file move that breaks discovery) would silently
  pass CI under `true`. Failing loud is the right signal.
- `web/src/routes/sign-up.test.tsx` — three new cases:
  - empty / whitespace-only display name → blocked
  - password at MAX_PASSWORD + 1 (129 chars) → blocked
  - surrounding whitespace on name + email → trimmed before
    `signUp.email` call
- Six route-adjacent test files (`sign-up.test.tsx`,
  `_authed.profiles.$profileId.libraries.$libraryId.albums*`,
  `…artists*`, `…playlists.test.tsx`,
  `…playlists.$playlistId.test.tsx`) — switch
  `React.PropsWithChildren` (which referenced the global
  `React` namespace without an `import * as React`) to a typed
  `PropsWithChildren` imported via `import type { PropsWithChildren }
  from 'react'`. The tests passed before only because the JSX
  transform happens to expose `React` ambient; the explicit
  import locks the contract and is what every other test file
  in the suite already does.
- `web/src/routes/sign-in.test.ts` → `-sign-in.test.ts` —
  TanStack Router's `-`-prefix convention excludes the file
  from the auto-generated route tree. Without the rename the
  router plugin had been incidentally treating
  `sign-in.test.ts` as a candidate route module on every dev
  reload. Kept `.ts` (not `.tsx`) because the test doesn't
  render JSX (`Link: () => null` mock).
- `.github/pull_request_template.md` — first heading `## Summary`
  → `# Summary`. MD041 (first-line-heading) wants H1; the
  template was the only file in the repo tripping it.
- `.github/labeler.yml` — `web/src/routes/sign-in/**` /
  `…sign-up/**` only matched DIRECTORIES; the actual routes are
  `sign-in.tsx` / `sign-up.tsx` files at the root of
  `routes/`. Switch to `sign-in{,/**}` / `sign-up{,/**}` so a
  PR touching either form lights up the auth label.

## Validation

- `bun run typecheck` clean.
- `bun run lint` clean.
- `bun run test` — 236 pass across 26 files (was 233, +3 new
  sign-up boundary cases).
- `prettier --check` on the modified files clean.

## Refs

Closes 7/24 items of #43. Cumulative: 18/24 across the three
batched PRs (#60 P1 + #61 P2 + this).

The 4 P0 items + 1 deferred P2 (styles.css OKLCH unification)
remain. P0 was tagged in the issue as "ship with the archive
PR" — verifying status as a follow-up.

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: auth Authentication, tokens, sessions scope: components Web UI components scope: deps Dependencies scope: design-tokens Design tokens / theming scope: docs Docs, README, assets scope: routes Web routes scope: tooling Build / dev tooling scope: web Embedded web player (React) size: xl > 500 lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants