fix(review): publication tags removed in the re-review tab are actually removed (#635) - #636
Merged
Merged
Conversation
Bumps the compose-images group with 1 update: axllent/mailpit. Updates `axllent/mailpit` from v1.30.6 to v1.30.7 --- updated-dependencies: - dependency-name: axllent/mailpit dependency-version: v1.30.7 dependency-type: direct:production dependency-group: compose-images ... Signed-off-by: dependabot[bot] <support@github.com>
… with 8 updates Bumps the production-minor-patch group with 8 updates in the /app directory: | Package | From | To | | --- | --- | --- | | [@unhead/vue](https://github.com/unjs/unhead/tree/HEAD/packages/vue) | `3.2.3` | `3.4.0` | | [@vueuse/core](https://github.com/vueuse/vueuse/tree/HEAD/packages/core) | `14.3.0` | `14.4.0` | | [cytoscape](https://github.com/cytoscape/cytoscape.js) | `3.34.0` | `3.34.1` | | [dompurify](https://github.com/cure53/DOMPurify) | `3.4.12` | `3.4.14` | | [pinia](https://github.com/vuejs/pinia) | `4.0.2` | `4.0.3` | | [swagger-ui](https://github.com/swagger-api/swagger-ui) | `5.32.11` | `5.32.14` | | [swagger-ui-dist](https://github.com/swagger-api/swagger-ui) | `5.32.11` | `5.32.14` | | [vue](https://github.com/vuejs/core) | `3.5.40` | `3.5.41` | Updates `@unhead/vue` from 3.2.3 to 3.4.0 - [Release notes](https://github.com/unjs/unhead/releases) - [Commits](https://github.com/unjs/unhead/commits/v3.4.0/packages/vue) Updates `@vueuse/core` from 14.3.0 to 14.4.0 - [Release notes](https://github.com/vueuse/vueuse/releases) - [Commits](https://github.com/vueuse/vueuse/commits/v14.4.0/packages/core) Updates `cytoscape` from 3.34.0 to 3.34.1 - [Release notes](https://github.com/cytoscape/cytoscape.js/releases) - [Commits](cytoscape/cytoscape.js@v3.34.0...v3.34.1) Updates `dompurify` from 3.4.12 to 3.4.14 - [Release notes](https://github.com/cure53/DOMPurify/releases) - [Commits](cure53/DOMPurify@3.4.12...3.4.14) Updates `pinia` from 4.0.2 to 4.0.3 - [Release notes](https://github.com/vuejs/pinia/releases) - [Commits](vuejs/pinia@v4.0.2...v4.0.3) Updates `swagger-ui` from 5.32.11 to 5.32.14 - [Release notes](https://github.com/swagger-api/swagger-ui/releases) - [Commits](swagger-api/swagger-ui@v5.32.11...v5.32.14) Updates `swagger-ui-dist` from 5.32.11 to 5.32.14 - [Release notes](https://github.com/swagger-api/swagger-ui/releases) - [Commits](swagger-api/swagger-ui@v5.32.11...v5.32.14) Updates `vue` from 3.5.40 to 3.5.41 - [Release notes](https://github.com/vuejs/core/releases) - [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md) - [Commits](vuejs/core@v3.5.40...v3.5.41) --- updated-dependencies: - dependency-name: "@unhead/vue" dependency-version: 3.4.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-minor-patch - dependency-name: "@vueuse/core" dependency-version: 14.4.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-minor-patch - dependency-name: cytoscape dependency-version: 3.34.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-minor-patch - dependency-name: dompurify dependency-version: 3.4.14 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-minor-patch - dependency-name: pinia dependency-version: 4.0.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-minor-patch - dependency-name: swagger-ui dependency-version: 5.32.14 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-minor-patch - dependency-name: swagger-ui-dist dependency-version: 5.32.14 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-minor-patch - dependency-name: vue dependency-version: 3.5.41 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-minor-patch ... Signed-off-by: dependabot[bot] <support@github.com>
…ith 18 updates Bumps the dev-dependencies group with 15 updates in the /app directory: | Package | From | To | | --- | --- | --- | | [@axe-core/playwright](https://github.com/dequelabs/axe-core-npm) | `4.12.1` | `4.13.0` | | [@playwright/test](https://github.com/microsoft/playwright) | `1.62.0` | `1.62.1` | | [@testing-library/user-event](https://github.com/testing-library/user-event) | `14.6.1` | `14.6.5` | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `26.1.1` | `26.2.0` | | [@vitest/coverage-v8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8) | `4.1.10` | `4.1.11` | | [@vue/compiler-sfc](https://github.com/vuejs/core/tree/HEAD/packages/compiler-sfc) | `3.5.40` | `3.5.41` | | [axios](https://github.com/axios/axios) | `1.18.1` | `1.19.0` | | [cssnano](https://github.com/cssnano/cssnano) | `8.0.2` | `8.0.7` | | [eslint](https://github.com/eslint/eslint) | `10.8.0` | `10.9.0` | | [globals](https://github.com/sindresorhus/globals) | `17.8.0` | `17.11.0` | | [lint-staged](https://github.com/lint-staged/lint-staged) | `17.2.0` | `17.3.0` | | [rollup-plugin-visualizer](https://github.com/btd/rollup-plugin-visualizer) | `7.0.1` | `7.1.1` | | [sass](https://github.com/sass/dart-sass) | `1.102.0` | `1.103.1` | | [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.65.0` | `8.67.0` | | [vue-tsc](https://github.com/vuejs/language-tools/tree/HEAD/packages/tsc) | `3.3.8` | `3.3.11` | Updates `@axe-core/playwright` from 4.12.1 to 4.13.0 - [Release notes](https://github.com/dequelabs/axe-core-npm/releases) - [Changelog](https://github.com/dequelabs/axe-core-npm/blob/develop/CHANGELOG.md) - [Commits](https://github.com/dequelabs/axe-core-npm/commits/v4.13.0) Updates `@playwright/test` from 1.62.0 to 1.62.1 - [Release notes](https://github.com/microsoft/playwright/releases) - [Commits](microsoft/playwright@v1.62.0...v1.62.1) Updates `@testing-library/user-event` from 14.6.1 to 14.6.5 - [Release notes](https://github.com/testing-library/user-event/releases) - [Changelog](https://github.com/testing-library/user-event/blob/main/CHANGELOG.md) - [Commits](testing-library/user-event@v14.6.1...v14.6.5) Updates `@types/node` from 26.1.1 to 26.2.0 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `@vitest/coverage-v8` from 4.1.10 to 4.1.11 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md) - [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.11/packages/coverage-v8) Updates `@vitest/ui` from 4.1.10 to 4.1.11 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md) - [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.11/packages/ui) Updates `@vue/compiler-sfc` from 3.5.40 to 3.5.41 - [Release notes](https://github.com/vuejs/core/releases) - [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md) - [Commits](https://github.com/vuejs/core/commits/v3.5.41/packages/compiler-sfc) Updates `axios` from 1.18.1 to 1.19.0 - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](axios/axios@v1.18.1...v1.19.0) Updates `cssnano` from 8.0.2 to 8.0.7 - [Release notes](https://github.com/cssnano/cssnano/releases) - [Commits](https://github.com/cssnano/cssnano/compare/cssnano@8.0.2...cssnano@8.0.7) Updates `eslint` from 10.8.0 to 10.9.0 - [Release notes](https://github.com/eslint/eslint/releases) - [Commits](eslint/eslint@v10.8.0...v10.9.0) Updates `globals` from 17.8.0 to 17.11.0 - [Release notes](https://github.com/sindresorhus/globals/releases) - [Commits](sindresorhus/globals@v17.8.0...v17.11.0) Updates `lint-staged` from 17.2.0 to 17.3.0 - [Release notes](https://github.com/lint-staged/lint-staged/releases) - [Changelog](https://github.com/lint-staged/lint-staged/blob/main/CHANGELOG.md) - [Commits](lint-staged/lint-staged@v17.2.0...v17.3.0) Updates `postcss` from 8.5.25 to 8.5.26 - [Release notes](https://github.com/postcss/postcss/releases) - [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md) - [Commits](postcss/postcss@8.5.25...8.5.26) Updates `rollup-plugin-visualizer` from 7.0.1 to 7.1.1 - [Changelog](https://github.com/btd/rollup-plugin-visualizer/blob/master/CHANGELOG.md) - [Commits](btd/rollup-plugin-visualizer@v7.0.1...v7.1.1) Updates `sass` from 1.102.0 to 1.103.1 - [Release notes](https://github.com/sass/dart-sass/releases) - [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md) - [Commits](sass/dart-sass@1.102.0...1.103.1) Updates `typescript-eslint` from 8.65.0 to 8.67.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.67.0/packages/typescript-eslint) Updates `vitest` from 4.1.10 to 4.1.11 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md) - [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.11/packages/vitest) Updates `vue-tsc` from 3.3.8 to 3.3.11 - [Release notes](https://github.com/vuejs/language-tools/releases) - [Changelog](https://github.com/vuejs/language-tools/blob/master/CHANGELOG.md) - [Commits](https://github.com/vuejs/language-tools/commits/v3.3.11/packages/tsc) --- updated-dependencies: - dependency-name: "@axe-core/playwright" dependency-version: 4.13.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: "@playwright/test" dependency-version: 1.62.1 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies - dependency-name: "@testing-library/user-event" dependency-version: 14.6.5 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies - dependency-name: "@types/node" dependency-version: 26.2.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: "@vitest/coverage-v8" dependency-version: 4.1.11 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies - dependency-name: "@vitest/ui" dependency-version: 4.1.11 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies - dependency-name: "@vue/compiler-sfc" dependency-version: 3.5.41 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies - dependency-name: axios dependency-version: 1.19.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: cssnano dependency-version: 8.0.7 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies - dependency-name: eslint dependency-version: 10.9.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: globals dependency-version: 17.11.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: lint-staged dependency-version: 17.3.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: postcss dependency-version: 8.5.26 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies - dependency-name: rollup-plugin-visualizer dependency-version: 7.1.1 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: sass dependency-version: 1.103.1 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: typescript-eslint dependency-version: 8.67.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: vitest dependency-version: 4.1.11 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies - dependency-name: vue-tsc dependency-version: 3.3.11 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] <support@github.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014EDxFc8ddBksJf33dibjN1
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014EDxFc8ddBksJf33dibjN1
…635) useReviewForm.submitForm() submitted the union of the set loaded from the server and the set currently in the form, so a PMID the curator removed was restored one line before serialisation. The chip vanished, the PUT returned 200, and publication_replace_for_review() re-INSERTed the row -- which is exactly what #635 reports. The union was a 'BUG-05' guard against a reactivity failure the current bindings cannot produce, and it was the only reason removal worked from Modify Entity but not from re-review: useEntityInfo and useReviewApprovalActions always submitted the live selection. The #635 matrix is split into useReviewForm.publications.spec.ts so both spec files stay under the 600-line ceiling. It asserts toEqual on the whole array, never toContain -- the defect was an extra surviving entry, which the pre-#635 containment assertions were blind to. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014EDxFc8ddBksJf33dibjN1
…ew (#635) Three findings from a Codex gpt-5.6-sol review of the #635 spec, each verified against the code before acting: * submitReviewChange() decided create-vs-update from review_info.review_id, populated by loadReviewInfo(), which catches its errors and lets the modal open anyway. A failed metadata fetch therefore POSTed, minting a DUPLICATE review whose publications were INSERTed onto the new row. It now reads useReviewForm's own reviewId, whose load is atomic. Red-green proven by Review.submitMode.spec.ts. * The publication-decrease log_warn lived in publication_replace_for_review()'s is.null(conn) branch, which the review write path never takes (it always passes txn_conn) -- so the one operation that can drop a curated publication logged nothing. Moved into the shared path and read on the write connection, making it transaction-local. * The literature -> publications parse is extracted from the endpoint into functions/review-literature-parsing.R and pinned by 23 assertions across all four populated/empty combinations. bind_rows(.id=) labels by frame POSITION, so an empty additional_references must not shift the surviving gene_review rows into slot 1 and relabel them -- it does not, but nothing said so. The e2e regression + fixture: the baseline seeded publication_id '12345678' / publication_type 'PMID', which matches neither filter in loadReviewData(), so the row rendered as no chip and #635 had no end-to-end coverage. Production holds only PMID-prefixed ids and additional_references / gene_review types (verified live: 8148/1099 in the join table, 4304/386 in publication, zero others), so the fixture is corrected to that shape -- which is what lets review.publication-removal.spec.ts assert removal through the API, the check a UI-only assertion would have passed while the bug was live. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014EDxFc8ddBksJf33dibjN1
… renderer contract (#620) markdown-it 15 bundles its own TypeScript declarations, so @types/markdown-it is removed -- two declaration sources for one module is how a confusing vue-tsc failure gets built. It also moves to linkify-it v6, which turns fuzzy links OFF by default: with linkify:true alone a bare example.com silently stopped becoming a link while https://example.com kept working. That renders curator-authored content on /About and in the CMS preview, so the bump would have quietly changed how already-published text looks. md.linkify.set({fuzzyLink:true}) restores it. useMarkdownRenderer had no test at all, so nothing pinned its output across a major bump. It now has one, covering the fuzzy/explicit linkify split, the html:false escaping path, the DOMPurify allowlist, and the javascript: href sink. The fuzzy-link case was written against markdown-it 14, went red on the bump, and is green again only because of the explicit setting -- which is the whole point of having it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014EDxFc8ddBksJf33dibjN1
# Conflicts: # app/package-lock.json
# Conflicts: # app/package-lock.json # app/package.json
…635) Found by driving the real dev stack: BFormTag renders its title from its own internal tagText, and ReviewFormFields fills the tag's default slot with a <BLink> rather than a bare string, so the attribute comes out as the literal '[object Object]'. The obvious [title="PMID:..."] selector therefore matches nothing and the test would have passed vacuously. Reseed moved to afterEach so every test in the file hands the next a clean fixture, not just the last one. Red-green proven against the real stack: with the union reintroduced (and nothing else changed) the spec fails on 'the removed publication must be gone from the entity' -- after the chip-removed and save-returned-200 steps have already passed, which is exactly the reported symptom and exactly what a UI-only assertion would have missed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014EDxFc8ddBksJf33dibjN1
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014EDxFc8ddBksJf33dibjN1
Docs + release bump for #635 and the folded-in dependabot PRs (#620, #628, #633, #634). AGENTS.md gains five gotchas, each one a trap this change actually hit: the union-vs-live-selection rule and why only one of the three review surfaces had it; deriving isUpdate from the form's own reviewId rather than error-swallowed metadata; the positional bind_rows(.id=) contract behind the extracted literature parser and the fact that its zero-row case is the DESTRUCTIVE one; the production shape the Playwright publication fixture must keep; and that a BFormTag chip must be matched by text because its title attribute renders as '[object Object]'. test-endpoint-review.R sources the new parser into .GlobalEnv, not the testthat file env: the handler is eval'd into make_review_sandbox()'s new.env(parent = globalenv()), so its lookup chain never reaches the file env. The sibling re-review endpoint spec uses the same idiom. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014EDxFc8ddBksJf33dibjN1
Four findings from a Codex gpt-5.6-sol review of the branch diff. It cleared the core #635 fix and confirmed this.reviewForm.reviewId.value is correct (setup() ref-unwrapping is shallow, so a nested composable object keeps its refs -- Review.vue's own comment says the same). * P2, real: de-duplication ran BEFORE sanitisation, so 'PMID: 123' and 'PMID:123' both reached the payload -- they are only equal once the whitespace is gone. Sanitise first. The API collapses them anyway, so this was never DB corruption, but the payload should say what the curator meant. Red-green proven by a new whitespace test. * P2, real: the parser test's comment claimed a cross-list duplicate reaches the review_triple unique key. It does not -- review_write_prepare() passes the tibble through publication_write_classify_genereviews(), whose distinct(publication_id) collapses it first. Comment corrected to state the actual downstream contract. * P1, partly real: the e2e save matcher accepted create OR update. Editing a queued re-review is always an update, so a create would mean the duplicate- review defect -- and the reseed's ON DUPLICATE KEY UPDATE would not clean that duplicate up. Now requires /api/review/update. The cross-file sharing of entity 123 with curate.variation-provenance.spec.ts is documented, with the --workers=1 requirement stated. * P1, real: regenerating package-lock.json resolved every caret range to the newest in-range release, landing five packages above their Dependabot pin and advancing three transitive dev-only packages (svgo 4.1.0 and its required css-select 6 / css-what 7 majors). Verified dev:true in the lockfile, so they cannot reach the shipped bundle, and every intended bump is present at or above its pin. Disclosed precisely in the CHANGELOG rather than left silent. Also: test-unit-review-write-repositories.R stubbed only db_execute_statement, so the count query the moved shrink-warning added blew up on its fake string connection. Stubbed and turned into two assertions that the count is read on the CALLER'S connection -- reading it on the pool would race the DELETE it describes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014EDxFc8ddBksJf33dibjN1
Repo convention (.planning/reviews/). Both are gpt-5.6-sol at high effort: the spec review pre-code (11 findings, DO-NOT-SHIP) and the diff review (4 findings, DO-NOT-SHIP), each triaged against the code in the commits above rather than accepted wholesale -- two of the spec review's findings were factually wrong and are rebutted with live query results. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014EDxFc8ddBksJf33dibjN1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #635.
The bug
useReviewForm.submitForm()did not submit the curator's selection. It submitted the union of that selection and the set loaded from the server:Removing a PMID cleared it from the reactive form — which is why the chip disappeared and the reporter saw the UI update — and the union restored it one line before serialisation. The PUT carried the pre-edit set,
publication_replace_for_review()faithfully re-INSERTed the row, and the save reported success. GeneReviews had the identical defect.The server was never complicit.
publication_replace_for_review()is an honest DELETE-then-INSERT of exactly what it is sent, and the endpoint's empty-literaturebranch already means "remove them all". No API change was needed for the fix.The union lived in exactly one composable. That is the report's other observation, explained:
useReviewForm.tsuseEntityInfo.tsuseReviewApprovalActions.tsThe guard defended against a reactivity failure the current bindings cannot produce:
ReviewFormFields.vuebindsv-modelstraight onto the composable's own reactive object,loadReviewData()is atomic (a rejection propagates out ofinfoReview()beforeshow()), and the modal never reads a draft back. A guard that cannot distinguish "the curator cleared the field" from "a hypothetical desync" is not a guard — it is this bug. It was also enshrined in a unit test ("Simulate form reactivity issue"), which is inverted here.Two further defects, found by adversarially reviewing the fix
submitReviewChange()derived create-vs-update fromreview_info.review_id, populated byuseReviewData.loadReviewInfo()— which catches its errors, reports them, and returns, after which the modal opens anyway. On that failureisUpdatefell tofalseand the save POSTed/api/review/create, forking the curation record and INSERTing publications onto the new row. It now readsuseReviewForm's ownreviewId, whose load is atomic.publication_replace_for_review()'sis.null(conn)branch; the review write path always passestxn_conn. Moved into the shared path and read on the write connection, so it is transaction-local. Verified live:WARN Publication count decreasing for review 3155: 2 -> 1.Verification
useReviewForm.publications.spec.ts(9 tests — remove-one / remove-all / remove-genereview / add+remove / whitespace-dedupe),Review.submitMode.spec.ts(4),test-unit-review-literature-parsing.R(23 assertions across all four populated/emptyliteratureshapes),test-unit-review-write-repositories.R(the count must be read on the caller's connection).review.publication-removal.spec.tsdrives the real modal and asserts throughGET /api/review/<id>/publications— the check a UI-only assertion would have passed while the bug was live. Proven red against the pre-fix bundle: it fails on "the removed publication must be gone from the entity" only after the chip-removed and save-returned-200 steps pass, reproducing the report exactly.additional_references: ["PMID:32705143"], methodPUT), confirmed the row was gone fromndd_review_publication_join, confirmed exactly one review row for the entity, saw the newWARNline — then restored every mutated row.make ci-local✅ ·make code-quality-audit✅ · 305 vitest files / 2436 tests ✅ · type-check + strict ✅ · lint 0 errors ✅ ·build:bundle-budget✅--workers=1: 165 passed, 3 env-gated skips, 1 pre-existing failure — see Known unrelated failure below. The fixture correction also re-activates four Variation ontology provenance: mark machine-derived annotations and stop silent promotion to curator-authored #608 provenance tests that were skipping behind thereviewSaveWorksprobe.Fixture correction
The baseline seeded
publication_id = '12345678'/publication_type = 'PMID', which matches neither filter inloadReviewData()— so the row rendered as no chip at all, which is why this bug shipped without end-to-end coverage. Production holds only the two real shapes (join table 8148additional_references/ 1099gene_review;publication4304 / 386; zero of anything else — queried live), so both tables are corrected to match.Folded-in Dependabot PRs
Closes #620 · Closes #628 · Closes #633 · Closes #634
#620 (
markdown-it14 → 15) was not a drop-in and is the reason it is worth calling out:@types/markdown-itis removed.example.comwould have silently stopped auto-linking in curator-authored About/CMS content.useMarkdownRendererhad no test at all, so nothing pinned this. It now has one; the fuzzy-link case was written against 14, went red on the bump, and is green again only because of an explicitmd.linkify.set({ fuzzyLink: true }).Because the three npm branches conflicted on the lockfile, it was regenerated.
npmresolves each caret range to the newest in-range release, so five packages land slightly above their Dependabot pin, and three transitive, dev-only packages advance (svgo4.1.0 and thecss-select6 /css-what7 majors it requires). All aredev: truein the lockfile — build-time CSS/SVG minification that cannot enter the shipped bundle. Every intended bump is present at or above its pin. Detailed in the CHANGELOG rather than left silent.Known unrelated failure
analyses.data-releases.spec.tsfails locally against the empty-DB Playwright fixture./api/analysis/releasesreturns{"releases":[]}and the page correctly renders "No releases published yet", but the spec's guard uses non-waitingisVisible()before the async fetch resolves, so it never takes its own empty-statecontinue. No file in this diff is involved. Left for #573 — the real fix is deciding whether the fixture should seed a release, not silencing the guard.Review
Two adversarial passes with Codex
gpt-5.6-sol(high), archived under.planning/reviews/: the spec review pre-code (11 findings) and the diff review (4 findings). Each was triaged against the code rather than accepted — two spec-review findings were factually wrong and are rebutted with live query results. The diff review explicitly cleared the core fix, including thatthis.reviewForm.reviewId.valueis correct becausesetup()ref-unwrapping is shallow.One thing neither the spec nor either review caught, and only driving the real browser did:
BFormTagrenderstitlefrom its internaltagText, andReviewFormFields.vuefills the slot with a<BLink>, so the attribute is the literal"[object Object]". The planned.b-form-tag[title="PMID:…"]selector matches nothing and would have made the E2E pass vacuously.🤖 Generated with Claude Code
https://claude.ai/code/session_014EDxFc8ddBksJf33dibjN1