mirror of
https://github.com/civitai/civitai.git
synced 2026-09-20 22:08:18 +08:00
main
84 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e92cf5fe4a |
test(geometry): a browser tier that loads the real cascade at a phone viewport (#4601)
* test(geometry): a browser tier that loads the real cascade at a phone viewport
Adds a fourth Vitest project, `geometry`, and demonstrates it catching a defect
whose own source comment records that nothing rendered can see it.
WHAT THE GAP IS, AND WHAT IT IS NOT. The `component` project is NOT jsdom — it is
real headless Chromium via @vitest/browser-playwright, `page.viewport()` moves
`window.innerWidth`, and `getBoundingClientRect()` returns real boxes. What it is
missing is the STYLESHEET and the VIEWPORT. `test/component-setup.tsx` injects
only the `:root` custom properties parsed out of globals.css, so the document
holds 24 CSS rules: Mantine classes are styleless, Tailwind utilities are inert,
and any `getComputedStyle` assertion whose expected value is the CSS initial
value passes against a broken component. And nothing sets a viewport, so files
inherit the runner's silent 414x896.
THE SAME FIXTURE, THE SAME CORRECT SOURCE, IN BOTH TIERS (PageBlockHost in its
production shell chain):
`component` `geometry`
viewport 414 x 896 390 x 844 (default vs set)
CSS rules in the document 24 3,677
box-sizing on a bare div content-box border-box
`className="flex"` block flex
chrome bar height 200 31
host frame height 350 844
APP COLUMN HEIGHT 150 813
That last row is the argument. 150 is ALSO what the app column measures once the
recorded `flex: 1` defect is planted, so a threshold written in the `component`
tier would have to expect the number the DEFECT produces.
THE HARNESS. `test/geometry-setup.tsx` loads the production cascade in production
order — the `@layer tailwind-preflight, theme, mantine, modules;` statement first
(as _document.tsx emits it), then globals.css, then every `@mantine/*` layer
stylesheet _app.tsx imports. It defaults to a 390x844 phone and THROWS unless the
window reports back the size it asked for; tests assert `observed` against their
own literal on top of that. It exports measurement helpers: `box`,
`childrenUnionBox` (the union of child rects — `scrollHeight` is clamped to the
padding box and cannot see a parent taller than its content), `flexAxis`,
`flexLonghands` (longhands, because `getComputedStyle(el).flex` serialises
`1 1 220px` and `1 1 0%` identically), and `cascadeEvidence`.
WHY A PROJECT AND NOT A CHANGE TO THE SHARED SETUP. Loading the cascade in
`component-setup.tsx` moves existing numbers — measured, the same chrome bar is
200px there and 31px with the cascade, a 169px move on one element, under 212
files / 2,362 tests of which 14 read getBoundingClientRect and 20 read
getComputedStyle. The per-file import pattern (15 files do it today, 3 also take
globals.css) stays available and is not deprecated; what it cannot give is a
guarantee — those files each picked their own subset, none declares the @layer
order, none sets a viewport, and the "did my stylesheet load" guard is
re-hand-rolled per file. The `geometry` glob (`src/**/*.geometry.test.tsx`) is
disjoint from every other project's, so nothing that runs today changes project
and no file is collected twice.
DEMONSTRATED RED. `PageBlockHostFillHeight.geometry.test.tsx` asserts that the
app column reaches the bottom of the host frame at 390x844 and at 390x640.
Dropping `flex: 1` from `app-page-content` (the mutation PageBlockHost.tsx's own
comment records as invisible to every rendered tier) fails it:
the app column ends at y=181 inside a frame that ends at y=844 — 663px of the
phone is blank below a running App Block. The column measured 150px of the
frame's 844px, with flex longhands {"grow":"0","shrink":"1","basis":"auto"}.
Restoring the property returns it to 10/10. A second mutant — dropping `flex: 1`
from the frame's `fit === 'fill'` branch — collapses the column to 269px and is
caught by the literal floor rather than by the frame/content comparison, which
both mutations keep satisfied.
BOTH MUTANTS ARE ALSO CAUGHT IN THE NODE TIER TODAY, by verbatim source pins in
pageBlockHostMaxWidth.test.ts and pageRunScrollContract.test.ts. Stated plainly
so this is not read as claiming otherwise. The difference is what each guard can
SEE: a source pin is a claim about the text of one file, blind to a collapse
arriving from the cascade, from an ancestor or from a viewport, and it has to be
rewritten every time the block is legitimately reformatted.
WHAT RUNS THIS TODAY: NOTHING. lint.yml selects `--project 'unit*'`,
`'@civitai/*'` and `'app:*'`; no pattern matches `component` or `geometry`,
because the Actions runners install no Chromium. `component` has the preview
pipeline's report-only status; `geometry` has no CI home at all. Wiring one is a
pipeline change and deliberately not in this PR — but a harness nothing runs
rots, so it is said out loud in the setup file rather than left to be discovered.
Verification: typecheck 0 errors · geometry 2 files / 10 tests passed · component
212 files / 2,362 tests passed (unchanged) · scripts unit tier 24 files / 521
tests passed · eslint clean on both new test files.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* ci(geometry): run the geometry tier, and refuse a green that collected nothing
Adds a `Geometry tests` job to lint.yml. Without it this harness runs in no gate
at all — the workflow selects projects by name (`unit*`, `@civitai/*`, `app:*`)
and none of those patterns matches `geometry`.
ALIGNED WITH THE `unit` JOB'S COMMENT, NOT AN OVERRIDE OF IT. That comment gives
exactly one reason for keeping browser tests out — "they need Chromium, which
this job does not install. That is the whole reason." — which is a statement
about what that job provides, not a ban on providing it. The same comment then
retracts the only other objection on the record ("Don't cite a cold-cache flake
as a reason to keep this job Chromium-free") and names vitest.config.mts's
dedupe + optimizeDeps pre-bundling as the canonical fix. A job that DOES install
Chromium satisfies the stated condition.
GEOMETRY ONLY. `component` stays ungated and that is now visible rather than
fixed. Measured on a 16-core box: `geometry` is 2 files / 10 tests in 9.07s;
`component` is 212 files / 2,362 tests in 112.92s wall, of which 334s is test
time spread across workers — so a 2-core runner (browser pool `min(12, cpus-1)`
= 1 instance) does not divide it. That is an order of magnitude more expensive,
with 212 files of pass/fail history this workflow has never seen. It belongs in
its own PR.
REPORT-ONLY, mirroring `unit` and for its stated reason: blocking a brand-new
tier from day one would red unrelated PRs and the job would be switched off
within a week. `main` has no required_status_checks, so nothing here blocks a
merge either way; `continue-on-error` only decides whether a red renders as red
or as red-but-ignored. The FLIP TO BLOCKING note says concretely what would make
that safe. The `unit` job's selectors and its `continue-on-error` are untouched.
CHROMIUM comes from `pnpm exec playwright install --with-deps chromium` — the
workspace-local playwright, so the revision follows this repo's own pin rather
than a second version written into the workflow. Desynchronising those two is
the documented failure mode (CLAUDE.md records 59 preview specs dying on a
revision mismatch with zero specs run). A local NixOS bundle mismatch is a
property of that host and is handled by PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH; the
pin is not moved to accommodate it.
A GREEN VITEST RUN IS A CLAIM, NOT EVIDENCE — the hazard the `packages` job's
ledger exists for, one project over. `--project` matching nothing exits 0, and
this tier's glob is deliberately narrow, which is exactly the kind of pattern
that can quietly stop matching. The new step asserts floors of 2 files and 10
tests from the JSON report, with `if: always()` so it also fires when the tests
fail or the runner aborts without writing a report.
Two things measured rather than assumed while writing it: the file count comes
from `testResults.length`, NOT `numTotalTestSuites` — against a real report this
run is 2 files while that field reads 4, because it counts `describe` blocks.
And the gate script was extracted back out of the parsed YAML and executed on
all three arms before commit: real report -> exit 0 (2 files, 10 tests); a
report with `testResults: []` -> exit 1; a missing report -> exit 1 with its own
message.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(ci): the geometry job must be report-only on PRs ONLY, not on pushes to main
Caught by this repo's own gate on the previous commit's run:
`scripts/__tests__/main-branch-ci-coverage.test.ts` > "has no unconditionally
report-only job on the push path" went red (Unit tests shard 2, 1 failed / 5,624
passed).
The job was written with a literal `continue-on-error: true`. That is report-only
on EVERY event, including a push to `main`, where it produces a run reporting
`success` while the step underneath failed — the stale-TRUE shape that guard
exists to forbid, and the exact hazard the `unit` job's own comment spells out:
"A green that has to be disbelieved is worse than no run at all."
Now `${{ github.event_name == 'pull_request' }}`, byte-identical to `unit`'s.
Report-only on PRs (a new tier should not red unrelated work while it settles),
honest verdict on `main` (where the merge has already happened and there is no
unrelated work to protect). The guard accepts a conditional precisely because a
conditional can differ between a PR and a push; a literal cannot.
The comment now records this so the next reader does not "simplify" it back.
Red at
|
||
|
|
afe84cf7c8 |
ci: shard the unit suite across 4 runners, 10.4m -> 3.8m (#4392)
Measured from the Actions API (n=12): the job was 555.5s of test work plus 66.5s of fixed overhead = 622s, the 10.4m median, and 3.4x the next-slowest job. T(N) = 66.5 + 555.5/N puts N=4 at ~3.4m. Measured after: 10.3m -> 3.8m on the critical path (2.7x) at +36% runner-minutes, reproduced across two runs with a 1.12-1.14x shard spread. N=4 rather than more because `App unit tests + typecheck` is 2.9m — past N=5 sharding optimises something that is no longer the bottleneck. Ships with a positive control, `scripts/ci/assert-shard-ran.mjs`. Sharding adds a failure this job did not have: a `--shard=i/N` selecting no files exits 0 and reports green, and four green checks look identical whether they ran 22,000 tests or none. The guard asserts per shard that work happened, counting EXECUTED assertions rather than `numTotalTests` (which includes skipped, so an all-self-skipping shard would pass a total-based floor), and its bounds scale with `total` so a change to the matrix does not trip them. It earned its keep immediately: on the first run it failed all four shards while `Unit tests` reported SUCCESS. `pnpm run … -- --shard=…` forwards the `--` literally, vitest DISCARDS everything after it, and so `--shard` and `--outputFile` were both silently dropped — each runner executed the entire suite (7m30s-9m54s per step) and wrote no report. Without the control this would have merged as four green checks that tested nothing verifiable. A subsequent adversarial audit found three further claims that measurement did not support, all fixed here: bounds hardcoded off a ten-day-stale suite size that would have tripped within weeks and blamed the wrong cause; a comment asserting post-`--` args become filename filters when they are discarded; and a "mutation-verified" claim that five of twelve mutants walked through. Four are now covered by named cases; the fifth was unreachable dead code and is removed rather than papered over with a test that could not kill it. Final sweep, re-run after the Prettier pass: 10 mutants, 10 killed, each by its own named test, with a no-op negative control that correctly survived. Suites 20/20. Verified live across two runs; the typecheck-scripts gate was confirmed to have executed rather than assumed green. Not verified: behaviour on a push to `main`, where `continue-on-error` is false and a red shard renders as a genuine failure. That path only exercises on merge. |
||
|
|
63d4a1efa7 |
feat(text-moderation): measure a text policy per surface, and let one surface run it (#4358)
* chore(gitignore): document what local/ is protecting The XGuard tuning harness lives there alongside the ticket agent, and it carries scanner policy prose and corpora pulled from production. This repo is public, so the entry is load-bearing rather than housekeeping — say so where someone might otherwise tidy it away. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(xguard-lab): decide SSL from the URL's sslmode, not the hostname `/xguard` returned a 500 with SELF_SIGNED_CERT_IN_CHAIN for anyone reaching the lab database through a local port-forward, which is the documented dev path for the cluster instance. SSL was decided from `isLocalHost(url)`, but a tunnel and a local docker Postgres are both `localhost` and need opposite answers — the tunnel needs SSL on with verification off, plain docker rejects SSL outright. The connection string already declared its sslmode; the code was not reading it. `labelConnectionString()` had the same bug by another route: eval-core and rate-core each open a bare `pg.Client` from it, bypassing the Kysely path, so the policy editor's Evaluate action failed the same way. Fixed at the shared helper. `moderator-db.ts` already hardcodes `sslNoVerify: true` against the same database, so two files pointing at one instance disagreed about its SSL and only one of them was right. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(xguard-lab): evaluate against the scanner the content is actually scanned by `scan()` hardcoded `mode: 'prompt'`, so a batch of model listings was scored against the prompt registry. Prompt and text are separate registries with separate label sets and separate thresholds, so the wrong mode does not fail — it returns a confident number for a scanner the content never passes through. Mode is now derived from `sample.source` rather than defaulted, because a default is silent and an operator clicking Evaluate has no reason to know the two exist. An unknown source throws naming the source; a batch mixing both throws rather than averaging two scanners into one precision. The resolved mode is stamped on the run note, since `eval_run` has no column for it and a run whose mode cannot be recovered cannot be compared against any other run. The Evaluate action in the policy editor passed no mode at all. Live baselines now require an explicit threshold instead of defaulting to one. A baseline exists to say what production does today, and production's threshold lives in the orchestrator registry — a default silently measures a different operating point and records it as "live". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(xguard-lab): make a label a judgement and a domain what it is read against Every rubric restated what kind of text it judges — the prompt ones that they judge prompts, the listing ones that they judge listings. A new surface meant another copy of that paragraph in every label. "Does this text assert a minor" is the same question whether the text is a prompt, a model listing, an article or a bounty. What differs is the shape of the input and the conventions of the people writing it. `DOMAINS` holds that, `rubric` holds the judgement, `domainNotes` holds a convention that applies to one surface only, and `rubricFor` composes them. A new surface is one `DOMAINS` entry rather than an edit to every rubric. The rater no longer carries a hardcoded idea of what it is reading — the domain comes from `sample.source`, for the same reason the scanner mode does. An unknown source throws. `domains` records where a label is meaningful, and asking for a pair that is not listed throws rather than quietly rating against the wrong preamble. The listing labels claim `modelListing` only: their bodies still name conventions specific to that surface, so claiming prompt reuse would assert something that has not been measured. Splitting those bodies into a neutral core plus `domainNotes` is the follow-up that makes the claim true. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(xguard-lab): sample and review corpora for any text-mode entity The lab could only sample generation prompts from ClickHouse, so preparing a batch for anything scanned in text mode meant hand-seeding rows. `sample-entities.ts` stratifies an entity's content across a label's score bands using scores `EntityModeration.result` already holds, so building a batch costs no model calls. Stratifying is the point: a label's scores are rarely spread evenly across live content and different labels skew in opposite directions, so a uniform sample of a skewed label says nothing about where its boundary sits. Listings with little or no description are kept as a bounded minority rather than excluded, since dropping them entirely would tune a label against a population it does not meet in production. Everything entity-shaped is in `ENTITY_TEXT`, which maps an entity to the table and columns its scanned string is built from. Each entry must match that entity's `resolveContent` in its production adapter, or reviewers judge text the scanner never sees. Challenge and WildcardSetCategory are absent deliberately — neither is a title+body pair, and inventing a resolver that merely looks right is the drift that comment warns about. `seed-from-predictions.ts` feeds the rows a policy fired on back into the review queue. Precision is TP/(TP+FP) and every term is a fired row, so when positives are a small minority, reviewing those measures it exactly while a random sample spends most of the effort on rows that cannot change the answer. It is documented as unusable for recall, since the rows it drops are the false negatives. Harness paths in the README and `import-policy.ts` follow the move to `local/`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(text-moderation): let one surface scan under its own label policy A tuned text policy had only one route to production: writing the global text registry, which Model, Article, Challenge and WildcardSetCategory all read. So raising a threshold measured on model listings also moved it for every other text consumer, none of which it was measured against. `createXGuardModerationRequest` already accepted and forwarded `labelOverrides` for both modes — only `submitTextModeration`, the entity-facing wrapper every adapter goes through, dropped it on the floor. It now takes and forwards them, and the override shape is an exported type rather than a third inline copy. Forwarding alone would have shipped a footgun. The dedup answers a request from an earlier workflow whenever the content hash matches, and that workflow was scored under whatever policy was in force at the time — so the first request under a new policy would have been answered with the old policy's verdict, for content that was never scanned under the new one. That is the same failure this branch has been fixing elsewhere: a confident number for a scan that did not run. The overrides now form part of the dedup key. Canonicalised rather than serialised as given, because a per-surface policy is meant to be the steady state: an equivalent policy in a different order has to keep deduping, or every save of an unchanged entity burns a scan. The suffix is empty when there are no overrides, so every hash written so far still matches and no entity is rescanned by this change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
5bf9ab1429 |
fix(stickers): wrap the placement tray into two scrollable rows (#4289)
* fix(stickers): let CSS own the placement tray's overflow (#868kut2bf) The tray's sticker row scrolled through `ScrollArea.Autosize`, which wraps its child in a `display:flex; overflow:auto` box whose `flex:1` inner box keeps the default `min-width:auto`. With a `wrap="nowrap"` row that inner box refuses to shrink to the panel, so it becomes a second scroll container and Mantine's own viewport measures a width larger than anything the user can see. Measured on a 500px viewport with 12 stickers owned: the panel is 474px wide and the scroll viewport came out 555px, so 81px of the track — and the last sticker — sat outside the panel's clip, and the thumb was sized 322px against a 954px row instead of 235px. That is the wrong-proportions half of the report. The missing-bar half is the same wrapper: `type="auto"` shows the bar only when its ResizeObserver produces a measurement, and on first mount it does not. Same geometry both times (555 < 954), `data-state=hidden` on first open and `visible` after a close and reopen — which is what the third reporter described. It is not image-load timing; the images were fully cached in both runs. A plain `overflow-x-auto` div has one scroll container, gets its width from the panel by construction, and needs no measurement, so both shapes go away. Verified after the change: scroller width 474 = panel width 474, bar present on first open, and the last sticker fully inside the panel at max scroll. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(stickers): guard the tray's single plain overflow container Structural, not layout: the browser project loads no stylesheet, so widths measured there are meaningless. Asserts the shape that broke — one `overflow-x-auto` ancestor, nothing scrolling between it and the clipping panel, and no Mantine ScrollArea anywhere in the tray. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(stickers): gate the tray's overflow shape in a suite CI actually runs The browser project runs in no CI job and loads no stylesheet, so the sibling browser test can neither gate nor measure. Same three structural assertions, in the unit project (happy-dom + createRoot), which does run on a PR. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: ignore vitest browser-mode failure artifacts A failing `*.browser.test.tsx` writes screenshots to .vitest-attachments/, which was untracked rather than ignored and got staged by a `git add -A`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(stickers): wrap the placement tray into two scrollable rows One long horizontal row is the wrong shape for a collection. Measured signed in as an account owning 83 stickers, it ran 6452px wide inside a 1254px panel, so almost everything owned was reachable only by dragging a scrollbar sideways. The tiles now wrap and the tray scrolls vertically, capped at two rows. The cap is derived from the tile height and gap rather than written as a pixel, so a taller tile cannot silently leave the second row half-shown. Also drops the tray's second note. "One a day, shared between stickers and remix galleries" sat under a line that already says whether this placement is free, and explained a limit nobody had asked about; it still appears on the remix-gallery modal and in the spent-allowance reason, where the reader is asking that question. And the price line no longer offers a choice that does not exist: the free placement is taken automatically by the first draft that can use it, so it now reads "Free · one use" or "<price> Buzz + one use", never both. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(stickers): say what the drafts line is actually for "Closing this panel leaves them on the image" answered a question nobody had asked yet, in the line that is supposed to say what to do next. And "buy the ones you want" describes a purchase; what the button does is pay to place one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(stickers): name both things a placement costs "Free · one use" and "50 Buzz + one use" both left "use" doing two jobs. A placement costs a placement AND a sticker use, so the line now says so: "Free placement + one sticker use" or "50 Buzz + one sticker use". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
481582d969 |
flake: own the dev toolchain, guard the pins, one command to a running app (#4107)
* feat(flake): make the Nix flake own the dev toolchain, and add one command to start The flake shipped nodejs_22 while package.json declares engines.node ">=24.0.0 <25", .nvmrc pins 24.19.0 and the Dockerfile builds production on node:24.19.0-alpine3.24. A NixOS developer was running a major the repo does not support, and nothing said so. Toolchain: - node and pnpm are now DERIVED from .nvmrc and package.json's packageManager rather than named twice. .nvmrc is treated as the authority because it is what every workflow's actions/setup-node reads and what the Dockerfile tracks. - flake.lock moved 2026-04-23 -> 2026-08-18 (117 days). At that rev nodejs_24 is exactly 24.19.0, which is what made agreeing with .nvmrc possible at all. - pnpm now comes from `pnpm_10`, not the unversioned `pkgs.pnpm`. At the new rev the unversioned attribute resolves to 11.21.0 -- a major bump that rewrites pnpm-lock.yaml -- so this bump would otherwise have shipped pnpm 11 to every dev shell silently. - postgresql_16 -> postgresql_17, matching the primary `db` container. The postgres/redis/clickhouse entries are CLIENTS for the compose-hosted servers; that is now stated in the file instead of left to be guessed. - npm_config_manage_package_manager_versions=false. Measured: without it, pnpm downloads and re-execs the exact version from the packageManager field, so the flake's pnpm pin was being defeated at runtime (`pnpm --version` returns 10.28.1 with the var unset, 10.34.5 with it set). Guards (`nix flake check`, 4 checks): - toolchain-pins: the flake's node must satisfy engines.node and equal .nvmrc, and its pnpm must share a major with packageManager. Deliberately does NOT re-check the .nvmrc/Dockerfile/engines triangle -- node-version-consistency.test.ts already owns that, and a predicate open-coded twice starts disagreeing. - prisma-pin: re-derives the resolved @prisma/client AND its engine commit from pnpm-lock.yaml and compares them to the values flake.nix hardcodes. These were correct but unguarded: package.json declares `^6.3.0`, a caret range, so a routine lockfile refresh moves the client while the flake's engines stay put, and the failure surfaces at runtime in every dev shell. - pin-guards-selftest: breaks each pin on purpose and requires the guard that owns it to fire while the others stay silent. - dev-scripts: builds the shell entrypoints, which is what runs their shellcheck. (`nix flake check` builds checks.* but only EVALUATES packages.*, measured.) Entrypoints: - `nix run .#dev` - docker preflight, submodule, .env.development, compose up, wait for postgres, pnpm install, then `next dev`. Every step idempotent and non-destructive; migrations and seeding stay opt-in. - `nix run .#dev-server` - runs the dev-server CLI on the flake's node. The daemon re-execs itself with process.execPath, so whichever node starts the CLI is the node it runs on until it is restarted. - `nix run .#doctor` - the same pin checks against the working tree. Compose project is pinned to `civitai` so every worktree shares the one local stack instead of each spawning a duplicate that fails on the port binds. * fix(flake): give `nix run` the same env as the dev shell, not just the shell Found by running the bootstrap on a genuinely clean worktree rather than reasoning about it. `mkShell`'s `env` applies to `nix develop` only, so both values it carried were absent from `nix run .#dev`: - `pnpm install`'s postinstall runs `prisma generate`. Without PRISMA_QUERY_ENGINE_LIBRARY et al, prisma tried to fetch an engine for platform `linux-nixos` and the bootstrap died on `404 ... /linux-nixos/libquery_engine.so.node.sha256`. - pnpm re-execed itself as 10.28.1 from the packageManager field even though PATH pointed at the flake's 10.34.5, so the app reported a pnpm the flake had not pinned. The env is now one attrset (`devEnv`) rendered two ways: `env` for the shell and an `export` preamble for the apps, so they cannot drift. `nix run .#dev-server` gets it too -- the daemon runs `pnpm install` / `db:generate` on its own when it sees the lockfile move, which would have hit the identical 404. * docs: describe the toolchain the repo actually has, not the one it used to Every claim below was checked against the code before rewriting, and the measurements are quoted where they are load-bearing. README.md - "Node.js (version 20 or later)" -> 24.19.0, with .nvmrc named as the authority. - `make init` was DEAD, not merely awkward: it ran `npm i`, and package.json's `preinstall` runs `only-allow pnpm`, which exits 1 under an npm user agent (measured, with the pnpm-user-agent control exiting 0). Both bootstrap paths the README offered went through it. - MinIO console is on :9001, not :9000 (:9000 is the S3 API). The instructions sent people to the wrong port to mint the keys the next step needs. - `git submodule update --recursive` -> `--init`; without `--init` it is a no-op on a fresh clone, which is precisely when it is being run. - Data Migrations step 1 pointed at `schema.prisma`, which is gitignored and regenerated from `schema.full.prisma` on every `db:generate`, so edits to it were silently discarded. - Adds the Nix path (`nix run .#dev`) and a real non-Nix sequence. - engines.node is ADVISORY, stated plainly: pnpm 10.34.5 under node 26.7.0 against ">=24.0.0 <25" prints `WARN Unsupported engine` and exits 0. An earlier draft of this very README claimed it refuses. It does not, and that is the reason the drift survived so long. Makefile - `npm i` -> `pnpm install` (see above). `npm-install` kept as an alias. - `gen-prisma` ran a bare `prisma generate`, which reads the gitignored slim schema that does not exist yet on a fresh clone; now `pnpm run db:generate`, which generates it first. - `dev` ran bare `cross-env`/`next`, requiring the caller to put node_modules/.bin on PATH by hand; now via `pnpm exec`. - `docker-compose` (EOL v1) -> `docker compose`. - COMPOSE_PROJECT_NAME pinned to `civitai`. Reproduced first: `make start` in a worktree died with `Bind for :::15434 failed: port is already allocated` because compose named the project after the directory. .envrc.example (new, tracked) + .gitignore - `.env*` matched `.envrc` too, so nothing tracked in the repo mentioned the flake at all -- the only reference was a line in CLAUDE.md filed under worktree hygiene. Placeholders only; the real .envrc stays ignored. .claude/skills/dev-server/SKILL.md - The skill said nothing about node. The daemon is spawned with `process.execPath` (cli.mjs:66, console.mjs:87) and hands its env to every `next dev` it supervises, so the first shell to run a CLI verb decides the node for everything, indefinitely. Measured on this box: daemon on 26.7.0, with no pnpm on PATH at all. Documents `nix run .#dev-server` and how to check. - `npm run dev:daemon` -> `pnpm run dev:daemon`, in a repo that bans npm. src/__tests__/node-version-consistency.test.ts - Comment-only. It said flake.nix "is on a different major" and could not be aligned because the pinned nixpkgs had no Node 24 this new. Both halves are now false, and a comment a maintainer might act on is worth correcting. Also: docs/pnpm-migration.md's "Node.js 18.x or later"; the generated-header line in scripts/generate-slim-schema.js telling readers to run `npm run db:generate`; CLAUDE.md's local-dev section (no node version, no services) and its stale "flake's 22.22.2" figure. NOT changed, because it could not be exercised here: the devcontainer pins typescript-node:1-22 (Node 22, outside engines.node). Flagged in README with the tag to use -- there is no `1-24`, the template major moved on, so `3-24`. * docs(flake): the four postgres containers are not all one version prisma-pit and db are postgres 17; notification-db and logical-db are 15. The comment justifying postgresql_17 read as though they were uniform, which would have made the next person's version decision from the wrong premise. * docs: keep the non-Nix path the default, demote the flake to optional The flake is used by one maintainer. Everyone else uses Docker + nvm, and that has to stay the path a contributor lands on. The previous revision inverted that: README's Installation section led with "With Nix (recommended...)" and titled the standard path "Without Nix" — framing the majority workflow as the fallback. CLAUDE.md opened "From nothing to a running app, one command:" with `nix run .#dev`, and the dev-server skill led its fix with "Start it through the flake and this cannot happen". None of that made Nix *required* — verified: `.github/` is untouched by this branch, no workflow references Nix (the apparent hits are substrings of `eslint-unix.json` and `--format unix`), and `nix flake check` is not wired to any CI gate. It was purely an ordering-and-emphasis problem, which is the kind that costs a new contributor twenty minutes before they find the section that applies to them. Changes, all editorial: - README: `#### Standard setup` now precedes `#### Optional: Nix flake`, and the Nix section opens with a blockquote saying it is not the supported default, that nothing requires it, and why it exists at all (NixOS has no published `linux-nixos` Prisma engine, so a flake is the practical way to work there). The signals/buzz instructions lead with `docker compose up -d` and mention `nix run .#dev -- --full` parenthetically. - CLAUDE.md: the bootstrap block is now the nvm/docker sequence, labelled as the default path, with the flake shown after it as NixOS-only and explicitly flagged as something not to assume a contributor has. The dev-server step no longer instructs going through `nix run .#dev-server`; it states the requirement (a shell whose node matches `.nvmrc`) and notes the flake does that for you on NixOS. - dev-server SKILL.md: the fix is now stated setup-agnostically — start the daemon from a shell whose node matches `.nvmrc` with pnpm on PATH, which `nvm use` gives you — with the flake wrapper presented as the optional NixOS convenience, and an explicit note that nothing in the document depends on Nix. No behaviour, tooling or gate changes: the Makefile, flake, guards and their tests are untouched by this commit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
93ee73cd84 |
test(perf): instrument the unit suite and track the isolation migration (#3957)
* test(perf): instrument the unit suite and track the isolation migration
The unit suite spends 81% of its worker time importing, and nothing in the repo
could say which modules or which files. This adds the measurement that answers it,
plus a dashboard so the isolation migration has a burn-down nobody has to maintain.
The finding that motivated the shape of it: traced at 1 worker, the module BODIES
of two of the heaviest test files total ~0.4s against a 25.4s import phase. The
cost is vite-node's per-module fetch, which under the `forks` pool is a
child-process IPC round trip per module per file. So cost is linear in module
COUNT, not module weight, and the static import graph is the right thing to rank by.
- graph.mjs static first-party import graph + vi.mock inventory
- reporter.mjs per-file collect/setup/test timings from any run
- bench.mjs fixed 90-file stratified yardstick, so two measurements taken
hours apart by different people are comparable
- sweep.mjs pool x isolation x worker-count matrix, run back to back
- trace-* module-execution tracer; counts what actually ran, which the
static graph cannot know (a vi.mock factory stops the real
module and its subtree from executing)
- why.mjs shortest import path between two modules
- dashboard.mjs builds .test-perf/dashboard.html from whatever is on disk
Output goes to .test-perf/, gitignored. Nothing here runs in CI or changes how
any suite executes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test(perf): add the per-worker union report, and record three dead ends
Under isolate:false a worker keeps one module registry, so its cost is the UNION
of what its files import rather than the sum. order.mjs reports that union, which
is the number that bounds what removing isolation can deliver: measured at ~36
seconds per worker to build, near-constant at 8 and at 24 workers, and a wall-clock
floor more workers cannot shrink.
Three things measured today that do NOT help, written down so the next person does
not pay for them again:
- Affinity file ordering. A greedy graph-similarity sequencer gave a mean per-worker
union of 1139 modules at 31 workers against alphabetical's 1084 - slightly worse.
Alphabetical already groups by directory and directory already correlates with the
graph. The sequencer is deleted; the measurement is kept.
- NODE_COMPILE_CACHE. Cold 26.8s, warm 51.4s, warm again 33.9s on the yardstick. The
cache filled (5.3MB) so it was active; vite-node does not evaluate through the
loader it covers.
- vmThreads. Two clean 90-file runs, then the five sharp-executing files crashed or
passed on identical input at 2 and 3 workers. It is a race, and CI's 4 vCPU
resolves to the width measured at 1-in-3 SIGSEGV.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs(test-perf): three measurement rules this project had to learn twice
- The 90-file yardstick understates isolate:false and cannot judge it. That flag
amortises the registry build across the files a worker runs, so its win scales
with files-per-worker: 90 files at 16 workers is ~6 each and measures 1.65x,
while 1065 files at 8 workers is ~133 each and measures 16x on the same phase.
- No --no-isolate number is quotable without a per-file collected count. Its damage
is not only failing assertions: files silently collect ZERO tests, and how many is
width-dependent (9 of 90 at forks/4, 14 of 90 at threads/4, 0 at threads/16, same
input). A summary line cannot show this.
- The noise floor on a shared box is +/-30%: one configuration measured 53.3s and
76.6s in a single session. Below ~20%, quote phase numbers or nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test(perf): show the guard allowlist as the migration burn-down
The dashboard was deriving migration status from the static vi.mock inventory,
which is an estimate. The authoritative number is the length of the guard's
allowlist: it ratchets in both directions, so a new direct mock fails and a
migrated file left on the list fails too, and it therefore cannot drift from what
the suite will actually accept.
Reads it from the working tree when present, otherwise from the branch carrying
it, so a dashboard built on main still shows the real number rather than nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs(test-perf): name the baseline, retract the IPC mechanism, fix the yardstick's promise
Three defects found by an adversarial review of this PR, all in the class of
asserting something nobody re-checked.
- The README quoted one baseline while shipping an artifact with different
numbers for the same tree - 4565.5s against 5476.3s of import, 36% apart. The
tree did not change; the box did. All three measurements of `3863adcbb0` are
now tabulated with the rule that a main-relative figure must name the run it
was measured against, because that spread is the same size as several of the
effects being measured against it. The derived import share is 81% or 84.3%
depending which row you pick, and both are now stated.
- The per-module IPC mechanism was retracted in mail hours ago and left standing
here. The pool sweep refutes it: `threads` beat `vmThreads` while paying a cold
fetch, which shipping module source cannot explain. Now labelled inferred, with
the competing reading and the note that V8 compile time was never instrumented.
- The yardstick claimed it made measurements "hours apart" comparable, three
bullets above a +/-30% noise floor measured inside one session. It fixes what is
measured, not when. Also records that a null from a 90-of-1065 sample is not
evidence of no effect - two changes measured flat there were later shown real.
And the gitignore claim is now scoped to "by this change", since it is false on
main until this merges.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(test-perf): count the modules a worker loads, not the ones a bundler compiles
The static graph over-counted a page-gate test by ~75x, which put four of the
cheapest files in the suite at the top of the closure ranking. Their measured
worker time ranks 202-572 of 1065.
Four independent causes, each removing modules the naive walk counted:
- lazy `import()` is not followed, EXCEPT from a test file itself. A
`dynamic(() => import())` in a page never runs; an `await import()` in a
test body is what the test exists to do. Collapsing the two makes those
files either the top of the ranking or 1 module each.
- a `vi.mock` factory without `importOriginal` truncates the subtree behind
it. The mocked module itself is still counted -- registering the mock is
what causes the transform -- and is counted even when nothing imports it.
- `import { type X } from` erases the statement. Only `import type {` was
handled, and the inline form is the common shape here.
- a line filter cannot strip a multi-line `import type`: it leaves
`} from '...'` behind, and IMPORT_RE's lazy `[\s\S]*?` glues that orphan
onto the previous import, inventing an edge. Stripped as statements now.
Plus `event-engine-common` in SRC_DIRS -- a submodule imported by relative
path from src/server/services, so its modules and the civitai-db-queries
files reachable only through it were invisible.
Validated by diffing the model against a transform-hook trace as SETS, not
counts: 3 of 5 files exact with empty diffs both ways, 16 modules of
symmetric error across 691 traced (2.32%). Counts alone agreed often enough
to hide two of the four causes -- a count agreeing is not the rule agreeing.
`graphModules` is now the honest count and stays the default field, so
dashboard.mjs is corrected without a change of its own; `graphModulesRaw`
keeps the bundler view. closures.json gains `mode: 'real'` and a note
describing the truncation rules, so a consumer can refuse a naive one.
Also flags the two order.mjs union figures as pre-honest levels needing a
re-run; the ordering conclusion is a ratio and survives.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(test-perf): stop the type-statement stripper eating real code, and record maxWorkers honestly
Three review findings, all mine.
1. graph.mjs -- the type-statement stripper swallowed spans of real code.
`TYPE_STATEMENT_RE` also started matching on a bodyless `export type X = ...`
(no `from`), and the lazy `[\s\S]*?from '...'` then scanned FORWARD across the
file to the next `from '...'`-shaped text -- inside comments and strings
included -- deleting every statement in between before IMPORT_RE saw them.
src/server/common/enums.ts lost a real `@civitai/notifications/constants`
edge across a 7,881-character span.
The gap is now tempered: between `type` and `from` a real type-import
statement holds only a binding clause, so it may not cross `;`, `=`, or
another `import`/`export` keyword. Measured across 5,294 files, spans of
more than 12 lines fall from 73 to 28, and every one of the 28 remaining is
a genuinely long multi-line type-import list. Re-validated against the
transform-hook traces: symmetric error 2.32% -> 2.03% over 691 traced
modules, so the fix is strictly in the right direction.
Direction of the bug was UNDER-count, and `graphModules` is the ranking key
for the dashboard, order.mjs and bench.mjs --make-subset. The earlier
validation did not cover it: 5 files of 1,065, none of the affected ones.
2. reporter.mjs -- the one unguarded filesystem call. It sits inside an awaited
vitest lifecycle hook, so a throw (read-only cwd, `.test-perf` existing as a
file, ENOSPC, an AV EPERM) would red a run whose tests all passed, and the
failure would be attributed to the code under test. A measurement tool must
never be able to fail a suite; losing the recording is the correct trade.
3. reporter.mjs -- `maxWorkers` is not on vitest 4's `ctx.config`. Verified by
execution: the emitted config was `{isolate, pool, argv}`. Every run ever
recorded therefore stored null, and dashboard.mjs rendered all of them as
"(default) workers" -- claiming a fact nobody measured. Recovered from argv
(`--max-workers=8`, `--max-workers 8`, `--maxWorkers=8`), falling back to
VITEST_MAX_WORKERS, and reported as 'unknown' rather than null when neither
is present. The env var and the flag are recorded separately because they do
not behave the same -- the flag reaches a queued run and the env var does
not. The dashboard now shows historical nulls as 'unrecorded', not
'default'.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
c9b492e8b5 |
feat(skills): add feature-walkthrough, for demoing a feature you cannot demo live (#3823)
* feat(skills): add feature-walkthrough, for demoing a feature you cannot demo live Turning a finished feature into screenshots a reviewer can read is repeatable work, and most of the cost is rediscovering the same friction: the browser-automation server discards a chunk's return value, so an inspection script cannot report what it saw; a sticky announcement sits above the page and swallows clicks, failing 30s later with "subtree intercepts pointer events", which reads like a bad selector; artifacts run under a CSP that blocks every external host, so screenshots have to be inlined as data URIs under a 16 MB ceiling; and captures have a dependency order, because a pending state has to be shot before it is accepted. `capture.mjs` solves the first two in code — it keeps the return value and injects `dismissOverlays`, `shot`, `shotOf`, `shotModal`, `mainText`, `modalText`. `build-report.mjs` inlines the images and fails the build on a missing one or an oversized page, rather than letting a rejected publish or a silently absent screenshot be the first sign of trouble. The skill leans on dev-server, browser-automation and postgres-query rather than restating them, and deliberately does not carry a sign-in-as-any-user recipe. Its `.env` is gitignored with an `.env.example` to copy, matching postgres-query. It also states the rule that makes the whole thing safe: seeding and restoring write, so this runs against a dev database only, and screenshots leave with whatever is on screen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(skills): keep walkthrough screenshots out of a public checkout `SHOTS_DIR` defaulted to a relative `shots`, so running a capture from the repo root wrote screenshots of dev accounts — usernames, avatars, their content — into a public, permanent checkout, one `git add .` away from being published. The skill said to use a scratch directory while shipping a default that did the opposite. The default is now a temp directory outside the repo, and `/shots/` plus `**/walkthrough-shots/` are gitignored as a backstop for anyone who points SHOTS_DIR somewhere convenient instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
ee5e88f440 | gitignore | ||
|
|
902d222ed1 |
ci: run the nine packages/* test suites, which nothing has ever executed (#3642)
* ci: run the nine packages/* test suites, which nothing has ever executed The root `unit` Vitest project's `include` is root-relative (`src/**`, `scripts/**`), and the `Unit tests` job runs `vitest run --project unit`. So no CI job in this repo has ever invoked a workspace package's suite: 616 tests across nine `packages/*` packages ran only for whoever remembered `pnpm --filter <pkg> test` by hand. That is not theoretical. The schema-drift detector (#3591) shipped 81 tests into this gap, and two of them had been RED on `main` since #3592 landed — with nothing anywhere to say so. Root `vitest.config.mts` now globs each package's OWN config file, so every package keeps the config it was written against. The new `Package unit tests` job is BLOCKING: the reasoning that made `Unit tests` report-only does not transfer, because this is 616 tests with no browser, no database and no Next module graph, and the whole run is ~15s. Three pre-existing failures the job surfaced, all fixed here rather than skipped: * civitai-db-queries/src/infra/enum-array-parsers.explain.test.ts could never pass without a database. `describe.skipIf(!url)` skips the tests but Vitest still EXECUTES the describe callback during collection, so the eager `new Pool({ connectionString: noVerify(undefined) })` threw `TypeError: Invalid URL` and failed the whole FILE to import — which reports as "1 failed test file" with no test count, not as a failing test. The Pool moves into `beforeAll`, which genuinely does not run when skipped. * production-snapshot.test.ts pinned 235 nullability findings on seven `*Rank` tables. #3592 marked those columns optional to match the database and the findings correctly went away. The assertion now guards the REMEDIATION (Rank family at zero) with a positive control on that zero — the 11 findings that remain. * parse-prisma-schema.test.ts pinned Prisma's defaulted `onDelete` at one named site, `TagsOnImageNew.image`, and #3589 gave that relation an explicit `onDelete`. Re-anchored to the POPULATION of bare relations, which an ordinary schema edit cannot retire. A green vitest run is a claim, not evidence: `--project` matching nothing exits 0, and so does a config whose globs stopped resolving. scripts/ci/assert- package-suites-ran.mjs therefore asserts a ledger — every package with a vitest config and a test file on disk must appear in the results — so the job fails when the executed set SHRINKS, which the totals cannot see. Validated against three known-bad reports (empty run, one package dropped, missing report file) before being trusted. Test count, measured in CI: 811 files / 12,031 tests before, all from `src/`; 61 files / 616 tests added by the new job, of which 81 are the drift detector's. * fix(ci): correct the "blocking" claim, and close three ledger blind spots Audit follow-ups on the packages/* CI job. The job was described as BLOCKING. It is not, and the distinction is load-bearing for whoever next decides whether to flip `unit`: `main` has branch protection but no required_status_checks at all, so a red check here does not prevent a merge. What it actually buys is rendering RED instead of red-but-ignored. Corrected in the workflow comment, the README and the PR body, along with what would make it a real interlock. The Rank positive control was green for the wrong reason. Asserting the Rank family is at zero nullability findings is satisfied just as well by a differ that never visits a Rank table — measured: making compare.ts `continue` on every model whose table ends in `Rank` left that test GREEN and reddened only two neighbouring ones. The `nullability.length >= 11` control could not see it, because it proves the KIND is still emitted, not that those seven tables are in scope. Now pinned via findings of a different kind that the same seven models must still produce; re-running the identical mutation kills the test itself. Three ledger blind spots, each demonstrated with a probe before and after: - It only looked in `src/`. A package with its tests in a top-level `__tests__/` was invisible to the EXPECTED set, so it could never be noticed going missing. Now scans the whole package directory. - It only matched `.test.ts`/`.spec.tsx`. `.mts`, `.cts` and `.js` tests were invisible the same way. - It counted SKIPPED tests as having run, summing assertionResults.length. A package that self-skipped in its entirety still satisfied the ledger — and self-skipping on a missing DATABASE_URL is exactly the pattern these suites use, so that failure mode is live, not hypothetical. It now requires a non-zero EXECUTED count and reports skips beside it, so a package quietly turning itself off is visible rather than absent from the arithmetic. Both directions verified by exit code, not by reading output: real report 0; tests-outside-src probe 1; .mts probe 1; wholly-skipped package 1; empty run 1; one package dropped 1; missing report 1. Known remaining gap, deliberately not widened into this change: `apps/*` has four more vitest configs and ~43 test files that no CI job runs. Same one-line fix in the same projects array, plus teaching the ledger about `apps/` — it hardcodes `packages/` today. Follow-up PR. Also: the CI job's JSON report is gitignored (the documented command left an untracked artifact at the repo root), and the workflow comment's "four EXPLAIN tests" is corrected to 3 + 1 across two files. |
||
|
|
0ddd82b64a |
fix(challenge): escalate NSFW-text challenges — cancel green, raise R on yellow (#3211)
* docs(challenge): spec for NSFW text-scan escalation + green→yellow flip Design for escalating a user challenge to R and flipping green→yellow when its text scans as sexual content, plus currency-scoping the initial-prize externalTransactionId to prevent a silent-drop unfunded-pool bug on re-charge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(challenge): implementation plan for NSFW scan escalation + flip Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(challenge): currency-scope initial-prize externalTransactionId Refunding a charge leaves its externalTransactionId occupied in the ledger, so a later yellow re-charge on the shared green id would be silently dropped by the createBuzzTransaction dedup, leaving an unfunded pool. Suffix the id with the currency; the -creator prefix matchers still match both variants. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(challenge): scan text for suggestive+explicit, not nsfw only The nsfw label (threshold 0.75) misses crude sexual themes; suggestive/explicit (threshold 0.5) catch them with a large margin. Centralize the label set so the adapter submit and scanUserChallenge stay in sync. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(challenge): add computeNsfwEscalation pure decision Given a scanned challenge, decides the raised allowed/display nsfw levels and whether a green user challenge flips to yellow + refunds its green initial prize. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(challenge): applyChallengeNsfwEscalation IO helper Applies a scan verdict: marks Scanned, raises to R, flips green->yellow, refunds the green initial prize (refund-before-update for crash safety), updates the collection browsing level, and notifies the creator. Idempotent on retry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(challenge): delegate scan applyResult to escalation helper The adapter now keeps only the Blocked path; clean/NSFW verdicts route through applyChallengeNsfwEscalation, treating any triggered label as NSFW. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(challenge): broaden flip refund prefix + harden collection update - Flip refund uses the -creator prefix so it also matches pre-deploy charge ids (-creator, no currency suffix); the narrow -creator-green prefix missed them and stranded the creator's escrow while zeroing the pool. - collection.updateMany instead of update so a deleted collection no-ops rather than poison-pilling webhook retries with P2025. - Refresh the adapter's stale top-of-file comment for the delegated flip. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(challenge): pivot design from green→yellow flip to cancel/void Cancelling a green NSFW challenge (via the existing race-safe voidChallenge) instead of flipping it to yellow reuses battle-tested code and eliminates the currency-migration edge cases. Yellow challenges still raise to R. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(challenge): cancel green NSFW challenges instead of flipping to yellow Green user challenges whose text scans NSFW are now voided (Cancelled + collection closed + initial prize refunded via the race-safe voidChallenge) and the creator is notified to recreate on civitai.red. Yellow challenges still raise to R and stay live. Replaces the buzzType-flip + green-prize-refund path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs+test(challenge): refresh adapter comment for cancel; add already-R escalation case Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(challenge): correct cancel-path ordering to void-before-Scanned Match the spec to the implemented (crash-safe) order: void first so a crash leaves the challenge Cancelled/hidden, never Scanned-and-visible. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(challenge): guard blocked-path against deleted challenge + align stale docs - adapter blocked path: early-return when the challenge was deleted between scan submit and the webhook, instead of a bare update that throws P2025 and fails the moderation callback (restores pre-refactor behavior). - spec title + plan header: flag the flip→cancel pivot so the docs don't read as though the code is wrong. Addresses Copilot PR review feedback. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(challenge): scan nsfw label only (suggestive/explicit not yet reliable) Only the nsfw XGuard label is currently trustworthy, so scan for it alone. Trade-off: nsfw's 0.75 threshold misses borderline sexual text (~0.68) — only clearly-NSFW text escalates until suggestive/explicit are reliable. Escalation logic is label-agnostic; this is a one-const change. * docs(challenge): document text-scan NSFW handling in feature doc; drop session spec/plan Add a Text moderation bullet to the challenge-platform Safety/gating section (green NSFW → void+refund, yellow → raise to R, nsfw-label-only interim). Remove the superpowers/ spec + plan — implementation scratch that doesn't belong in main. * docs: remove superpowers/specs scratch (article-rating #2779, public-challenges #2965) Implementation-detail specs that don't belong in main; removed per maintainer request alongside this PR's own doc cleanup. * chore: gitignore docs/superpowers/ (skill scratch, not shipped to main) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
f07c11fc0d |
feat(challenges): open platform to public user-created challenges (dark) (#2965)
Opens challenge creation to regular users (was mod/system only). Behind userChallenges Flipt kill-switch (availability mod), platform behind challenge-platform-enabled flag.
Schema
- New Challenge cols: entryFee, maxParticipants, judgingCategories, ingestion (scan status), buzzType, operationSpent
- createdById → nullable, FK ON DELETE SET NULL (user challenge survives owner deletion)
- New: ChallengeIngestionStatus enum, ChallengeReport join, ReportEntity.Challenge, ChallengeCategory table (DB-backed judging-category library)
- Migrations manual-apply per env
Core systems
Eligibility — creator score ≥5000, good standing, tier active-challenge caps (free1/bronze2/silver3/gold5), daily create limit 5, can't-enter-own guard.
Funding (buzz) — creator escrows initial prize; entry fee split into non-refundable house cut (25) + refundable pool contribution; per-image idempotent charge; ledger-verified partial charges self-heal on retry. Winners paid from final pool in challenge's stored buzzType.
Judging — weighted category system (theme mandatory + gated, weights sum=100, max 4). Curated DB category library (no free-text → injection-safe). Rich rubrics live DB-only (ChallengeCategory.rubric), NOT in repo (public-repo leak fix). {{SCORING_RUBRICS}} sentinel injects at review time. Entry-fee challenges judge every entry; dailies keep sampling.
Moderation — async text scan (title/theme/invitation/desc) via XGuard; POI + cover-scan gates; visibility gate hides non-Scanned from non-owners; challenge Report → mod queue.
NSFW/domain isolation — green (.com) vs yellow (.red) buzz currency, domain-locked. Green forces SFW. Feed/detail/winners gate on REAL cover nsfwLevel (not declared). Soft-gate via <Gated> MatureContentRedirect.
Prod-readiness (Phase from PR #3107)
Job scalability (bounded limitConcurrency, batch sizes, review rotation by reviewedAt, no silent LIMIT 50 drop), concurrency-safe activation/void/delete (conditional writes, claim-before-refund), LLM spend tracking (operationSpent vs house cut → Axiom metric), off-resource fee-leak fix, <2-entrant winner-pick skip.
UI
Unified ChallengeUpsertForm (variant moderator|user), /challenges/create + eligibility requirements card, feed split (Daily row + Community masonry), owner Edit/Delete context menu, My Challenges view, Create-a-Challenge in global CreateMenu w/ NEW badge.
Notable review-driven fixes
Buzz-mint vectors closed (unfunded entry, edit-path escrow bypass), negative-input floors, winner mapping by creatorId (name-spoof hardening), refund-by-internal-txn-id, judge-prompt injection preamble.
|
||
|
|
bbf8c2b1e5 |
chore: gitignore nested SvelteKit .svelte-kit output (apps/*)
Creator-studio branch work leaves generated .svelte-kit/ behind when switching back to main, showing as untracked noise for everyone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
05c0bff7e0 |
chore: ignore nested coverage dirs + commit notifications coverage audit (#2935)
The root .gitignore had `/coverage` (root-anchored), so per-package coverage output like apps/notifications/coverage/ (left by a vitest --coverage run) showed as untracked and dirtied the tree. Broaden to `coverage/` so any workspace package's coverage output is ignored (root coverage still ignored too). Also commit the notifications test-coverage audit that drove the recent worker / create.ts / markNotificationsRead behavioral-test PRs (#2925/#2926/#2927), into claudedocs/ (already a tracked docs dir). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
74e690ac8b |
fix(cls): reduce model page layout shift
- Carousel: reserve each slide's aspect-ratio box up front (EmblaSlide render-prop) so the container height is correct from first paint instead of snapping off Embla's initialHeight; SSR-prefetch the carousel images so it has real dimensions. - Creator card: SSR-prefetch user.getCreator (hydrate real data, no zeroed-fallback growth) and render the default background as a base layer so it doesn't flash blank while a cosmetic loads. - Donation goals: render nothing while loading/empty instead of a skeleton that collapses (the common case). - Action buttons: explicit flex/flex-1 children instead of Mantine `grow` (no fill-then-shrink). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
3d5934b9b6 |
Merge main into monorepo-bootstrap (231 commits)
Reconcile main with the package-extraction branch. 8 conflicts resolved by keeping the @civitai/* package shims and porting main's NEW logic into the packages (not the app shims): - prisma enums/models: re-export shims; regenerated from merged schema (Model3D*) - package.json/pnpm-lock: union scripts; @civitai/client beta.71->73, +three - @civitai/axiom: logToAxiom stderr-before-guards reordering (test relocated here) - @civitai/telemetry: redisSelfHealReconnect + redisMetricWriteFailSoft counters - clickhouse tracker.ts: Tracker.view() context fields + new blockRender() - @civitai/redis: ported main's cluster self-heal + packed-compression subsystem (cluster-selfheal/inflight/deadline-hits/packed-compression moved into the package, deadline.ts + 8 REDIS_CLUSTER_SELFHEAL_* env vars wired, metric bridge) - search services: dropped next-auth import -> ~/types/session SessionUser Verified: typecheck 0 errors (app+packages); redis(53)/axiom(3)/app redis+logging(78) tests pass; two independent review agents confirmed no lost main functionality and a faithful (9/9 invariants) redis-resilience port. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
b22c0f1f96 |
chore: gitignore hub e2e playwright artifacts
apps/auth/e2e generates a playwright-report/, test-results/, and minted storage states under .auth/ — generated/secret-ish, not source. Ignore them. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
f4d5e501ce | Merge branch 'main' into hackaton/3d-model-support | ||
|
|
552a53e27c |
Merge origin/main into monorepo-bootstrap
Brings in main's App Blocks feature (+ @civitai/app-sdk, @civitai/blocks-react). Conflicts resolved: - package.json: keep @civitai/auth (workspace) + main's @civitai/app-sdk + @civitai/blocks-react - .github/workflows/pr-check.yml: accept main's deletion - src/shared/utils/prisma/models.ts: keep our @civitai/db-schema re-export shim - db-helpers.ts / redis/client.ts / prom/client.ts: keep our package-extracted shims (ours) - oauth/authorize.ts: keep both imports (OIDC nonce + app-block client check) - pnpm-lock.yaml: reconciled via pnpm install Ported main's App Blocks infra additions INTO our extracted packages so the incoming code compiles against the package architecture: - @civitai/redis: REDIS_KEYS.BLOCKS + REDIS_SYS_KEYS.BLOCKS keys; withRedisCommandTimeout (app shim) - @civitai/db: 'apps' pool instance + APPS_DATABASE_URL/appsUrl - @civitai/telemetry: App Blocks metrics (blockBuzzAttribution, appStorageOps/QuotaExceeded/Latency) Typecheck: 0 errors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
e29a606955 |
test(component): Vitest browser-mode component-testing scaffold (runs in Tekton, removes GH Actions pr-check) (#2547)
* test(component): add Vitest browser-mode component-testing scaffold + SeedInput Adds a second Vitest project (`component`, browser mode / real Chromium via Playwright) alongside the unchanged 857-test `unit` suite. Includes a renderWithProviders scaffold (Mantine + QueryClient + next/router mock), a process.env shim for browser mode, a report-only GH Actions `component-tests` job, and the first test on the high-churn, e2e-impossible SeedInput generation leaf (6 cases, mutation-proven). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(component): address audit findings (typecheck test/, seed-test teeth, optimizeDeps) - tsconfig: add `test` to include so the load-bearing browser-process-shim + component-setup are actually typechecked (were only checked transitively/not at all). (audit H1) - SeedInput test: stub Math.random for an exact-value assertion bounded by MAX_RANDOM_SEED (was a loose MAX_SEED range that survived a constant-seed mutation). (audit M1) - vitest component project: optimizeDeps.include next/router to stop the "Vite unexpectedly reloaded a test" flake warning. (audit L4) - drop the stale "857 tests" count from the config comment. (audit M2) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci: remove GitHub Actions pr-check.yml — consolidate PR checks onto Tekton typecheck + unit-tests already run in the Tekton PR-preview pipeline (author- gated on MEMBER/OWNER/COLLABORATOR), and component-tests now run there too (talos-infra: report-only npm-component-tests task). Removing this workflow stops paying for GitHub Actions runner time. Tradeoff: external-contributor PRs (not author-authorized) no longer get automated checks — accepted per the "pure Tekton" decision. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
cca249e517 |
ci(lighthouse): report-only Lighthouse CI on PR previews (Tekton) (#2516)
* ci(lighthouse): report-only Lighthouse CI on PR previews (Tekton) Adds the civitai-side config for a new report-only `lighthouse` task in the datapacket-talos pr-preview pipeline. On each preview build it runs Lighthouse (5 runs, desktop, median-aggregated) against the deployed preview at pr-<N>.civitaic.com and posts a report-only PR comment with perf/a11y scores + LCP/CLS/TBT/INP and a public report link. - lighthouserc.json: 3 routes (/ /models /generate), numberOfRuns 5, desktop preset, --no-sandbox, temporary-public-storage upload. All assertions are `warn` (report-only) — promotion path documented (deterministic CLS/byte budgets to `error` first, noisy timing metrics last, after a soak). - tests/lighthouse-mint-cookie.cjs: mints the gate-passing ci-smoke-gold NextAuth session cookie (same next-auth/jwt encode + NEXTAUTH_SECRET as tests/preview-auth.setup.ts) and injects it into collect.settings.extraHeaders -> lighthouserc.runtime.json, so headless Chrome clears the preview-auth middleware /login gate instead of measuring the login page. Report-only first; structured to flip to gating later. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci: retrigger preview for lighthouse upload fix * ci: retrigger preview for lighthouse manifest fix --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ee89847977 |
Merge remote-tracking branch 'origin/main' into monorepo-bootstrap
# Conflicts: # .gitignore # src/server/prom/client.ts |
||
|
|
513e64d348 |
Merge branch 'main' into hackaton/3d-model-support
# Conflicts: # .gitignore # package.json # src/components/HomeContentToggle/HomeContentToggle.tsx |
||
|
|
f63154e2f9 |
chore(monorepo): Turborepo + package dependency boundary enforcement
Add Turborepo for task caching/orchestration, and lock in the package boundary discipline that keeps its cache invalidation trustworthy. Turborepo: - turbo.json with build/typecheck/lint/test/dev tasks - "cacheDir": ".turbo/cache" pins each git worktree's cache to itself (an explicit relative cacheDir resolves to the worktree root, not the shared git common-dir) -- no per-worktree TURBO_CACHE_DIR setup needed - .turbo gitignored Package deps + lint: - declare every @civitai/* package's real external dependencies; redis, clickhouse, axiom, and telemetry were relying on root hoisting -- a phantom-dep trap any second app would hit (as the moderator did with @civitai/db) - packages/.eslintrc.cjs (root:true) enforces import/no-extraneous-dependencies (every import must be a declared dep, so Turborepo's graph-based cache invalidation stays correct) and bans ~/... app imports; run via `pnpm lint:packages` - @civitai/db-schema keeps a documented inline exception for @prisma/client (declaring it breaks `prisma generate`; proper fix is a custom output) CI: - pr-check.yml runs lint:packages on every PR/push to main Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
c790726d44 |
chore: gitignore .turbo cache
Turborepo writes its local task cache to .turbo/; ignore it so the cache never lands in a commit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
2e4e017280 |
feat(apps): scaffold moderator app — second-app factory consumption
A Next.js 16 app in apps/moderator that consumes @civitai/db as a workspace dep via createPrismaClients() — proving a fresh app can use the base-package factories. Welcome page is static (db wiring in lib/db.ts is dormant/ready). Notes for future apps: each app needs its own empty instrumentation.ts + proxy.ts to shadow the main app's (Next 16 infers the repo root as workspace root); declared @civitai/db's real deps (pg, prom-client, zod) so they resolve outside root hoisting. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
d572773c31 |
Merge branch 'main' into monorepo-bootstrap
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
6c421733a8 |
chore(next16): gitignore auto-generated next-env.d.ts
Next 16 rewrites next-env.d.ts's routes import between .next/dev/types (next dev) and .next/types (next build) depending on the last command run, so tracking it produces constant churn and merge conflicts. The file it imports lives under the already-ignored .next/, and Next regenerates next-env.d.ts on every dev/build, so untracking it is safe. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
3530a85f30 |
Migrate to Next.js 16 + Turbopack; fix Turbopack-dev runtime regressions
Build/deps/config:
- next, eslint-config-next, @next/bundle-analyzer → ^16; Turbopack is the
default build bundler. Added turbopack:{} config, dropped removed config keys.
- Removed all 151 typed-scss-modules *.module.scss.d.ts in favor of ambient
declarations in src/types/css-modules.d.ts (Turbopack panics on the .d.ts).
- CSS modules: rewrote bare :global to function form for Turbopack.
- trpc/[trpc] handler typing; bundler-agnostic applySourceMaps.
Runtime fixes (Turbopack dev/prod):
- CAConsentManager: static-import instead of next/dynamic. A dynamic component
wrapping the whole app async-loads its chunk under Turbopack dev, causing a
whole-tree hydration mismatch that re-mounts the app and orphans the server
DOM (the "double layout"; US:CA-gated). General rule: wrapper dynamics remount,
leaf dynamics are fine.
- ReactQueryDevtools: load client-only (ssr:false) — not SSR-safe under Turbopack.
- SharedWorkers (signals + civitai-link): Turbopack doesn't compile .ts
SharedWorker entries (vercel/next.js#74842), breaking dev AND the prod build.
Pre-bundle each with esbuild to public/workers/*.js (scripts/build-workers.mjs,
wired into predev/prebuild) and instantiate via static path.
See docs/next-16-migration.md for full details and remaining open items.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
5cc887c97c |
refactor(packages): factory + per-package env schema for base packages
Convert the civitai-* base packages from eager singletons into app-agnostic factories with their own zod env schemas, and wire them into the main app via thin shims. Packages now import only external deps (+ @civitai/db-schema); the app injects behavior (loggers, Flipt resolver, slow-query sink) and owns HMR globals + the Next build guard. - Phase 0: pnpm-workspace.yaml, per-package package.json + index barrels, @civitai/* tsconfig paths, transpilePackages - axiom/redis/db/clickhouse: createX() factories reading package-owned env.ts (z.prettifyError); Partial<Config> overrides - db: createPrismaClients + config-driven getClient pool factory; kv-helpers.ts breaks the client<->db-helpers cycle; limitConcurrency vendored; pg singletons -> app shims - clickhouse: base client stays; Tracker (auth/session/schema-coupled) extracted to src/server/clickhouse/tracker.ts - telemetry: prom helpers stay; DB pool-gauge block -> src/server/prom/client.ts - prisma generate paths updated for the db-schema package; generated slim schema gitignored Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
6404bc8b4a |
chore: gitignore Claude Code session lock
.claude/scheduled_tasks.lock is per-process state (PID + proc-start tick + acquisition timestamp) written every time Claude Code starts a session. Tracking it meant every contributor saw it as a perpetually modified file. Removing from index + adding to .gitignore so it stays local-only. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
9192134523 | update gitignore | ||
|
|
bb7e9c7560 |
Model UI Overhaul - File Management & Download Experience (#1964)
* feat: [US-001] - Add quantType and componentType type definitions - Add ModelFileQuantType type: 'Q8_0' | 'Q6_K' | 'Q5_K_M' | 'Q4_K_M' | 'Q4_K_S' | 'Q3_K_M' | 'Q2_K' - Add ModelFileComponentType type: 'VAE' | 'TextEncoder' | 'UNet' | 'CLIPVision' | 'ControlNet' | 'Config' | 'Other' - Add quantType and componentType to BasicFileMetadata interface - Add quantType to UserFilePreferences interface - Update preferenceWeight to use Partial<Record<...>> for type compatibility Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Restore model UI overhaul planning documents Re-adds planning files from feature/model-ui-overhaul-plan branch: - Main plan document with implementation details - Proposal document with options analysis - HTML mockups for sidebar and file upload UI Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: US-002 - Add quantTypes and componentTypes constants Added modelFileQuantTypes and modelFileComponentTypes constant arrays to src/server/common/constants.ts for use in UI selectors. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * feat: US-003 - Add quantType to file preference scoring - Added quantType: 0.5 to preferenceWeight object for scoring - Updated defaultFilePreferences to include quantType: 'Q4_K_M' as default - FileMetaKey type already includes quantType from BasicFileMetadata - Files with matching quantType now get 0.5 added to their score Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * feat: US-004 - Add quant type preference to user settings UI - Added 'Preferred Quant Type' Select to SettingsCard.tsx - Shows conditionally when format preference is GGUF - Uses constants.modelFileQuantTypes for options - Wired to user.filePreferences.quantType - Added tooltip explaining quant types (Q8 = best quality, Q4 = balanced, Q2 = smallest) - Default value: Q4_K_M (balanced quality/size) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * feat: US-005 - Update file service for quantType selection Updated getFileForModelVersion to accept and handle quantType parameter. Files with matching quantType will now be scored appropriately during file selection. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * feat: US-006 - Update download API for quantType parameter - Added quantType to validation schema with Zod enum validation - quantType is automatically passed through to file selection via spread operator - Validated against constants.modelFileQuantTypes - Misalignment check automatically handles quantType Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * feat: US-007 - Add quant type selector to file upload form - Added quantType field to FileFromContextProps and SchemaError types - Updated FilesProvider to initialize and handle quantType from metadata - Added quantType to metadata upload payload in handleUpload - Added quantType to modelFileMetadataSchema in server schema - Added validation refine to require quantType for GGUF files - Added conditional quantType Select in FileEditForm for .gguf files - Added tooltip explaining quant types (Q8 = best quality, Q4/Q2 = smaller) - Updated handleSave and handleReset to include quantType - quantType is marked as required (withAsterisk) for GGUF files Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * feat: US-008 - Add component type selector to file upload form Added componentType field to file upload form for non-Model file types. Component type selector appears for VAE, Text Encoder, Config, and Archive files. Auto-suggests componentType based on file type (VAE -> VAE, Text Encoder -> TextEncoder). Changes: - Added componentType to FileFromContextProps and SchemaError types - Added componentType to modelFileMetadataSchema - Added componentType Select to FileEditForm with conditional display - Updated handleSave, handleReset, and handleUpload to include componentType - Added auto-suggestion logic when file type changes Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * feat: US-009 - Restructure file upload UI into sections - Restructured Files.tsx into three sections: Model Files, Required Components, Optional Files - Added section headers with appropriate icons (IconFile3d, IconPuzzle, IconFileSettings) - Model Files section: files with type 'Model' or 'Pruned Model' - Required Components section: files with type 'VAE', 'Text Encoder' - yellow warning styling - Optional Files section: files with type 'Config', 'Archive', 'Workflow', etc. - Files auto-categorize based on type into appropriate section - Yellow Card styling for Required Components section to indicate importance Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * feat: US-010 - Create Link Component Modal Implemented LinkComponentModal for linking to existing models on Civitai as required components. Users can now: - Select component type (VAE, TextEncoder, UNet, CLIPVision, ControlNet) - Search for models using QuickSearchDropdown - Select version from model - Select file from version - Link component is saved and displayed in Files.tsx Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: [US-011] - Update FilesProvider validation for new fields - Add component-only model validation: models without Model type files must have at least 2 required components (VAE, Text Encoder, UNet, etc.) - Update conflict checking to include quantType in duplicate detection, preventing duplicate [size, type, fp, format, quantType] combinations Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: [US-012] - Create file variant grouping utility Add groupFilesByVariant() utility function that groups model files by: - Format (SafeTensor, GGUF, other) for model files - Component type (VAE, TextEncoder, etc.) for component files Within each group, files are sorted by quality (best first): - SafeTensor: fp32 > fp16 > bf16 > fp8 > nf4, full > pruned - GGUF: Q8_0 > Q6_K > Q5_K_M > Q4_K_M > Q4_K_S > Q3_K_M > Q2_K Also exports GroupedFileVariants type for consuming components. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: [US-013] - Update download button with variant dropdown - Created DownloadVariantDropdown component for grouped file variant selection - Groups files by format (SafeTensor, GGUF, Other) - Shows 'Best match' badge on user's preferred file based on preferences - Dropdown shows file size, precision/quant type for each variant - Default selected file is user's preference match - Integrated into ModelVersionDetails sidebar when multiple model variants exist Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: [US-014] - Add Required Components accordion section to sidebar - Create RequiredComponentsSection.tsx with yellow warning styling - Group component variants (e.g., Text Encoder fp16/fp8) with expandable lists - Single-variant components show without dropdown - Multi-variant components show expandable list with best match auto-selected - Add Download button for each component - Add "Download All Components" button that downloads preferred variants - Update detailAccordions default to include required-components Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: [US-015] Handle component-only models in sidebar - Detect component-only models (no model files, only components) - Hide main download button for component-only models - Show informational message: 'This is a modular model - download components below' - Make 'Download All Components' the primary action (filled button, larger size) - Keep Generate button visible for component-only models Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: [US-016] - Update FileInfo display for new metadata - Show quantType for GGUF files in file info popover - Show componentType for component files (VAE, TextEncoder, etc.) - Added friendly display names for component types - Handle missing metadata gracefully (only show if set) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: Fix prettier formatting in FileInfo.tsx Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: Fix prettier formatting in feature files Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: Remove unused imports from RequiredComponentsSection Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: [BUG-001] - Persist linkedComponents to FilesProvider context Move linkedComponents state from local useState in Files.tsx to FilesProvider context to ensure state persists across component remounts and is available for form submission. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: [BUG-002] - Add componentType to duplicate file checking Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: [BUG-003] - Fix race condition in LinkComponentModal auto-selection - Move auto-selection logic from render to useEffect hook - Add versionsLoading check to prevent accessing files before data loads - Add hasAutoSelectedFileRef to prevent multiple auto-selections - Reset ref in handleBack to allow re-selection when navigating back Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: [BUG-005] - Agent review validation with null safety fix Ran agent-review to validate BUG-001 through BUG-004. Fixed issue identified in review: added missing toLowerCase() for .zip extension check in FileInfo.tsx for consistency with .gguf check. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: [PAT-003] - Verify form validation patterns Audit of Zod validation patterns in FilesProvider.tsx - no code changes needed. Pattern compliance verified: Zod schema extension, refine usage, showErrorNotification. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: [PAT-006] - Verify download button patterns Audit completed for DownloadVariantDropdown.tsx patterns. All patterns match established codebase conventions: - createModelFileDownloadUrl usage matches other components - formatKBytes usage consistent across codebase - DownloadButton polymorphic pattern properly implemented Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: [PAT-008] - Run agent-review on pattern compliance External agent review completed for all new/modified components: - LinkComponentModal.tsx: PASS - modal, search, select patterns correct - FilesProvider.tsx: MOSTLY PASS - found silent error catch issue - Files.tsx: PASS - well-structured - RequiredComponentsSection.tsx: PASS - with code duplication note - DownloadVariantDropdown.tsx: PASS - with code duplication note - SettingsCard.tsx: PASS - optimistic updates correct Key findings documented: - getFileLabel/getFileDescription duplicated across 2 files (non-blocking) - Silent catch block in FilesProvider line 391 (non-blocking) - All core patterns (modal, search, select, accordion, tRPC) followed correctly Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: [QUA-001] - Extract shared file helper functions - Create ~/utils/file-display-helpers.ts with getFileLabel and getFileDescription - Update DownloadVariantDropdown.tsx to import from shared utility - Update RequiredComponentsSection.tsx to import from shared utility - Remove duplicated function definitions from both files Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: [QUA-002] - Fix silent error catch block Add proper error handling to the empty catch block in FilesProvider.tsx. Previously, errors from createFileMutation.mutateAsync were silently caught. Now displays an error notification to the user matching established patterns. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: [QUA-003] - Check for unused imports and dead code - Remove unused import MasonryScroller from masonic in Files.tsx - Remove unused variables offset and resizeObserver in Files.tsx - Fix unescaped apostrophe (We'll -> We'll) in Files.tsx - Remove unused index parameter in map callbacks in Files.tsx - Remove unused componentType prop destructuring in RequiredComponentsSection.tsx - Remove unused modelType prop destructuring in DownloadVariantDropdown.tsx Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: [QUA-004] - Verify accessibility attributes Added comprehensive ARIA attributes and keyboard accessibility: - DownloadVariantDropdown: Added role="button", aria-expanded, aria-haspopup, aria-label, tabIndex, keyboard handler to dropdown trigger; role="listbox" to dropdown content; role="option", aria-selected, aria-label to VariantItem - RequiredComponentsSection: Added role="button", aria-expanded, aria-label, tabIndex, keyboard handler to expandable component headers; role="listbox" to variant list; role="option", tabIndex, aria-selected, aria-label, keyboard handler to file selection boxes - LinkComponentModal: Added aria-label to QuickSearchDropdown and Select components for search, version, and file selection Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: [QUA-006] - Verify useEffect cleanup - Added useMemo to the `files` variable in LinkComponentModal.tsx to maintain reference equality for useEffect dependencies - Fixes ESLint react-hooks/exhaustive-deps warning about the files logical expression causing useEffect dependencies to change on every render - Reviewed all useEffect hooks in LinkComponentModal.tsx, FilesProvider.tsx, and DownloadVariantDropdown.tsx for proper cleanup - Verified no async operations/subscriptions/timers require cleanup in these files Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: Remove accidentally committed temp and ralph project files - Remove ralph phase2-pattern-reuse prd.json and progress.txt (should be gitignored) - Remove tmpclaude-* temp files - Add tmpclaude-* pattern to .gitignore Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Fixes typecheck issues * feat(model-ui): overhaul model sidebar with variant downloads, components, and actions - Restructure primary actions card with consolidated icon row (generate, share, like, vault, civitai link, notifications, collect, bid, report) - Add DownloadVariantDropdown with grouped file sections (SafeTensor/GGUF/Other) and best-match badge - Add RequiredComponentsSection with component variant selection and Download All - Persist linked components via RecommendedResource with isLinkedComponent flag - Overhaul PermissionIndicator to show all permission badges (commercial, generation, credit, merges, license, NSFW) - Handle early access/Buzz pricing in download and component sections - Expand upload UI component filter to include UNet, CLIPVision, ControlNet - Remove dead LinkComponentModal code (keep type export only) - Fix: side effect in setState updater causing duplicate API calls - Fix: memoize filesVisible to prevent groupFilesByVariant recomputing every render - Fix: stagger multi-download to avoid browser popup blocking - Fix: gate download hrefs on canDownload for early access models - Fix: add staleTime to getFollowingUsers query to reduce unnecessary refetches Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Includes design file * feat: model sidebar UI polish - verification status, button styling, reviews - Add VerifiedText scan status to all file variants (download dropdown, required components, optional files) - Remove redundant VAE alert banner from ModelFileAlert (now in RequiredComponentsSection) - Hide Bid button for unpublished/deleted model versions - Restyle Generate button as primary CTA with gradient background - Make download button styling consistent (light blue secondary CTA) for single and multi-variant - Update VerifiedText to match design (smaller text, colored icons, no ThemeIcon wrapper) - Fix VerifiedText click propagation preventing variant toggle on popover click - Fix light mode hover backgrounds on variant rows for readability - Replace ResourceReviewThumbActions with ModelVersionReview in Details card Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add isRequired to file metadata & merge upload UI sections - Add `isRequired` field to BasicFileMetadata, modelFileMetadataSchema, and linkedComponentSettingsSchema - Move LinkedComponent type from LinkComponentModal.tsx to model-file.schema.ts - Replace hardcoded 3-way file split (Model/Required/Optional) with dynamic 2-way split (Model Files + Additional Components) driven by acceptedModelFiles per model type - Add Switch toggle for required/optional on FileCard and LinkedComponentCard - Smart defaults: component types (VAE, Text Encoder, etc.) default to required - Update groupFilesByVariant() to use metadata.isRequired for sidebar grouping - RequiredComponentsSection now data-driven (no hardcoded component type list) - Update validation to use isRequired for component-only model checks Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Updates design * fix: restore buzz earned and generation popularity metrics to model version details Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: enrich linked components at read time & add backend link creation - Batch-fetch ModelFile data (name, sizeKB, type, metadata) in both controllers to enrich linked components at read time, fixing stale data for older records - Add addLinkedComponent mutation that resolves file details server-side, eliminating the extra modelVersion.getById fetch from the frontend - Remove sizeKB from linkedComponentSettingsSchema (no longer persisted) - Add fileType/fileMetadata to LinkedComponent for correct download URLs - Update RequiredComponentsSection and ModelVersionDetails to use file metadata for download URLs instead of user preferences - Display file size next to download button consistently across all component types - Add UNet, CLIPVision, ControlNet to modelFileTypes and modelFileOrder - Various UI polish: icon swap, border removal, no-wrap on file sizes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: guard download buttons behind purchase check for optional/linked files Optional files and linked components in both ModelVersionDetails and RequiredComponentsSection were missing canDownload guards, allowing direct downloads on early-access models that require purchase. Also removes unused downloadPrice prop from ComponentGroup. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address PR review: type safety, null handling, and shared constants - Add componentFileTypes constant to DRY up hardcoded arrays in 3 locations - Tighten componentType schema validation from z.string() to z.enum() - Prevent null quantType via allowDeselect={false} on SettingsCard Select - Fix groupFilesByVariant to default isRequired to true (isRequired !== false) - Normalize component-only validation to match linked component convention - Remove unused RecommendedResourceSettings import Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Correctly filters out incompatible resources when linking components * Fix model UI feedback: share popover, download URLs, resource select, upload errors - Add withinPortal to ShareButton Popover to fix clipping in sidebar - Use primary download URL for linked components to avoid 404 misalignment - Default resource select to 'all' tab for modelVersion linking and skip empty recommendedModels filter to prevent spinner - Improve file upload rejection messages with supported types and max file limit notifications Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Port resource select fixes to new ResourceSelectModal component Apply the same fixes from the deleted ResourceSelectModal2.tsx: - Default to 'all' tab when selectSource is 'modelVersion' - Skip empty recommendedModels filter to prevent spinner Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Replace useEffect with conditional state for modelVersion tab default Use local useState instead of localStorage-backed state when selectSource is 'modelVersion', avoiding the stale tab problem without a useEffect override. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fixes bad download url for required and optional components * Fix Download All Components to download multiple files via hidden iframes The previous implementation used forEach + setTimeout which broke the browser's user-gesture trust context, causing only the first file to download. The model download endpoint uses res.redirect(), so programmatic <a> tag clicks get blocked after the first navigation. Hidden iframes each follow redirects independently in their own browsing context. Also adds loading state to the download button. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Migrate vaeId to linked components system Replace the legacy ModelVersion.vaeId FK with the linkedComponents system (RecommendedResource table). This unifies VAE linking under the same mechanism used for all other component types (TextEncoder, UNet, CLIPVision, etc.). - Add SQL migration to create RecommendedResource records for existing vaeId refs - Add getLinkedVaeIds helper for batch VAE resolution from linked components - Update model controller, file service, generation service, and public API - Replace raw SQL vaeId column refs with RecommendedResource subqueries in caches - Generalize file download to support all linked component types, not just VAE - Remove vaeId from selectors, schemas, and frontend form - Remove duplicate VAE display in model detail (linkedComponents already shows it) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Removes TextualInversion and Hypernetwork filters when selecting linkedComponents * Retrigger pr preview deployment * Address model upload wizard UI feedback - Add Workflow and Upscaler to file types, component types, and file order - Expand GGUF quant types from 7 to 25 (K-quants, legacy, I-quants) - Allow .gguf files in Checkpoint additional components (for quantized text encoders) - Fix .zip inference: no longer auto-assigns type, user must pick - Fix .zip format mapping from 'Diffusers' to 'Other' - Add .json default inference to 'Config' - Add ComfyUI-friendly display labels (CLIP / Text Encoder, UNet / Diffusion Model, etc.) - Add Upscaler and Workflow to linked component resource selector - Improve validation error surfacing with per-file toast notifications - Add red border highlight on file cards with validation errors - Make quant type select searchable for the expanded list Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Update quantQualityRank with expanded GGUF quant types Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add Consolidate Versions feature for merging multi-version models Allow creators to merge multiple model versions into a single version, moving all files into the target version with configurable file type mapping and aggregating stats (downloads, likes, etc.) from all source versions. Triggered from the model detail page context menu. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: update import path for ReportEntity to use shared utils * feat: enhance ConsolidateVersions UI and support file metadata updates Overhaul the ConsolidateVersions modal with improved theme-aware design, file metadata editing (fp, size, format, quantType, isRequired), and updated schema/service to persist metadata changes during consolidation. Also pass quantType and isRequired through getModelsWithVersions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: GGUF validation, .bin file type options, and required toggle reset - Skip fp (precision) validation for GGUF checkpoint files since they use quantType instead — the UI shows Quant, not Precision, for GGUF files - Allow .bin files to be assigned component types (VAE, UNet, etc.) in the file type dropdown, not just Model/Negative - Stop resetting the Required toggle when changing file type in Additional Components — preserve the user's manual selection Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: remove DownloadHistory reference from consolidateVersions The DownloadHistory table no longer exists in production — downloads are now tracked in ClickHouse. The raw SQL query was causing a 42P01 error when consolidating versions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: expand file upload types, selective version merging, rename consolidate to merge - Allow config-type files (.json, .yaml, .yml, .txt) as primary uploads for Workflows, Poses, Wildcards, and Other model types - Add .safetensors to Detection primary extensions - Ensure all model types accept archive + config in additional components - Extract shared filterFileTypeByExtension to file-display-helpers.ts - Make inferFileType model-type-aware for correct .zip default assignment - Fix additional dropzone defaultType bug (was always using first type) - Add selective source version picking to merge versions wizard - Rename ConsolidateVersions to MergeVersions across frontend and backend Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: remove debug console.log referencing ctx.isGreen in trpc middleware Stray debug log was accessing ctx.isGreen which doesn't exist on the context type — isGreen is a feature flag on ctx.features, not ctx directly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: skip type inference for additional component drops, add Diffusion Model file type Files dropped in the Additional Components section no longer auto-infer as 'Model' type, letting users pick the correct component type (VAE, UNet, etc.). Split grouped labels "CLIP / Text Encoder" → "Text Encoder" and "UNet / Diffusion Model" → separate "UNet" and "Diffusion Model" options. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: reorganize model sidebar cards for better UX flow Move resource review card after the accordion so it no longer interrupts between download and model details. Group download-related alerts near the download card. Move donation goals below review as a secondary CTA. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(model-upload): add UNet and Diffusion Model to Checkpoint primary file types Move UNet and Diffusion Model from the additional components section to the primary model files section for Checkpoint models, so users can select these types directly from the model file type dropdown. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: manuelurenah <manuel.ureh@hotmail.com> |
||
|
|
2a12585c75 | chore(gitignore): exclude docs/incidents and cloudflare skill local state | ||
|
|
c57437acb1 |
Add Freshdesk support skill for Claude Code
Adds a Freshdesk API integration skill for querying tickets, replying to customers, managing KB articles, and looking up contacts from Claude Code. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
aaf7ec3620 |
feat: Add Dynamic Prize Pool card to challenge pages
Combined 3-section SpotlightCard for challenges with dynamic pricing: - Green top: Growing prize pool with shimmer animation and progress bar - Yellow middle: Your Entries with segmented review progress and guarantee button - Gray bottom: Generate + Submit action buttons Includes mod-only preview toggle for testing different entry states, extracted shared constants/helpers (DRY refactor), and reusable ProgressLegendDot and GlowDivider components. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
47910c946e |
feat: Add paid challenge judging (#2031)
* feat: Add paid challenge judging — pay Buzz to guarantee entry review Users can pay Buzz to guarantee their challenge entries get reviewed by the AI judge instead of waiting for random selection. Adds reviewCost field to Challenge model, requestReview mutation for buzz payment + tag assignment, getUserUnjudgedEntries query, guarantee checkbox in submit modal, post-submission review UI on detail page, and removes the per-user scoring cap for paid (reviewMeTagId) entries. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: Add UI preview screenshots for paid challenge judging Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: Add flat-rate review option for paid challenge judging Extends the paid review system to support both per-entry and flat-rate pricing. Flat rate charges once and covers all current + future entries. UI updates include a review cost type selector, segmented progress bar with hover animation, spotlight card effect, and HoverCard→Popover swap. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: Address PR review issues for paid challenge judging - Use pendingCount instead of unreviewedCount for guarantee cost display to avoid inflating the Buzz price with already-queued entries - Fix test assertions to check createBuzzTransactionMany (per-entry transactions) instead of createBuzzTransaction (single transaction) - Invalidate getUserUnjudgedEntries after submit to prevent stale data Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: Revert guaranteeCost to use unreviewedCount (include queued entries) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: Move stories file out of pages/ to fix build error Next.js requires all files in pages/ to have a default React component export. Moved EligibleModels.stories.tsx to src/components/Challenge/. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: manuelurenah <manuel.ureh@hotmail.com> |
||
|
|
8456cadd62 |
Add Flipt feature flag management skill
- SKILL.md: Documentation for managing feature flags - flipt.mjs: CLI script for listing, getting, and managing flags - .env.example: Template for Flipt configuration - Supports list, get, create, enable, disable, delete commands - Graceful handling of read-only API tokens with GitOps guidance Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> |
||
|
|
bc058e8387 | Merge remote-tracking branch 'origin/test/animal-paragraph' | ||
|
|
04db54219c |
refactor: Consolidate Ralph daemon into skill and improve orchestration
- Move daemon code from ralph-daemon/ into ralph/daemon/ - Add `watch` command for efficient state change monitoring - Add child session prefix (child-*) for clear hierarchy - Add PRD type validation before session creation - Fix abort cascade bug with defensive array handling - Update orchestrator prompt to recommend watch over polling - Add gitignore for daemon runtime files (pid, data, temp dirs) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> |
||
|
|
b6b1dfc48f |
fix: Simplify .browser gitignore to ignore entire profiles folder
Each developer has different browser profiles, so there's no need to track the metadata file. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> |
||
|
|
8c83826368 |
refactor: Consolidate browser automation skill into HTTP server
- Remove redundant files (runner.mjs, login-helper.mjs, lib/) - Add multi-session support with named sessions - Add profile management with metadata - Add flow listing and execution endpoints - Add full-page screenshot support (?fullPage=true) - Fix project root path calculation (was going 4 dirs up instead of 3) - Update .gitignore for profile auth state files - Simplify SKILL.md documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> |
||
|
|
5a57ba1183 |
Add browser automation skill with REPL exploration and flow saving
- Interactive REPL mode for exploring pages via Playwright code chunks - Chunk recording system for capturing successful interactions - Flow saving with curation (select which chunks to keep) - Flow chaining (run saved flows as chunks in exploration) - Session folder structure for organizing screenshots - One-shot page inspection command Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> |
||
|
|
d2e116a0f1 |
Fix Redis Cluster failover handling with proactive topology refresh
After migrating to Redis Cluster, the app would hold stale connections after a failover because node-redis only refreshes topology on MOVED/ASK errors. If the old master goes down before responding, the client never discovers the new topology. Changes: - Add cluster-specific event handlers (node-error, node-disconnect) that trigger topology rediscovery when nodes fail - Add periodic topology refresh (default 30s) to catch missed failovers - Support multiple root nodes via REDIS_CLUSTER_NODES env var for redundant discovery during startup - Increase maxCommandRedirections from 16 to 32 for longer failovers New environment variables: - REDIS_CLUSTER_NODES: Comma-separated list of cluster node URLs - REDIS_CLUSTER_REFRESH_INTERVAL: Topology refresh interval in ms (default 30000) Addresses: https://github.com/redis/node-redis/issues/2806 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> Gate enhanced Redis cluster failover behind Flipt feature flag Adds REDIS_CLUSTER_ENHANCED_FAILOVER flag to Flipt enum and gates the enhanced failover handling (topology rediscovery on node events and periodic refresh) behind this flag. When flag is disabled (default): - Basic cluster operation continues normally - Node events are logged but don't trigger rediscovery - No periodic topology refresh When flag is enabled: - Node errors/disconnects trigger topology rediscovery - Periodic topology refresh runs every REDIS_CLUSTER_REFRESH_INTERVAL ms This allows safe rollout of the new failover handling behavior. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> Pass hostname context to Flipt for is-next segment matching The REDIS_CLUSTER_ENHANCED_FAILOVER flag uses the "is-next" segment (hostname == next.civitai.com) to enable enhanced failover by default on next.civitai.com while keeping it disabled elsewhere. This commit: - Extracts hostname from NEXTAUTH_URL for Flipt context - Passes hostname context to isFlipt() calls for segment matching - Logs the hostname being used for debugging Flipt flag configuration required: - Flag: redis-cluster-enhanced-failover - Default: disabled (false) - Rollout: segment "is-next" -> enabled (true) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> gitignore |
||
|
|
e08ab05e1c |
Add mod-actions Claude Code skill for moderator actions
Adds a new skill for taking moderator actions via tRPC API: - user lookup by ID or username - ban/unban users with reason codes and messages - mute/unmute users - set leaderboard eligibility - remove all user content Uses API key authentication with configurable endpoint (production or local). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> |
||
|
|
cfc440ea70 | Merge branch 'main' into feature/feeds | ||
|
|
ed9787c393 | Tie a model to the a workflow as soon as its submitted | ||
|
|
dbbec28265 | Merge with main | ||
|
|
91eca6dfa3 | Adjust multi fetching of collection permissions | ||
|
|
0d320ab932 | eslint rules for import resolver | ||
|
|
215fa4376d | Add prisma sliming |