chore: consolidate waveflow-web into the monorepo under web/ - #41
Conversation
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>
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughPR 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. ChangesMonorepo web et fonctionnalités front
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
Estimated code review effort🎯 5 (Critical) | ⏱️ ~90+ minutes
✨ Finishing Touches🧪 Generate unit tests (beta)
|
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>
There was a problem hiding this comment.
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 winNom de fichier de test non conforme, avec risque d’inclusion dans l’arbre de routes.
Ce fichier est en
.test.tssoussrc/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 insrc/routes/must end in.test.tsxto 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
⛔ Files ignored due to path filters (4)
web/bun.lockis excluded by!**/*.lockweb/public/favicon.icois excluded by!**/*.icoweb/public/logo192.pngis excluded by!**/*.pngweb/public/logo512.pngis 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.ymlCLAUDE.mdREADME.mdweb/.coderabbit.yamlweb/.commitlintrc.cjsweb/.env.exampleweb/.gitignoreweb/.husky/commit-msgweb/.prettierignoreweb/.prettierrc.jsonweb/.vscode/settings.jsonweb/CLAUDE.mdweb/CONTRIBUTING.mdweb/LICENSEweb/README.mdweb/db/README.mdweb/db/migrations/0001_better_auth_initial.sqlweb/db/migrations/0002_jwt_plugin.sqlweb/eslint.config.jsweb/package.jsonweb/packages/design-tokens/package.jsonweb/packages/design-tokens/src/apply.test.tsweb/packages/design-tokens/src/apply.tsweb/packages/design-tokens/src/index.tsweb/packages/design-tokens/src/palettes.tsweb/packages/design-tokens/src/themes.test.tsweb/packages/design-tokens/src/themes.tsweb/packages/design-tokens/tsconfig.jsonweb/public/manifest.jsonweb/public/robots.txtweb/scripts/db-migrate.tsweb/src/components/DeletePlaylistDialog.test.tsxweb/src/components/DeletePlaylistDialog.tsxweb/src/components/Footer.tsxweb/src/components/Header.tsxweb/src/components/NowPlayingOverlay.test.tsxweb/src/components/NowPlayingOverlay.tsxweb/src/components/OAuthButtons.tsxweb/src/components/PlayableTrackList.test.tsxweb/src/components/PlayableTrackList.tsxweb/src/components/PlayerBar.tsxweb/src/components/PlaylistFormDialog.test.tsxweb/src/components/PlaylistFormDialog.tsxweb/src/components/QueuePanel.test.tsxweb/src/components/QueuePanel.tsxweb/src/components/ThemePicker.test.tsxweb/src/components/ThemePicker.tsxweb/src/components/ThemeProvider.test.tsxweb/src/components/ThemeProvider.tsxweb/src/components/ThemeStyle.tsxweb/src/components/ThemeToggle.tsxweb/src/components/TrackFilterBar.test.tsxweb/src/components/TrackFilterBar.tsxweb/src/components/WaveflowLogo.tsxweb/src/lib/auth-client.tsweb/src/lib/auth.tsweb/src/lib/db.tsweb/src/lib/format-time.tsweb/src/lib/player-context.test.tsxweb/src/lib/player-context.tsxweb/src/lib/server/waveflow-server.test.tsweb/src/lib/server/waveflow-server.tsweb/src/lib/share-format.test.tsweb/src/lib/share-format.tsweb/src/lib/use-focus-trap.test.tsxweb/src/lib/use-focus-trap.tsweb/src/router.tsxweb/src/routes/__root.tsxweb/src/routes/_authed.profiles.$profileId.libraries.$libraryId.albums.$albumId.test.tsxweb/src/routes/_authed.profiles.$profileId.libraries.$libraryId.albums.$albumId.tsxweb/src/routes/_authed.profiles.$profileId.libraries.$libraryId.albums.test.tsxweb/src/routes/_authed.profiles.$profileId.libraries.$libraryId.albums.tsxweb/src/routes/_authed.profiles.$profileId.libraries.$libraryId.artists.$artistId.test.tsxweb/src/routes/_authed.profiles.$profileId.libraries.$libraryId.artists.$artistId.tsxweb/src/routes/_authed.profiles.$profileId.libraries.$libraryId.artists.test.tsxweb/src/routes/_authed.profiles.$profileId.libraries.$libraryId.artists.tsxweb/src/routes/_authed.profiles.$profileId.libraries.$libraryId.tsxweb/src/routes/_authed.profiles.$profileId.playlists.$playlistId.test.tsxweb/src/routes/_authed.profiles.$profileId.playlists.$playlistId.tsxweb/src/routes/_authed.profiles.$profileId.playlists.test.tsxweb/src/routes/_authed.profiles.$profileId.playlists.tsxweb/src/routes/_authed.profiles.$profileId.tsxweb/src/routes/_authed.profiles.tsxweb/src/routes/_authed.settings.test.tsxweb/src/routes/_authed.settings.tsxweb/src/routes/_authed.tsxweb/src/routes/about.tsxweb/src/routes/api/auth/$.tsweb/src/routes/desktop-login.tsxweb/src/routes/index.tsxweb/src/routes/p.$token.tsxweb/src/routes/sign-in.test.tsweb/src/routes/sign-in.tsxweb/src/routes/sign-up.test.tsxweb/src/routes/sign-up.tsxweb/src/server-fns/_internal.tsweb/src/server-fns/albums.tsweb/src/server-fns/artists.tsweb/src/server-fns/desktop-login.test.tsweb/src/server-fns/desktop-login.tsweb/src/server-fns/libraries.tsweb/src/server-fns/playlists.tsweb/src/server-fns/profiles.tsweb/src/server-fns/providers.test.tsweb/src/server-fns/providers.tsweb/src/server-fns/session.tsweb/src/server-fns/share.test.tsweb/src/server-fns/share.tsweb/src/server-fns/stream.tsweb/src/server-fns/theme.tsweb/src/server-fns/tracks.tsweb/src/styles.cssweb/tsconfig.jsonweb/vite.config.tsweb/vitest.config.ts
| 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. |
There was a problem hiding this comment.
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.
| 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.
| - 'web/src/routes/sign-in/**' | ||
| - 'web/src/routes/sign-up/**' |
There was a problem hiding this comment.
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.
| - '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 | |||
There was a problem hiding this comment.
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.
| ## 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
| - 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 |
There was a problem hiding this comment.
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
| on: | ||
| pull_request_target: | ||
| types: [opened, reopened, synchronize, edited] |
There was a problem hiding this comment.
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
| <p className="mt-1 text-xs text-[var(--sea-ink-soft)]"> | ||
| Last used {new Date(p.last_used_at).toLocaleDateString()} | ||
| </p> |
There was a problem hiding this comment.
🧩 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 || trueRepository: 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.tsRepository: 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 || trueRepository: 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 || trueRepository: 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.tsxRepository: 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 || trueRepository: InstaZDLL/waveflow-server
Length of output: 308
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,120p' web/src/routes/__root.tsxRepository: InstaZDLL/waveflow-server
Length of output: 3737
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,140p' web/src/routes/_authed.tsxRepository: 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 || trueRepository: 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" || trueRepository: InstaZDLL/waveflow-server
Length of output: 193
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,220p' web/src/routes/_authed.settings.tsxRepository: InstaZDLL/waveflow-server
Length of output: 5053
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,140p' web/src/routes/_authed.settings.test.tsxRepository: 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.
| // 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', | ||
| }) | ||
| }) | ||
| }) |
There was a problem hiding this comment.
🧹 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.
| const session = await auth.api.getSession({ | ||
| headers: new Headers(headers as HeadersInit), | ||
| }) | ||
| if (!session?.user) return null |
There was a problem hiding this comment.
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.
| @theme { | ||
| --font-sans: 'Manrope', ui-sans-serif, system-ui, sans-serif; | ||
| } |
There was a problem hiding this comment.
🛠️ 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/srcRepository: 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 || trueRepository: 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 || trueRepository: 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 || trueRepository: 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 || trueRepository: 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@themen’est pas en modeinline(seulement@theme { ... }autour de la ligne 5). - Les couleurs
accent/surfacene sont pas remappées depuisweb/packages/design-tokens:--surface/--surface-strongsont redéfinies localement enrgba(...)(dans:root), et le fichier ne contient ni--accent-*nioklch(...).
[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
| test: { | ||
| environment: 'jsdom', | ||
| globals: true, | ||
| passWithNoTests: true, |
There was a problem hiding this comment.
🧩 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 -lRepository: 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.tsRepository: 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.
| 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.
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>
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>
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>
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:
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@mainunderweb/with full history preserved (commitAdd 'web/' from commit 'a85cad0...'). The Rust crate root is untouched. Cargo never recurses intoweb/because there's noCargo.tomlthere.CI re-split
.github/workflows/ci.yml(rust)ci-rust.yml, path-filtered to Cargo / src / migrations / testsweb/.github/workflows/ci.yml(TS)ci-web.yml, gated bypaths: ['web/**']andworking-directory: web.github/workflows/codeql.yml.github/workflows/dco.ymlweb/.github/workflows/dco.ymlweb/.github/workflows/label-pr.ymlDependabot
One file at
.github/dependabot.yml: cargo at/, npm at/web, github-actions once. Thewaveflow-coreignore and the kysely 0.29 hold-back are both carried over.Labels
Top-level
scope: server/scope: webto 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.mdrewritten to describe the monorepo + repo layout.CLAUDE.mdnow points toweb/CLAUDE.mdfor half-specific guidance.web/CLAUDE.mdadded — covers TanStack server-fns, Better Auth plumbing, design-tokens package.Out of scope
release-please-config.json+.release-please-manifest.jsonin a follow-up when the first version is cut.waveflow-webrepo — depends on this PR landing. Follow-up: archive, README redirects here.Test plan
cargo check --all-targets --all-featurespasses locally.cargo clippy --all-targets --all-features -- -D warningsclean.git log --follow web/package.jsonreaches the original web repo commits).ci-rustskips (no Rust paths changed),ci-webruns (touchesweb/**),codeqlskips,dcoruns.Companion changes
The dev-stack README at
InstaZDLL/waveflow-dev-stackis updated locally to point at..\waveflow-server\web\.envinstead of..\waveflow-web\.env. Push that as a separate PR after this lands.Summary by CodeRabbit
Nouvelles fonctionnalités
Documentation