Files
civitai__civitai/package.json
T
Justin Maier 8113e62777 fix(metrics): exclude metric-suppressed accounts from Postgres reaction sums (#4959)
* fix(metrics): exclude metric-suppressed accounts from Postgres reaction sums

The reaction counts shown on posts, articles and bounty entries are summed in
Postgres from ImageReaction/ArticleReaction/BountyEntryReaction with no exclusion
predicate, so they count accounts the reaction-abuse detector already suppressed
from every other reaction surface. Unlike the ClickHouse totals these never decay:
the jobs recompute the same unfiltered sum from the same rows, so the numbers stay
wrong until the queries filter.

Post has two live reaction queries, not one — post.metrics.ts delegates to
post.metrics-old.ts whenever the simplified-post-metrics flag reads false, which
includes Flipt being unreachable. Both filter now.

The jobs read the list through a new getMetricExcludedUserIdsOrThrow rather than
the existing lenient reader. The lenient one degrades to [] so the reaction
milestone keeps firing during an outage; a metric job doing that would write an
unfiltered total that nothing later recomputes, because a job only revisits an
entity that receives another reaction. Rejecting instead leaves the cursor and the
queue untouched in createMetricProcessor, so the window is recomputed next run.

Answer and Question reaction metrics have the same shape and are left alone — out
of the scope this was asked for, and named as exemptions in the guard rather than
skipped silently.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(metrics): zero the entities whose reactions are ALL excluded, and filter the milestones too

Five review lanes over 9cb53ed. Everything here is a finding they raised, each verified
against the code or the replica before acting on it.

Filtering the aggregate was not enough. An entity whose remaining countable reactions
are zero produces NO ROW from a GROUP BY over the reaction table, and a missing row means
"no change" to every writer downstream — so the pre-exclusion total survived even a full
recompute. Measured on the replica: 18 of 594 affected articles and 338 of 1,154 affected
bounty entries are in that state. Post and article seed zeros into ctx.updates before the
aggregate overwrites them; bountyEntry has no JS intermediate, so its CTE now drives from
the affected ids with a LEFT JOIN.

That LEFT JOIN needed timeframeSum to be NULL-safe. Its leading `WHEN NOT (cond) THEN 0`
is NULL for an unmatched row and falls through to the AllTime arm, counting a reaction
that is not there. `(cond) IS NOT TRUE` is identical for every inner-joined caller.
Verified on the replica: with the old form a fully-excluded entry returns 1 heart / 1 like,
with the new form 0.

The seeding was only safe once a pre-existing bug was fixed. Both post jobs bound their
chunk with `BETWEEN ids[0] AND ids[ids.length - 1]` over an unordered Set, so roughly half
of all chunks matched nothing. Seeding zeros into a chunk that matches nothing would have
written zeros over real counts. The chunk is sorted now.

The article and bounty-entry milestone notifications counted unfiltered. Before this work
both halves were unfiltered and therefore agreed; filtering only the displayed half would
have manufactured, for those two entities, the exact display-vs-notification divergence
this defect is a sibling of. They use the LENIENT reader on purpose — a notification
should degrade to the old count, not to silence.

The guard now covers the notifications too, and three mutations that were demonstrated to
pass against it: a `.catch(() => [])` on the strict read, a filter spliced inside an SQL
line comment, and a wrong column argument. The last is fixed by construction — the column
is hardcoded rather than passed, since a raw-SQL parameter beside an integer guard reads
as though the guard covered it.

Also: the strict reader now reports the outage to Axiom instead of surfacing only as a
generic job error, and a comment claiming coercion parity with metric-reaction-repair.service.ts
was false and now says so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(metrics): assert the emitted SQL, not that a token appears in the file

Round 2 of review demonstrated six mutations that pass the source guard, each measured
rather than argued: a second unfiltered count of the same table in the same template
literal; a consistent alias swap so `r."userId"` names the image owner instead of the
reactor; `.then().catch()` and a plain try/catch around the strict read; a key moved
into the exemption list, which had no length pin; a `/* */` comment around the splice;
and the bounty-entry filter moved out of the LEFT JOIN's ON into a WHERE, which
collapses it to an inner join and restores the no-row defect the rewrite exists to fix.

A source guard checks that a token appears in a file. It cannot see what the composed
statement does, which is why six separate textual assertions each missed one of these in
their own way. `post-reaction-metrics-sql.test.ts` calls the real getReactionTasks with a
fake pg that captures every statement, and asserts on the SQL that was actually sent —
one test that catches the alias swap, the swallow, the commented splice, the second
count, the missing sort and the missing zero-fill.

Its fixture crosses the 30,000-image chunk boundary and returns a LOWER run of post ids
second. That is not decoration: the first version of the sort control PASSED, because
`getAffected` sorts its own return, so a single-chunk fixture cannot produce the
out-of-order set the bug needs. It was an assertion that could not fail for the case it
was named after.

The guard keeps the cases it can see, hardened: block comments as well as line comments,
a requirement that the filter be built from a direct `await` of the reader rather than
any expression with somewhere to swallow a rejection, a requirement that the alias `r` is
bound to the reaction table and to nothing else, a shape pin on the bounty-entry ON
clause, and a length pin on the exemption list.

Two fixes to the round-1 fix. The Axiom report was effectively unreachable: one latch
shared by both readers, and the lenient one runs on every reaction toggle, so it wins
every race and the only line for an incident would say a notification degraded while the
metric jobs stalled silently. Keyed per outcome now. And `post.metrics.ts` chunked image
ids from a ClickHouse query with no ORDER BY under the same inverted-BETWEEN bug fixed
one block below; post.metrics-old.ts has that ORDER BY, the live path did not.

Both `!clickhouse` branches had no test at all, in any file, because every other test
supplies a client.

Backfill note, recorded here because a squash merge takes commit messages and not the PR
body: this does NOT close ClickUp 868m6vftv. The filter only corrects an entity the next
time it is affected, so the already-wrong rows stay wrong until a backfill recomputes
them. That ships separately against main, not stacked on this branch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(notifications): keep the ClickHouse client out of the client bundle

The milestone filter pulled the exclusion-list reader — and through it the ClickHouse
client — into the notification processor files. Those files are in the _app client
graph, because prepareMessage renders there. no-server-infra-in-app-graph caught it: it
ran and failed in 44ms before the full suite was killed by a daemon restart, so the one
real result that run produced was this.

A lazy import inside prepareQuery does not fix it. The guard says why and is right: a
dynamic import() still compiles the chunk into the client bundle.

So the processors no longer read the list at all. The server-only runner,
send-notifications.ts, reads it once per job run with the lenient reader and passes it
through NotificationProcessorRunInput, which prepareQuery already receives. The pure SQL
builder moves to ~/shared/utils/excluded-reactor-filter.ts with no imports, and
metrics/metric-helpers re-exports it. One read per run instead of one per processor.

The field is optional and the two reaction milestones default a missing list to [],
because degrading to the pre-exclusion count is already the posture these notifications
want, and ten existing processor tests construct the input without it. That makes the
runner the single point deciding whether milestones are filtered at all, so the guard now
pins it: it must read with the lenient reader and pass the list to every prepareQuery,
and a processor may import neither reader.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(notifications): pin the runner-to-milestone hand-off by behaviour, not spelling

Round 3 of review measured eight mutations that ship the reaction milestones unfiltered
with the source guard green, because the guard pinned how the hand-off is SPELLED rather
than what is passed: a shadowing `const excludedUserIds = []` inside the batch loop,
`excludedUserIds.length = 0` after the read, a spread that overrides the shorthand, a
processor that ignores its input, a filter built but never spliced, and the filter moved
into the `affected` CTE — valid SQL that narrows which entities are revisited while the
COUNT stays unfiltered.

send-notifications.excluded.test.ts runs the real job with the real processors, captures
the SQL they send, and asserts the filter lands in the CTE that COUNTS. All six of the
lane's mutations fail it by name; the processor-level ones fail only their own milestone.
It also asserts no processor logged an error, because the runner swallows a per-processor
throw and a milestone whose SQL no longer builds would otherwise read as "no query".

NotificationProcessorRunInput.excludedUserIds is now required but nullable rather than
optional. The milestones default a missing list to [], so a second runner that simply
omitted the key would ship every milestone unfiltered without a sound. Required, tsc
rejects it. Verified that the protection is real rather than vacuous: typecheck does not
read __tests__, which is why ten fixtures building this input still pass, so the control
was on the production caller — dropping the key from send-notifications.ts fails with
TS2345 at line 49.

Plus direct tests of the shared SQL builder's empty-list and non-integer branches, which
only the non-empty path had reached. With the integer guard removed, the three non-integer
cases fail.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(notifications): bound the counting-CTE slice instead of failing open

countingCte sliced from `affected_value AS (` to the next `), ` and, finding none, fell
back to the end of the query. Round 4 of review measured that as a green mutant: put the
next CTE's name on its own line and move the filter into `reaction_milestone`, a CTE
that counts nothing, and the slice ran on far enough to include it. The helper now bounds
the slice by the next CTE header and requires it to find one. That mutant is red now.

The same round confirmed the other six mutants go red on the assertion named for their
defect rather than incidentally, and found an alias-revert mutant (bounty table back to
`br` beside a filter that names `r`) that this test does not catch because it never
executes the SQL. It does not need to: no-unfiltered-reaction-metric-sum's alias
assertion fails on it, verified.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(notifications): give the pgDb mock its full export set

pgDbMock.parity requires every inline `~/server/db/pgDb` factory to list the complete
export set, because kyselyDb.ts destructures all of them at module-eval time and Vitest
throws on any omitted name — during module LOAD, so a suite that reaches it dies at
collection and reports zero tests rather than failing. The job test listed only
pgDbRead. It passed because kyselyDb is not in its graph, which is the case the guard
exists to stop depending on. Caught by the full suite, the only thing that runs it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 20:23:31 -06:00

457 lines
22 KiB
JSON

{
"name": "model-share",
"version": "5.1.113",
"private": true,
"packageManager": "pnpm@10.28.1",
"engines": {
"node": ">=24.0.0 <25"
},
"scripts": {
"preinstall": "npx only-allow pnpm",
"start": "next start",
"start-debug": "NODE_OPTIONS='--inspect' next start",
"build:workers": "node scripts/build-workers.mjs",
"clean": "node -e \"fs.rmSync('.next',{recursive:true,force:true})\"",
"predev": "pnpm build:workers",
"dev": "next dev",
"dev:auth": "pnpm --filter @civitai/auth-app dev",
"dev:moderator": "pnpm --filter @civitai/moderator-app dev",
"dev:creator-studio": "pnpm --filter @civitai/creator-studio-app dev",
"dev:training-studio": "pnpm --filter @civitai/training-studio-app dev",
"dev:storage": "pnpm --filter @civitai/storage-app dev",
"dev-low": "cross-env NODE_OPTIONS=\"--max_old_space_size=6144\" next dev",
"dev-debug": "pnpm build:workers && cross-env NODE_OPTIONS=\"--max_old_space_size=8192 --inspect\" next dev",
"dev-snap": "cross-env NODE_OPTIONS=\"--max_old_space_size=8192 --heapsnapshot-near-heap-limit=3\" next dev",
"dev:daemon": "node .claude/skills/dev-server/console.mjs",
"dev:rgb": "node .claude/skills/dev-server/cli.mjs rgb start",
"dev:rgb:stop": "node .claude/skills/dev-server/cli.mjs rgb stop",
"dev:rgb:status": "node .claude/skills/dev-server/cli.mjs rgb status",
"prod": "cross-env NODE_ENV=production next dev",
"boost": "next-boost",
"release:base": "git checkout release && git pull --rebase && git rebase main && git push --force-with-lease && git checkout main",
"release:major": "git pull && npm version major && git push --follow-tags && pnpm run release:base",
"release:minor": "git pull && npm version minor && git push --follow-tags && pnpm run release:base",
"release:patch": "git pull && npm version patch && git push --follow-tags && pnpm run release:base",
"release": "pnpm run release:patch",
"release:auth": "pnpm run release:auth:patch",
"release:auth:patch": "node scripts/release-app.mjs apps/auth auth-app-v patch",
"release:auth:minor": "node scripts/release-app.mjs apps/auth auth-app-v minor",
"release:auth:major": "node scripts/release-app.mjs apps/auth auth-app-v major",
"release:notifications": "pnpm run release:notifications:patch",
"release:notifications:patch": "node scripts/release-app.mjs apps/notifications notifications-v patch",
"release:notifications:minor": "node scripts/release-app.mjs apps/notifications notifications-v minor",
"release:notifications:major": "node scripts/release-app.mjs apps/notifications notifications-v major",
"release:creator-studio": "pnpm run release:creator-studio:patch",
"release:creator-studio:patch": "node scripts/release-app.mjs apps/creator-studio creator-studio-v patch",
"release:creator-studio:minor": "node scripts/release-app.mjs apps/creator-studio creator-studio-v minor",
"release:creator-studio:major": "node scripts/release-app.mjs apps/creator-studio creator-studio-v major",
"release:storage": "pnpm run release:storage:patch",
"release:storage:patch": "node scripts/release-app.mjs apps/storage storage-v patch",
"release:storage:minor": "node scripts/release-app.mjs apps/storage storage-v minor",
"release:storage:major": "node scripts/release-app.mjs apps/storage storage-v major",
"release:event-engine": "pnpm run release:event-engine:patch",
"release:event-engine:patch": "node scripts/release-app.mjs apps/event-engine event-engine-v patch",
"release:event-engine:minor": "node scripts/release-app.mjs apps/event-engine event-engine-v minor",
"release:event-engine:major": "node scripts/release-app.mjs apps/event-engine event-engine-v major",
"release:moderator": "pnpm run release:moderator:patch",
"release:moderator:patch": "node scripts/release-app.mjs apps/moderator moderator-v patch",
"release:moderator:minor": "node scripts/release-app.mjs apps/moderator moderator-v minor",
"release:moderator:major": "node scripts/release-app.mjs apps/moderator moderator-v major",
"release:training-studio": "pnpm run release:training-studio:patch",
"release:training-studio:patch": "node scripts/release-app.mjs apps/training-studio training-studio-v patch",
"release:training-studio:minor": "node scripts/release-app.mjs apps/training-studio training-studio-v minor",
"release:training-studio:major": "node scripts/release-app.mjs apps/training-studio training-studio-v major",
"yolo": "pnpm run release",
"seed:scanner-policies": "tsx --env-file=.env scripts/seed-scanner-policies.ts",
"prebuild": "pnpm build:workers",
"build": "next build",
"build:dev": "pnpm build:workers && cross-env NODE_OPTIONS=\"--max_old_space_size=16384\" next build",
"build:analyze": "cross-env NODE_OPTIONS=\"--max_old_space_size=16384\" ANALYZE=true next build",
"size": "node scripts/bundle-budget.mjs",
"deploy": "pnpm run build && pnpm run db:deploy",
"postinstall": "pnpm run db:generate",
"typecheck": "node scripts/typecheck.mjs",
"lint": "eslint src/ --cache --cache-strategy metadata",
"lint:packages": "eslint packages --ext .ts",
"eslint": "cross-env TIMING=1 eslint src/ --quiet --cache --cache-strategy metadata",
"prettier:check": "node scripts/prettier-changed.mjs check",
"prettier:write": "node scripts/prettier-changed.mjs write",
"db:ui": "prisma studio",
"db:pull": "prisma db pull",
"db:push": "prisma db push",
"db:migrate": "node scripts/prisma-migrate-with-views-workaround.mjs",
"db:migrate:empty": "node scripts/create-empty-migration.mjs",
"db:applied": "node scripts/prisma-mark-migration-applied.mjs",
"db:deploy": "node scripts/prisma-migrate-with-views-workaround.mjs -p && npm run db:program",
"db:program": "node scripts/prisma-prepare-programmability.mjs",
"db:generate": "node scripts/generate-slim-schema.js && prisma generate --no-hints",
"db:check-generated": "pnpm run db:generate && git diff --exit-code -- packages/civitai-db-schema/src",
"db:seed": "prisma db seed",
"db:moderator:pull": "prisma db pull --schema apps/moderator/prisma/schema.prisma",
"db:moderator:generate": "prisma generate --no-hints --schema apps/moderator/prisma/schema.prisma",
"share": "ngrok http 3000",
"prepare": "husky",
"analyze": "cross-env ANALYZE=true next build",
"analyze:server": "cross-env BUNDLE_ANALYZE=server next build",
"analyze:browser": "cross-env BUNDLE_ANALYZE=browser next build",
"tsc:trace": "cross-env NODE_OPTIONS=\"--max_old_space_size=8192\" tsc --generateTrace ./trace --incremental false",
"tsc:analyze": "npx analyze-trace trace",
"test": "cross-env NODE_ENV=development npx playwright test",
"test:ui": "cross-env NODE_ENV=development npx playwright test --ui",
"test:gen": "cross-env NODE_ENV=development npx playwright codegen",
"test:reset": "make bootstrap-db",
"test:unit": "vitest --project 'unit*'",
"test:unit:run": "node scripts/test-unit-run.mjs",
"test:unit:coverage": "vitest run --project 'unit*' --coverage",
"test:packages": "vitest --project '@civitai/*'",
"test:packages:run": "vitest run --project '@civitai/*'",
"test:apps": "vitest --project 'app:*'",
"test:apps:run": "vitest run --project 'app:*'",
"test:lint-rules": "vitest run --project 'unit*' src/server/notifications/__tests__/notification-settings-polarity.test.ts src/server/schema/__tests__/track.addView.schema.test.ts src/server/services/__tests__/hub-filter-parity.test.ts src/server/services/__tests__/poi-checks-strip-benign-phrases.test.ts src/server/services/__tests__/video-leaderboard-badge-staging.test.ts src/server/services/__tests__/no-agent-ground-truth-write.test.ts src/server/services/__tests__/no-coerce-boolean-in-api.test.ts src/server/services/__tests__/no-direct-shared-module-mock.test.ts src/server/services/__tests__/no-divergent-active-sales-cap.test.ts src/server/services/__tests__/no-divergent-author-fee-base.test.ts src/server/services/__tests__/no-divergent-can-generate-derivation.test.ts src/server/services/__tests__/no-divergent-model-recency-derivation.test.ts src/server/services/__tests__/no-divergent-generation-submit-payload.test.ts src/server/services/__tests__/no-divergent-paid-gate-derivation.test.ts src/server/services/__tests__/no-divergent-safetensor-rule.test.ts src/server/services/__tests__/no-doubled-free-slot-noun.test.ts src/server/services/__tests__/no-hand-typed-redis-key-constants.test.ts src/server/services/__tests__/no-io-in-transaction.test.ts src/server/services/__tests__/no-job-kind-on-remix-mint.test.ts src/server/services/__tests__/no-lint-rules-script-drift.test.ts src/server/services/__tests__/no-menu-target-tooltip-nesting.test.ts src/server/services/__tests__/no-module-scope-cache.test.ts src/server/services/__tests__/no-pk-addressed-engagement-write.test.ts src/server/services/__tests__/no-server-infra-in-app-graph.test.ts src/server/services/__tests__/no-sharp-outside-native-project.test.ts src/server/services/__tests__/no-ssr-divergent-media-query.test.ts src/server/services/__tests__/no-stale-moderator-route-probe.test.ts src/server/services/__tests__/no-static-html2canvas-import.test.ts src/server/services/__tests__/no-unbounded-paging-fake.test.ts src/server/services/__tests__/no-unbumped-draft-status-write.test.ts src/server/services/__tests__/no-unfiltered-reaction-metric-sum.test.ts src/server/services/__tests__/no-unguarded-billable-submit.test.ts src/server/services/__tests__/no-unguarded-block-bridge-token.test.ts src/server/services/__tests__/no-unguarded-block-rest-token.test.ts src/server/services/__tests__/no-unguarded-user-text.test.ts src/server/services/__tests__/no-unhydrated-home-block-reactions.test.ts src/server/services/__tests__/no-unloadable-image-fixture.test.ts src/server/services/__tests__/no-unmoderated-blob-retraction.test.ts src/server/services/__tests__/no-unmuteable-comment-processor.test.ts src/server/services/__tests__/no-unpriced-default-model.test.ts src/server/services/__tests__/no-unroled-image-resource-match.test.ts src/server/services/__tests__/no-unscoped-email-verification-exemption.test.ts src/server/services/__tests__/no-untruthy-query-gate.test.ts src/server/services/__tests__/no-unverified-provenance-write.test.ts src/server/services/__tests__/no-unwrapped-knob-rotation.test.ts src/server/services/__tests__/no-wholesale-module-mock.test.ts",
"test:component": "node scripts/test-component-run.mjs",
"test:component:watch": "vitest --project component",
"test:geometry": "vitest run --project geometry",
"test:geometry:watch": "vitest --project geometry",
"meilisearch:migrate": "NODE_ENV=development tsx scripts/oneoffs/meilisearch-migration.ts",
"tsscript": "NODE_ENV=development tsx",
"madge:orphans": "madge --orphans --image ./public/orphans-graph.svg --ts-config ./tsconfig.json --extensions ts,tsx src/",
"depcheck": "depcheck",
"generate-types": "typed-scss-modules src",
"ts-script": "NODE_ENV=development tsx",
"generate:moderator-endpoints": "node scripts/generate-moderator-endpoint-catalog.mjs"
},
"prisma": {
"schema": "packages/civitai-db-schema/prisma/schema.prisma",
"seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} packages/civitai-db-schema/prisma/seed.ts"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.490.0",
"@aws-sdk/lib-storage": "^3.490.0",
"@aws-sdk/s3-request-presigner": "^3.490.0",
"@axiomhq/axiom-node": "^0.12.0",
"@civitai/app-sdk": "^0.14.0",
"@civitai/auth": "workspace:*",
"@civitai/buzz": "workspace:*",
"@civitai/client": "0.2.0-beta.98",
"@civitai/cybertipline-tools": "^0.1.0",
"@civitai/db-queries": "workspace:*",
"@civitai/db-schema": "workspace:*",
"@civitai/flipt": "workspace:*",
"@civitai/generation-metadata": "^0.3.0",
"@civitai/moderation": "workspace:*",
"@civitai/next-axiom": "^0.17.0",
"@civitai/orchestration-client": "0.2.0-beta.106",
"@civitai/shared": "workspace:*",
"@clavata/sdk": "^0.2.3",
"@clickhouse/client": "^1.23.1",
"@coinbase/cdp-sdk": "^1.13.0",
"@discordjs/rest": "^2.6.0",
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@emotion/react": "^11.10.4",
"@essentials/one-key-map": "^1.2.0",
"@flipt-io/flipt-client-js": "^0.2.0",
"@floating-ui/dom": "^1.6.0",
"@google-cloud/recaptcha-enterprise": "^5.1.1",
"@grafana/faro-web-sdk": "2.8.2",
"@grafana/faro-web-tracing": "2.8.2",
"@headlessui/react": "2.2",
"@hookform/resolvers": "^5.1.1",
"@mantine/core": "^7.17.7",
"@mantine/dates": "^7.17.7",
"@mantine/dropzone": "^7.17.7",
"@mantine/hooks": "^7.17.7",
"@mantine/modals": "^7.17.7",
"@mantine/notifications": "^7.17.7",
"@mantine/nprogress": "^7.17.7",
"@mantine/tiptap": "^7.17.7",
"@marsidev/react-turnstile": "^1.0.1",
"@meilisearch/instant-meilisearch": "0.13.5",
"@microsoft/signalr": "^7.0.10",
"@next/bundle-analyzer": "^16.3.0",
"@next/third-parties": "^15.0.3",
"@node-oauth/oauth2-server": "^5.3.0",
"@number-flow/react": "^0.5.7",
"@okikio/sharedworker": "^1.1.0",
"@openrouter/sdk": "^0.5.1",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/api-logs": "^0.219.0",
"@opentelemetry/core": "2.9.0",
"@opentelemetry/exporter-logs-otlp-proto": "^0.219.0",
"@opentelemetry/exporter-trace-otlp-proto": "^0.219.0",
"@opentelemetry/instrumentation": "0.219.0",
"@opentelemetry/instrumentation-http": "^0.213.0",
"@opentelemetry/instrumentation-redis": "^0.61.0",
"@opentelemetry/resources": "2.9.0",
"@opentelemetry/sdk-logs": "^0.219.0",
"@opentelemetry/sdk-node": "^0.219.0",
"@opentelemetry/sdk-trace-node": "^2.5.0",
"@opentelemetry/sdk-trace-web": "2.9.0",
"@opentelemetry/semantic-conventions": "^1.39.0",
"@paddle/paddle-js": "^1.2.1",
"@paddle/paddle-node-sdk": "^1.4.1",
"@paypal/react-paypal-js": "^8.1.3",
"@prisma/client": "^6.3.0",
"@prisma/instrumentation": "^7.4.2",
"@pyroscope/nodejs": "0.6.2",
"@react-hook/window-size": "^3.1.1",
"@react-pdf/renderer": "^3.3.8",
"@stripe/react-stripe-js": "^2.4.0",
"@stripe/stripe-js": "^2.2.0",
"@tabler/icons-react": "^3.7.0",
"@tanstack/react-query": "^5.101.0",
"@tanstack/react-query-devtools": "^5.101.0",
"@tanstack/react-virtual": "^3.13.12",
"@tiptap/core": "3.16.0",
"@tiptap/extension-color": "3.16.0",
"@tiptap/extension-heading": "3.16.0",
"@tiptap/extension-image": "3.16.0",
"@tiptap/extension-link": "3.16.0",
"@tiptap/extension-mention": "3.16.0",
"@tiptap/extension-placeholder": "3.16.0",
"@tiptap/extension-text": "3.16.0",
"@tiptap/extension-text-style": "3.16.0",
"@tiptap/extension-underline": "3.16.0",
"@tiptap/extension-youtube": "3.16.0",
"@tiptap/extensions": "3.16.0",
"@tiptap/html": "3.16.0",
"@tiptap/pm": "3.16.0",
"@tiptap/react": "3.16.0",
"@tiptap/starter-kit": "3.16.0",
"@tiptap/static-renderer": "3.16.0",
"@tiptap/suggestion": "3.16.0",
"@trpc/client": "^11.17.0",
"@trpc/next": "^11.17.0",
"@trpc/react-query": "^11.17.0",
"@trpc/server": "^11.17.0",
"@types/stream-to-blob": "^2.0.0",
"@typescript/analyze-trace": "^0.10.1",
"algoliasearch": "^4.23.3",
"archiver": "^6.0.1",
"blurhash": "^2.0.4",
"chalk": "^5.2.0",
"chart.js": "^4.4.0",
"chartjs-adapter-dayjs-4": "^1.0.4",
"circular-dependency-plugin": "^5.2.2",
"cloudflare": "^2.9.1",
"clsx": "^2.1.1",
"compromise": "^14.14.4",
"cookies-next": "^2.1.1",
"dayjs": "^1.11.12",
"decimal.js": "^10.5.0",
"devalue": "5.8.1",
"diff": "4.0.2",
"discord-api-types": "^0.38.37",
"discord.js": "^14.7.1",
"dotenv": "^16.4.5",
"draft-js": "^0.11.7",
"embla-carousel": "^8.6.0",
"embla-carousel-autoplay": "^8.5.2",
"embla-carousel-react": "^8.5.2",
"exceljs": "^4.4.0",
"exifreader": "^4.39.0",
"fastest-levenshtein": "^1.0.16",
"file-saver": "^2.0.5",
"form-graph": "^0.4.2",
"google-auth-library": "^9.15.0",
"googleapis": "^144.0.0",
"gray-matter": "^4.0.3",
"happy-dom": "^20.0.2",
"he": "^1.2.0",
"html-to-text": "^9.0.5",
"html2canvas-pro": "2.3.8",
"htmlparser2": "8.0.2",
"idb-keyval": "^6.2.0",
"immer": "^9.0.15",
"instantsearch.js": "4.64.1",
"jose": "^6.0.11",
"js-yaml": "^4.1.1",
"jsonwebtoken": "^9.0.1",
"jssha": "^3.3.1",
"jszip": "^3.10.1",
"konva": "^10.0.12",
"linkify-react": "^4.1.3",
"linkifyjs": "^4.1.3",
"lodash-es": "^4.17.21",
"lottie-react": "^2.4.1",
"lru-cache": "^11.2.2",
"mantine-react-table": "^2.0.0-beta.9",
"masonic": "^3.7.0",
"meilisearch": "0.33.0",
"motion": "^11.11.17",
"msgpackr": "^1.10.2",
"next": "^16.3.1",
"nodemailer": "^6.8.0",
"obscenity": "^0.4.5",
"openai": "^4.73.0",
"p-limit": "^6.2.0",
"path-to-regexp": "^6.2.1",
"pg": "^8.11.3",
"prom-client": "^14.2.0",
"qrcode.react": "^4.2.0",
"query-string": "^7.1.1",
"rand-seed": "^1.0.2",
"randomstring": "^1.3.0",
"react": "^18.3.1",
"react-blurhash": "^0.2.0",
"react-chartjs-2": "^5.2.0",
"react-dom": "^18.3.1",
"react-easy-crop": "^5.4.1",
"react-highlight-within-textarea": "^3.2.1",
"react-hook-form": "^7.71.1",
"react-instantsearch": "7.12.0",
"react-instantsearch-router-nextjs": "7.12.0",
"react-intersection-observer": "^9.4.0",
"react-joyride": "^2.9.3",
"react-konva": "^18.2.14",
"react-markdown": "^9.0.1",
"react-social-media-embed": "^2.5.9",
"recheck": "^4.5.0",
"redis": "^5.8.3",
"rehype-raw": "^7.0.0",
"rehype-stringify": "^10.0.1",
"remark-breaks": "^4.0.0",
"remark-gfm": "^4.0.0",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.1",
"request-ip": "^3.3.0",
"sanitize-html": "2.12.1",
"sass": "^1.82.0",
"semver": "^7.6.0",
"sharp": "^0.32.6",
"slate": "^0.94.1",
"slate-history": "^0.93.0",
"slate-react": "^0.95.0",
"slugify": "^1.6.5",
"socket.io-client": "^4.5.4",
"source-map": "^0.7.4",
"sqids": "^0.3.0",
"stacktrace-parser": "^0.1.10",
"stream-to-blob": "^2.0.1",
"stripe": "^11.6.0",
"superjson": "^2.2.6",
"three": "^0.180.0",
"trie-memoize": "^1.2.0",
"unfurl.js": "^6.4.0",
"unified": "^11.0.5",
"use-sound": "^5.0.0",
"uuid": "^9.0.0",
"viem": "^2.30.6",
"xml2js": "^0.6.2",
"yaml": "^2.8.1",
"zod": "^4.0.17",
"zustand": "^4.3.7"
},
"devDependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.2.6",
"@electric-sql/pglite": "^0.4.6",
"@faker-js/faker": "^9.0.3",
"@ladle/react": "^5.1.1",
"@next/eslint-plugin-next": "^15.5.19",
"@playwright/test": "^1.57.0",
"@prisma/generator-helper": "^5.22.0",
"@types/archiver": "^6.0.2",
"@types/cloudflare": "^2.7.9",
"@types/diff": "4.0.2",
"@types/file-saver": "^2.0.7",
"@types/he": "^1.2.3",
"@types/html-to-text": "^9.0.4",
"@types/js-yaml": "^4.0.9",
"@types/jsonwebtoken": "^9.0.2",
"@types/lodash-es": "^4.17.7",
"@types/mailchimp__mailchimp_marketing": "^3.0.12",
"@types/marked": "^4.0.7",
"@types/node": "24.13.3",
"@types/node-os-utils": "^1.3.1",
"@types/nodemailer": "^6.4.7",
"@types/offscreencanvas": "^2019.7.3",
"@types/pg": "^8.11.0",
"@types/pg-format": "^1.0.5",
"@types/randomstring": "^1.1.8",
"@types/react": "18.0.14",
"@types/react-dom": "18.0.5",
"@types/request-ip": "^0.0.37",
"@types/sanitize-html": "^2.6.2",
"@types/semver": "^7.7.1",
"@types/sharp": "^0.31.0",
"@types/three": "^0.180.0",
"@types/uuid": "^9.0.0",
"@types/vimeo__player": "^2.18.3",
"@types/xml2js": "^0.4.14",
"@typescript-eslint/eslint-plugin": "^5.33.0",
"@typescript-eslint/parser": "^5.33.0",
"@vitest/browser": "^4.1.11",
"@vitest/browser-playwright": "4.1.11",
"@vitest/coverage-v8": "^4.1.11",
"autoprefixer": "^10.4.19",
"cross-env": "^7.0.3",
"cssnano": "^7.0.1",
"esbuild": "^0.25.5",
"eslint": "8.57.1",
"eslint-config-next": "^15.5.19",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-local-rules": "^3.0.2",
"eslint-plugin-tailwindcss": "^3.15.1",
"husky": "^9.1.1",
"jsdom": "^27.4.0",
"pg-format": "^1.0.4",
"playwright": "^1.57.0",
"postcss": "^8.5.3",
"postcss-assign-layer": "^0.4.0",
"postcss-preset-mantine": "^1.17.0",
"postcss-simple-vars": "^7.0.1",
"prettier": "^2.8.8",
"prisma": "^6.3.0",
"prisma-generator-typescript-interfaces": "^1.6.1",
"prisma-kysely": "^2.2.0",
"tailwindcss": "^3.4.3",
"ts-node": "^10.9.1",
"tsx": "^4.19.2",
"turbo": "^2.9.17",
"typed-scss-modules": "^8.1.1",
"typescript": "^5.9.2",
"vitest": "^4.1.11",
"vitest-browser-react": "^2.2.0",
"ws": "^8.19.0"
},
"ct3aMetadata": {
"initVersion": "6.2.1"
},
"//eslint-config-next": "Stay on major 15. v16 is flat-config-only (peer eslint >=9); extending it from .eslintrc.js makes @eslint/eslintrc reject it and then crash while formatting the error, so lint silently never runs. Pinned below so a `pnpm up` can't walk it forward.",
"pnpm": {
"overrides": {
"eslint-config-next": "15",
"vite": "6.4.3",
"protobufjs@7": "^7.5.6",
"fast-xml-parser": "^5.9.3",
"@aws-sdk/core>fast-xml-parser": "5.2.5",
"axios": "^1.16.0",
"undici@6": "^6.27.0",
"tar-fs@2": "^2.1.5",
"tar-fs@3": "^3.1.3",
"ws@7": "^7.5.11"
},
"onlyBuiltDependencies": [
"@parcel/watcher",
"@prisma/client",
"@prisma/engines",
"bigint-buffer",
"bufferutil",
"core-js",
"esbuild",
"exifreader",
"msgpackr-extract",
"prisma",
"protobufjs",
"sharp",
"unrs-resolver",
"utf-8-validate"
],
"patchedDependencies": {
"@mantine/hooks": "patches/@mantine__hooks.patch"
}
}
}