LOADING…
0%
Build history

Every step,
from the first commit.

A complete build timeline of Grainstash taking shape—from early sequencer experiments to the product it is today.

Commits
1,543
Since
2024
Through
2026

Showing 151–200 of 1,543

Page 4 of 31

9 commits
12 commits
  1. Park updated CI workflow in ci/ (push lacks workflow scope)

    Claude bddf70c6

    Commit notes

    The branch's earlier commit updated .github/workflows/ci.yml (frontend gate, audit step, timeouts, main-only push triggers), but pushes from this automation cannot touch workflow files. This restores the active workflow byte-for-byte and stages the updated one in ci/ci.yml with apply instructions in ci/README.md — the same staging pattern used when CI was first added here. [private session redacted]

    Files
    3
    Added
    +165
    Removed
    −35
  2. Add integration suites: workspace authz, share-link lifecycle, uploads

    Claude c6dd8699

    Commit notes

    The three highest-risk untested route surfaces from the June audit now have DB/Redis-backed coverage using a shared route harness that mirrors the global error mapping: - workspaces: full owner/member/non-member authz matrix across list, update, delete, and member removal (incl. memberCount semantics). - share-links: create -> public info/access lifecycle, creation IDOR guard, password protection, expiry, owner-only deletion, the activity-IDOR regression from e1a6141, and free-plan limits (3 links/month, password = Pro). - uploads: direct-upload initiation (Redis session + locally-signed presigned URL + user-scoped storage key), non-audio rejection, quota 403, session list/delete cleanup, and the legacy chunked redirect parity. The storage-dependent complete path is documented as needing MinIO. All suites skip cleanly without infra and run for real in CI. [private session redacted]

    Files
    4
    Added
    +720
    Removed
    −0
  3. Fix all 23 svelte-check warnings; enforce zero-warnings in check

    Claude 7f5e1149

    Commit notes

    - ShareActivityFeed/ShareComments captured props once at init (state_referenced_locally) — real staleness bugs: switching the selected share link/token after mount kept querying the old one. The two query hooks now take getters (the convention createAllShareActivityQuery already used) so the prop is read reactively. - WorkspaceSwitcher had a <button> nested in a <button> (invalid HTML, SSR hydration mismatch); the manage affordance is now a span with button role, keyboard handling, and an aria-label. - PackOrganizer's four autofocus attributes replaced with a use:focusOnMount action (focus only when the user opens the inline edit, not at document load). - Label/control associations via for/id (ShareDialog, SharePermissions, UploadLinkForm); heading-only labels demoted to spans; color swatch buttons got aria-labels; the placement-modal backdrop uses the target===currentTarget pattern instead of an interactive inner div. - svelte-check now runs with --fail-on-warnings so new warnings block. [private session redacted]

    Files
    12
    Added
    +91
    Removed
    −48
  4. Bootstrap frontend unit tests with bun:test; cover token-refresh logic

    Claude 7dd4503d

    Commit notes

    Ten behavioral tests for eden.ts using Bun's built-in runner (no new dependencies): storage round-trip, single-flight refresh dedup (two concurrent refreshes -> one request), token preservation on network errors vs clearing + auth:expired on definitive rejection, SSE ticket preference with legacy-token fallback, and authFetch header injection. Adds the frontend "test" script that the CI frontend job runs. [private session redacted]

    Files
    2
    Added
    +165
    Removed
    −0
  5. CI: gate the frontend; add audit step, job timeouts; ADRs for deferred decisions

    Claude cc274020

    Commit notes

    - New required frontend job: svelte-check + unit tests + production build. Vite builds don't typecheck, so type breakage previously merged green and surfaced only on the Netlify deploy. - bun audit runs report-only in the test job; promote to blocking after the residual moderate/low findings are triaged. - timeout-minutes on every job (hung DB tests previously burned the 360-minute default); push triggers scoped to main (PRs already cover branches, halving duplicate runs). - ADR 0001 records the refresh-token storage decision (localStorage + CSP over httpOnly cookie, with revisit triggers); ADR 0002 records the single-OWNER_EMAIL admin model. RAILWAY.md now explains what the sync-lock script is for and when not to use it. [private session redacted]

    Files
    4
    Added
    +131
    Removed
    −1
  6. Cache public creator storefront reads in Redis (60s TTL + invalidation)

    Claude d3a3a005

    Commit notes

    Profile, storefront config, and published-pack listings are the hot unauthenticated reads; they now serve from a short-TTL Redis cache. Only successful responses are cached (404s propagate uncached), cache failures degrade to Postgres, and mutations (profile update, storefront edit, pack update/publish) invalidate the creator's keys. The 60s TTL bounds staleness for any mutation path that forgets to invalidate. [private session redacted]

    Files
    5
    Added
    +129
    Removed
    −7
  7. SSE auth via short-lived single-use tickets; ship report-only CSP

    Claude d8b876d7

    Commit notes

    - POST /events/ticket (Bearer-authenticated) mints a 30s single-use ticket in Redis; both SSE endpoints accept ?ticket= and consume it atomically with GETDEL. The legacy ?token= param still works for older clients, but the frontend now prefers tickets, so bearer tokens stop appearing in URLs, access logs, and browser history. - Remove SSE token-prefix console.logs and the per-request proxy console.log. - netlify.toml: add Content-Security-Policy-Report-Only (XSS is the account-takeover vector while tokens live in localStorage) and fix the [dev] block port to 4000. - Integration test covers ticket auth gating, TTL, and single-use consumption. [private session redacted]

    Files
    7
    Added
    +170
    Removed
    −44
  8. Make DB pool size configurable; warn when queue concurrency outruns it

    Claude c71b535d

    Commit notes

    - DB_POOL_MAX env var (default 20) controls the postgres-js pool; read directly from process.env so scripts can import db with partial env. - Worker logs a boot-time warning when the combined queue concurrency exceeds the pool size instead of silently queueing queries under load. - Fix env.example DATABASE_URL user to match docker-compose (beatstep, not the stale samplestep), and document DB_POOL_MAX. [private session redacted]

    Files
    4
    Added
    +35
    Removed
    −2
  9. Harden client token refresh: single-flight, no logout on network blips

    Claude 6af2815e

    Commit notes

    - All refresh paths (missing-token, 401 retry, SSE ensureValidToken) now share one in-flight refresh promise instead of the 401 path bypassing the dedup — concurrent 401s no longer surface unretried or race redundant token rotations. - Tokens are only cleared when the refresh endpoint definitively rejects the refresh token; transient network errors keep the session, and the next request retries. - On definitive rejection the client dispatches auth:expired; the auth store listens and clears the signed-in state instead of the app silently 401ing forever. [private session redacted]

    Files
    2
    Added
    +55
    Removed
    −66
  10. Return 429 for sharing/upload-link rate limits instead of 500

    Claude 2ef9ef23

    Commit notes

    The sliding-window helpers in share-links and upload-links threw plain Error on breach, which the global handler maps to 500 with the message suppressed in production. They now throw RateLimitError with the limiter's resetIn, so clients get 429 + Retry-After like every other rate-limited path. [private session redacted]

    Files
    2
    Added
    +4
    Removed
    −2
  11. Fix queue retry backoff, lock-owner guards, and hard job timeouts

    Claude e3fef13b

    Commit notes

    - FAIL_JOB now schedules retries on the delayed ZSET with the computed exponential backoff instead of re-queueing immediately (the Lua script previously discarded retryDelay — both branches were identical LPUSHes). The existing delayed-job promoter moves them back once due. - COMPLETE_JOB and FAIL_JOB verify lock ownership (mirroring EXTEND_LOCK) before mutating job state, so a stalled worker that wakes after its lock was reclaimed can no longer clobber the new owner's lock or job status. - Job handlers are raced against the abort signal: a handler that ignores the signal no longer holds its concurrency slot and heartbeat-extended lock forever — the job is failed at jobTimeout (owner-guarded). Abort reasons distinguish timeout/lock-lost from shutdown so graceful drain still lets in-flight jobs finish. - Add queue.integration.test.ts covering all three behaviors against real Redis (skips cleanly when unavailable). [private session redacted]

    Files
    2
    Added
    +260
    Removed
    −20
  12. Add post-remediation technical audit (June 2026, cycle 2)

    Claude 8c59a291

    Commit notes

    Verifies the prior audit's remediation wave commit-by-commit, inventories what remains open (frontend CI/tests, Stripe SDK, integration suites), and documents new verified findings: queue retry backoff no-op, completion without lock-owner check, advisory-only job timeouts, frontend refresh-flow defects, and localStorage tokens without CSP. Includes prioritized milestones, quick wins, and implementation sketches. Analysis only — no code changed. [private session redacted]

    Files
    1
    Added
    +460
    Removed
    −0
