Skip to content

fix(review): publication tags removed in the re-review tab are actually removed (#635) - #636

Merged
berntpopp merged 16 commits into
masterfrom
fix/re-review-publication-removal-635
Aug 25, 2026
Merged

fix(review): publication tags removed in the re-review tab are actually removed (#635)#636
berntpopp merged 16 commits into
masterfrom
fix/re-review-publication-removal-635

Conversation

@berntpopp

Copy link
Copy Markdown
Owner

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:

// BUG-05 fix: Merge original publications with current form data
const mergedPublications = [...new Set([...originalPublications.value, ...formData.publications])];

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-literature branch 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:

Surface Composable Union?
Re-review Edit-Review modal useReviewForm.ts yes — the bug
Modify Entity useEntityInfo.ts no
Approve Review useReviewApprovalActions.ts no

The guard defended against a reactivity failure the current bindings cannot produce: ReviewFormFields.vue binds v-model straight onto the composable's own reactive object, loadReviewData() is atomic (a rejection propagates out of infoReview() before show()), 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

  • A failed metadata fetch minted a duplicate review. submitReviewChange() derived create-vs-update from review_info.review_id, populated by useReviewData.loadReviewInfo() — which catches its errors, reports them, and returns, after which the modal opens anyway. On that failure isUpdate fell to false and the save POSTed /api/review/create, forking the curation record and INSERTing publications onto the new row. It now reads useReviewForm's own reviewId, whose load is atomic.
  • The publication-decrease warning never fired on the path that can lose data. It sat inside publication_replace_for_review()'s is.null(conn) branch; the review write path always passes txn_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

  • Unit, red-green proven by reverting each fix in turn: 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/empty literature shapes), test-unit-review-write-repositories.R (the count must be read on the caller's connection).
  • E2E review.publication-removal.spec.ts drives the real modal and asserts through GET /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.
  • Manual monkey test on the dev stack as a curator: removed a PMID from a real re-review entity, captured the outgoing payload (additional_references: ["PMID:32705143"], method PUT), confirmed the row was gone from ndd_review_publication_join, confirmed exactly one review row for the entity, saw the new WARN line — 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
  • Playwright --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 the reviewSaveWorks probe.

Fixture correction

The baseline seeded publication_id = '12345678' / publication_type = 'PMID', which matches neither filter in loadReviewData() — 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 8148 additional_references / 1099 gene_review; publication 4304 / 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-it 14 → 15) was not a drop-in and is the reason it is worth calling out:

  1. It bundles its own TypeScript declarations; @types/markdown-it is removed.
  2. It moves to linkify-it v6, which disables fuzzy links by default — bare example.com would have silently stopped auto-linking in curator-authored About/CMS content. useMarkdownRenderer had 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 explicit md.linkify.set({ fuzzyLink: true }).

Because the three npm branches conflicted on the lockfile, it was regenerated. npm resolves 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 (svgo 4.1.0 and the css-select 6 / css-what 7 majors it requires). All are dev: true in 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.ts fails locally against the empty-DB Playwright fixture. /api/analysis/releases returns {"releases":[]} and the page correctly renders "No releases published yet", but the spec's guard uses non-waiting isVisible() before the async fetch resolves, so it never takes its own empty-state continue. 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 that this.reviewForm.reviewId.value is correct because setup() ref-unwrapping is shallow.

One thing neither the spec nor either review caught, and only driving the real browser did: BFormTag renders title from its internal tagText, and ReviewFormFields.vue fills 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

dependabot Bot and others added 16 commits August 11, 2026 16:33
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
#	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
@berntpopp
berntpopp merged commit 11196eb into master Aug 25, 2026
10 checks passed
@berntpopp
berntpopp deleted the fix/re-review-publication-removal-635 branch August 25, 2026 15:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Publication tags cannot be removed in re-review tab

1 participant