16 commits
  1. Add CI workflow for backend testing, linting, and type checking

    Armin Naimi b1160548

    Commit notes

    Introduce a new GitHub Actions workflow in `.github/workflows/ci.yml` to automate backend testing with Postgres and Redis services, along with linting and TypeScript type checking. This setup ensures that the backend code is validated through a series of defined jobs, enhancing the CI process and maintaining code quality.

    Files
    3
    Added
    +102
    Removed
    −139
  2. Close password-reset timing side channel; fix stale doc references

    Claude 2e595f64

    Commit notes

    Defer all account-dependent work (user lookup, token issuance, email) in /forgot-password until after the uniform response is returned, so response timing no longer reveals whether an account exists or which auth provider it uses. Also update CLAUDE.md/AGENTS.md for the removed CSRF middleware and the correct /webhooks/stripe path. [private session redacted]

    Files
    3
    Added
    +68
    Removed
    −77
  3. Dependency remediation: clear all critical/high vulnerabilities

    Claude c5e2f573

    Commit notes

    bun audit: 81 vulnerabilities (2 critical, 34 high) -> 5 (0 critical, 0 high, 3 moderate, 2 low). - Add root overrides forcing patched transitive versions: protobufjs (RCE advisory), fast-xml-parser (entity-encoding bypass), devalue, ws, fast-uri, file-type, socket.io-parser. - Re-resolve stale lockfile entries that were pinning dozens of vulnerable transitive versions. - Update in-range direct deps: elysia 1.4.28, svelte 5.56.3, vite 7.3.5, @sveltejs/kit 2.64, drizzle-orm 0.45.2 (patch), @elysiajs/eden 1.4.9 aligned across workspaces. - Bump OpenTelemetry series 0.208 -> 0.217 (+ auto-instrumentations 0.75); telemetry init/shutdown smoke-tested. - Remove @ai-sdk/openai + ai from the root manifest (used only by the backend) and align the backend to the newer resolved versions, eliminating cross-workspace version skew. - Reformat 3 svelte files whose Prettier output changed with the toolchain bump. Residual (documented, deliberate): nodemailer <8.0.4 (low/moderate advisories only; fix is a major bump), esbuild <=0.24.2 (dev-only chains: drizzle-kit, react-email, adapter-netlify), cookie <0.7.0 (pinned by @sveltejs/kit; overriding would break elysia's cookie 1.x). Validated: 217 backend tests pass against live Postgres/Redis, tsc + biome clean, frontend svelte-check 0 errors, prettier/eslint clean, production builds of both workspaces succeed. [private session redacted]

    Files
    7
    Added
    +475
    Removed
    −605
  4. Add explicit Biome config; enable Biome in VS Code

    Claude 6be53174

    Commit notes

    Commit a biome.json matching the defaults the codebase already passes (tab indent, double quotes, recommended rules, organized imports) so lint behavior is explicit and tunable, and turn the Biome extension on in workspace settings so backend lint feedback is live in-editor instead of CI-only. [private session redacted]

    Files
    3
    Added
    +29
    Removed
    −2
  5. Adopt typed HTTP errors across route handlers

    Claude 161208c3

    Commit notes

    Convert 229 user-facing 'throw new Error(...)' sites in routes to NotFoundError (140), BadRequestError (53), ForbiddenError (21), and ConflictError (15) so clients get 4xx statuses instead of 500s with suppressed messages. Server/config faults and webhook-handler throws deliberately remain plain Error (5xx) so Stripe retries. Messages are unchanged. [private session redacted]

    Files
    16
    Added
    +289
    Removed
    −227
  6. Fix Stripe webhook verification failing on every delivery under Bun

    Claude 78de2fbf

    Commit notes

    stripe.webhooks.constructEvent (sync) throws unconditionally on Bun because the SDK selects the async-only SubtleCrypto provider, so every webhook delivery returned 500 and subscriptions/purchases never completed via webhook. Switch to constructEventAsync. Caught by the new webhook integration test. Also converts redirect-URL validation throws to BadRequestError (part of the error-hierarchy sweep). [private session redacted]

    Files
    1
    Added
    +7
    Removed
    −3
  7. Add Stripe webhook integration tests (signature + dedupe)

    Claude 378d40f5

    Commit notes

    Mounts the real webhook route against live Redis: verifies correctly signed events are accepted, duplicate deliveries are acknowledged without reprocessing (with a TTL'd claim), and bad/missing signatures are rejected. Test setup provides dummy Stripe credentials (||=, real values win) since constructEvent verification is pure crypto. [private session redacted]

    Files
    2
    Added
    +154
    Removed
    −0
  8. Add HTTP error hierarchy and map it in the global error handler

    Claude 89db2930

    Commit notes

    NotFoundError/ConflictError/ForbiddenError/BadRequestError now map to their status codes; previously every business-condition throw surfaced as a 500 with the message suppressed in production. Client-caused (4xx) errors are no longer logged as server faults. [private session redacted]

    Files
    2
    Added
    +67
    Removed
    −2
  9. Remove data! assertions in API client; delete unused mock data

    Claude 7455692e

    Commit notes

    Replace the error-check-then-data! pattern with a combined (error || !data) guard across all lib/api modules (100 occurrences, 10 files) so a response with neither error nor data fails loudly instead of crashing downstream. Delete lib/mock-data.ts: its only export was never imported. [private session redacted]

    Files
    11
    Added
    +196
    Removed
    −281
  10. Docs truth pass: fix Stripe/Mollie drift, ports, branding; archive stale docs

    Claude b0dab524

    Commit notes

    - AGENTS.md: payments are Stripe (not Mollie); stripe-connect route/ service/file references corrected; CI section now reflects the inactive ci/ci.yml reality; storefront-first positioning. - README.md: frontend port is 4000 (was 5173); CI claim corrected; removed nonexistent /healthz endpoint claim. - env examples rebranded to Grainstash where safe (values provisioned by docker-compose left consistent with compose defaults). - 12 stale pre-Stripe/pre-rebrand review/spec docs moved to docs/archive/ with an explanatory README; CLAUDE.md and PRICING.md references updated. [private session redacted]

    Files
    31
    Added
    +7453
    Removed
    −7451
  11. Validate JWT header algorithm; remove dead CSRF middleware

    Claude 84b46961

    Commit notes

    - Reject JWTs whose header claims any algorithm other than HS256 (verification was already HMAC-pinned; this removes the confusion vector entirely). - Remove the CSRF middleware: its enforcement hook was encapsulated in the plugin (Elysia local scope) and never executed for any route in any environment — verified empirically. More fundamentally, the API carries no ambient credentials (Bearer-token auth, refresh tokens in request bodies, no auth cookies), so double-submit CSRF protects nothing here. The frontend never fetched or sent tokens either. Documented in index.ts so cookie-auth work knows to reintroduce it. [private session redacted]

    Files
    3
    Added
    +20
    Removed
    −203
  12. Add event-id idempotency to the Stripe webhook

    Claude f9117cf1

    Commit notes

    Stripe retries deliveries, so the same event can arrive multiple times. Claim each event id in Redis (SET NX, 4-day TTL) after signature verification and acknowledge duplicates without reprocessing. Fails open on Redis errors; handler-level DB checks remain the backstop. [private session redacted]

    Files
    1
    Added
    +33
    Removed
    −0
  13. Sanitize zip entry paths in purchase download grants

    Claude 0e5f3f6b

    Commit notes

    Creator-controlled folder paths and display names were written into zip entries unvalidated, so a malicious pack could produce archive entries like ../../etc/passwd. Add sanitizeZipFileName/sanitizeZipFolderPath (strip traversal segments, path separators, control chars, reserved chars; cap depth and length) with unit tests, and apply them in the download-grant zip builder. [private session redacted]

    Files
    3
    Added
    +104
    Removed
    −3
  14. Fix N+1 queries in workspace list and share activity; close activity IDOR

    Claude e1a61417

    Commit notes

    - Workspace list: batch member counts with one GROUP BY query instead of one COUNT per workspace (twice over). - Share activity: batch resource-name lookups per resource type with inArray instead of one query per link via Promise.all; cap the link list at 200 rows (was unbounded). - Replace raw SQL id interpolation with a parameterized inArray. - The activity shareLinkId filter now verifies the link belongs to the requesting user; previously any link id exposed its activity. - Sanitize zip entry file names in the share-link zip download. [private session redacted]

    Files
    2
    Added
    +100
    Removed
    −52
  15. Add June 2026 technical audit and prioritized improvement plan

    Claude 5abe40e4

    Commit notes

    Full-repo audit: repo map, severity-rated findings across security, architecture, code quality, testing, performance, dependencies, DevEx, and docs; improvement strategy with explicit non-goals; milestone task plan with quick wins and implementation sketches. Analysis only — no code changes. [private session redacted]

    Files
    1
    Added
    +453
    Removed
    −0
13 commits
  1. Add missing password-reset frontend pages

    Claude 86a298c8

    Commit notes

    The password-reset emails link to /auth/reset-password on the frontend, but no such page existed (the frontend UI is Google-OAuth-only while the backend fully supports local accounts), so every reset link was a dead 404. - /auth/reset-password: consumes the emailed token, new password + confirmation form, handles missing/expired tokens. - /auth/forgot-password: enumeration-safe request form (mirrors the backend's identical-response behavior) and the re-request target for expired links. - API client: requestPasswordReset / resetPassword via Eden. [private session redacted]

    Files
    3
    Added
    +212
    Removed
    −0
  2. Fix purchase-security and payment logic bugs

    Claude 70b34ffe

    Commit notes

    Fixes the logic bugs found in the codebase analysis: - Guest purchase takeover (HIGH): registering an account with a guest buyer's email no longer claims their purchases. Guest purchases are only linked once the email is verified — via a new email verification flow (token table was dead schema; now wired: users.email_verified_at column, /auth/verify-email + /auth/resend-verification endpoints, verification email on signup, frontend verify page). Google OAuth emails count as verified. hasPurchasedPack only matches by email for verified accounts. - Partial refunds (HIGH): charge.refunded webhook events for PARTIAL refunds no longer revoke the buyer's downloads and claw back the creator's full earnings; only full refunds are processed. - Price validation (MEDIUM): pack priceCents is now a non-negative integer at the schema level, and packs/bundles must be free or cost at least Stripe's €0.50 minimum. Coupons are clamped so the remaining charge never drops below that minimum (previously a 90%-off coupon on a cheap pack produced checkouts Stripe rejects). - Bundle webhook ordering (MEDIUM): persist the payment intent ID before completing bundle purchases, so a crash mid-webhook can't leave completed purchases that refunds can never find. - Double-charge protection (MEDIUM): re-running checkout for the same pack/bundle reuses the existing open Stripe session instead of opening a second payable one; stale sessions are expired and their pending rows released; completed-but-unprocessed sessions block re-purchase. Failed session creation no longer leaves orphaned pending purchases. - Download zip memory (LOW): pack downloads now stream files through the archiver with backpressure instead of buffering every file (and effectively the whole zip) in memory. - customer.subscription.updated is now handled, keeping status/period in sync with portal-side changes without recording spurious payments. Also makes the frontend lint gate green: formats 6 drifted files and fixes 3 pre-existing ESLint errors that were masked by the format failure. [private session redacted]

    Files
    24
    Added
    +7494
    Removed
    −92
  3. Harden auth: constant-time login + refresh token rotation

    Claude 84a54276

    Commit notes

    Two auth weaknesses in the credential and session flow: - Login user enumeration via timing. The login handler returned immediately when no account matched the email, but ran bcrypt (~tens of ms) when it did, so an attacker could distinguish registered from unregistered emails by response latency despite the identical "Invalid credentials" message. Now the handler always runs a bcrypt comparison — against a cached dummy hash when the account is missing — so timing no longer depends on account existence. - Refresh tokens were rotated but not revoked. POST /auth/refresh issued a new refresh token while leaving the presented one valid until its natural expiry, so a leaked refresh token stayed usable indefinitely and token reuse went undetected. The endpoint now revokes the presented token when it issues the replacement, so a single refresh token cannot be reused after rotation. [private session redacted]

    Files
    1
    Added
    +36
    Removed
    −7
  4. Make payout processing and cancellation atomic and race-safe

    Claude 0fd1ec18

    Commit notes

    processPayout and cancelPayout both used a read-then-check status guard followed by separate, non-transactional UPDATEs whose WHERE clauses did not re-check the status. This left two money bugs under concurrency/retries: - processPayout: two calls (double-click, webhook retry) could both pass the `status === "pending"` read, then each deduct pendingBalanceCents and add to totalPayoutsCents — double-counting the payout. - cancelPayout: two concurrent cancels — or a cancel racing processPayout — could each return amountCents to availableBalanceCents, minting money the creator never earned (only floored at 0 on the pending side by GREATEST). Both now follow the same atomic pattern already used by requestPayout and completePurchase: a single transaction whose status compare-and-swap (`WHERE status = 'pending' ... RETURNING`) is the sole point of mutual exclusion. If no row is returned, the call bails, so balances are adjusted exactly once regardless of concurrency or duplicate delivery. processPayout also now sends its notification email using the post-commit balances (accurate figures) instead of pre-update reads. No schema change; behavior is identical on the happy path. [private session redacted]

    Files
    1
    Added
    +120
    Removed
    −121
  5. Fix refunds wrongly draining platform balance for Stripe Connect sales

    Claude bd99294f

    Commit notes

    completePurchase credits earnings two ways: Stripe Connect sales pay the seller directly and only bump totalEarnings/totalSales (recordCreatorSaleInTx), while platform-balance sales also credit availableBalanceCents (creditCreatorEarningsInTx). processRefund, however, always deducted availableBalanceCents — so refunding a Connect sale drained balance the creator earned from other, non-refunded platform-balance sales (only floored at 0 by GREATEST, masking the corruption). The purchase row carried no record of which crediting path was used, so the refund couldn't mirror it. - Add purchases.paid_via_connect (boolean, default false) + migration 0022. - Set it in completePurchase's atomic completion update (authoritative point, same value used for crediting), keeping credit and refund symmetric. - processRefund now deducts availableBalanceCents only when !paidViaConnect; totalEarnings/totalSales still decrement for every refund. Default false preserves prior behavior for historical rows (whose path is unknown), so no regression on existing data. [private session redacted]

    Files
    5
    Added
    +6657
    Removed
    −3
  6. Apply fixed fee once per bundle transaction, not per pack

    Claude 3ee18834

    Commit notes

    Fixes an edge case in the fee floor added earlier: createBundlePurchase ran calculatePlatformFeeForAmount once per pack, so the fixed per-transaction component (PLATFORM_FEE_FIXED_CENTS) was charged N times for a single Stripe checkout. A 5-pack bundle took 5×€0.35 in fixed fees while the platform pays the processor's fixed cost only once — eroding seller earnings and contradicting the "per-transaction cost" rationale. - Add calculateBundleFeesForAmounts: percentage fee per pack, but the fixed component applied once across the bundle, distributed to packs that have room, capped so per-pack earnings stay >= 0. Fees + earnings reconcile exactly to the bundle price. - Extract a shared percentageFeeCents helper; single-pack fees unchanged. - Wire the bundle helper into createBundlePurchase. - Export allocateBundleAmounts (the proportional largest-remainder splitter) and add unit tests for it (conservation, proportionality, all-free split, empty list) plus the new bundle fee logic (single fixed component, reconciliation, zero-amount, one-pack == single-pack parity). - Document the once-per-bundle behavior in PRICING.md. [private session redacted]

    Files
    3
    Added
    +157
    Removed
    −7
  7. Clear uploads.ts dead code; make lint/typecheck CI gates required

    Claude 65b4afa6

    Commit notes

    Removes the abandoned chunked-upload code path and the type/lint debt it carried, so the backend is clean under both `biome check src` and `tsc --noEmit` (32 typecheck errors → 0). uploads.ts: - Remove four disabled chunked-upload endpoints (PUT /chunks/:i, POST /presigned-urls, POST /chunks/:i/confirm, POST /chunks/confirm-batch). Each threw "Chunked uploads are disabled" as its first statement; the unreachable code below produced 29 null-safety errors. The frontend uses none of them (it uploads via the direct presigned flow). - Remove the duplicate "legacy" POST /:uploadId/complete handler (shadowed by the identical active one earlier in the chain). - Drop now-unused storage imports and refresh the stale header doc comment. sharing-limits.ts: - Widen getSharingTierForPlan/getSharingLimitsForPlan params from the SubscriptionPlan union to string. The implementation already prefix-matches with a free-tier default, so it safely accepts the raw DB plan string; removes 3 errors at the share/upload-link call sites without casts. Repo-wide Biome formatting fixes (quote/import/wrapping normalization, no behavior change) so `biome check src` passes clean. CI: drop continue-on-error from the lint and typecheck jobs in ci/ci.yml; update ci/README.md and CLAUDE.md to reflect that both are now blocking gates. [private session redacted]

    Files
    13
    Added
    +73
    Removed
    −526
  8. Fix Pro-tier margin leak and pricing-model integrity

    Claude 19d88d28

    Commit notes

    Implements the decided hybrid pricing model (free 15% / Pro+Team 3%, storage-tiered subscription) and closes the unit-economics gap it had. - Add a fixed per-transaction component (PLATFORM_FEE_FIXED_CENTS, default €0.35) on top of the percentage platform fee. Stripe Connect destination charges deduct the processor's fixed cost (~€0.30) from the platform's application fee, so a pure-percentage fee at the reduced 3% Pro rate netted the platform a loss on most sample-pack sales. The fixed component is capped at the sale price (seller earnings never negative) and never applies to free items. - Export calculatePlatformFeeForAmount and add unit tests covering free/Pro splits, the zero-price guard, the negative-earnings cap, and the processor-cost invariant. - Fix stale "0% for Pro" comments in purchases.ts (the code charges 3%). - Remove the unimplemented "Priority audio analysis" claim from the landing page (analysis jobs are not enqueued with plan-based priority). - Add PRICING.md as the single source of truth and mark the three superseded pre-Stripe business-model docs as historical. [private session redacted]

    Files
    8
    Added
    +200
    Removed
    −18
  9. fix(db): add missing 0021 drizzle snapshot to eliminate generate drift

    Claude cb64dcd8

    Commit notes

    The drizzle/meta/ folder only had snapshots for a subset of migrations (0000, 0004, 0008, 0011, 0020), so the newest snapshot lagged the newest migration (0021_stripe_migration). Because `drizzle-kit generate` diffs schema.ts against the latest snapshot on disk, the next `db:generate` would re-surface everything migration 0021 already did (the mollie -> stripe transition) as a phantom pending migration. Generate the 0021 snapshot from schema.ts via drizzle-kit's own serializer (generateDrizzleJson), chained onto 0020's id, instead of hand-editing the 170KB+ JSON. Verified: - `drizzle-kit generate` -> "No schema changes, nothing to migrate" - `drizzle-kit check` -> "Everything's fine" Does not affect db:migrate or tests (snapshots are only used by generate/push). The remaining historical gaps (0001-0019 hand-written migrations) are not needed: generate only reads the latest snapshot and validation only flags malformed snapshots or prevId collisions, not gaps.

    Files
    1
    Added
    +6623
    Removed
    −0