Skip to content

test(db): reproduce stale buffered sync when optimistic snapback races a persisting transaction#1561

Closed
kevin-dp wants to merge 1 commit into
TanStack:mainfrom
kevin-dp:kevin/snapback-buffered-sync-repro
Closed

test(db): reproduce stale buffered sync when optimistic snapback races a persisting transaction#1561
kevin-dp wants to merge 1 commit into
TanStack:mainfrom
kevin-dp:kevin/snapback-buffered-sync-repro

Conversation

@kevin-dp
Copy link
Copy Markdown
Contributor

@kevin-dp kevin-dp commented May 27, 2026

Summary

Adds a failing reproduction test for a stale-data bug originally reported as "auto-index returns the row in the wrong bucket after an optimistic→synced update", and confirms that the same symptom still reproduces against the fix in #1517.

The repro narrows the bug down to a different mechanism than the index-bucket lifecycle issue #1517 addressed: it lives in commitPendingTransactions's gating on hasPersistingTransaction, not in BasicIndex/BTreeIndex or in isRedundantSync.

CI is intentionally red: the failing test describes the correct behaviour, and stays failing until the bug is fixed.

What the test reproduces

Models the user's "fast successive drags" kanban scenario:

  1. User drags card P from stage A → stage B → optimistic transaction T1 starts persisting.
  2. User immediately drags card Q from stage A → stage B → optimistic transaction T2 starts persisting.
  3. T1's mutationFn resolves (server-confirmed). In the user's app, awaitTxId races out via the snapshot path before the shape stream has delivered P's row change — so when the optimistic delta lifts, no buffered sync exists for P, and recomputeOptimisticState drops P's optimistic upsert as "stale". Visible state for P snaps from B back to A.
  4. The shape stream now delivers P's row change. But T2 is still persisting, so commitPendingTransactions is gated on hasPersistingTransaction and the sync message sits buffered in pendingSyncedTransactions indefinitely.
  5. While T2 keeps persisting, anyone reading the live query sees P stuck in stage A — even though the server has confirmed it as stage B, and the sync message for that change is sitting in pendingSyncedTransactions.

In the user's app, with continuous drag traffic the "another transaction is persisting" condition is essentially always true, so the buffered sync never flushes → "stays until reload".

The test also includes a baseline case (single drag, no concurrent traffic) that passes — demonstrating that the bug only surfaces when a concurrent persisting transaction is blocking sync application for unrelated keys.

Relationship to #1517

The user originally suspected BasicIndex was leaving keys in stale buckets; #1517 addressed that by tracking key→bucket and dropping prior buckets on add. They retested with #1517 applied and the snapback still reproduced. I ran this repro on both main and kevin/stale-index-bucket-repro (the #1517 branch) — same failure mode either way. So this is a separate bug; the user's awaitMatch workaround sidesteps it by preventing the premature optimistic lift in step 3.

This PR does not propose a fix. The probable direction is for commitPendingTransactions to be able to apply syncs for keys not touched by any currently-persisting transaction, instead of all-or-nothing gating on hasPersistingTransaction.

Test plan

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Tests
    • Added comprehensive test suite for database concurrent transaction behavior with optimistic updates, including baseline scenarios and edge case validation.
    • Identified and documented a known issue with specific concurrent transaction sequences that requires resolution.

Review Change Stack

…s a persisting transaction

Adds `tests/auto-index-snapback.test.ts` with the failing case marked `it.fails`.
See PR description for the full bug write-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

📝 Walkthrough

Walkthrough

A new Vitest test suite validates auto-index snapback behavior during optimistic updates with concurrent in-flight transactions. The suite includes a baseline scenario confirming correct state and live-query updates after optimistic+sync cycles, and a bug-reproduction scenario modeling overlapping transactions where one resolves before buffered sync delivery while another persists.

Changes

Auto-index snapback validation

Layer / File(s) Summary
Test setup and baseline scenario
packages/db/tests/auto-index-snapback.test.ts (lines 1–70)
Imports collection, transaction, and query primitives; defines flushTasks helper for draining async tasks; implements baseline test applying optimistic update with sync and verifying persisted collection state and live-query filter results.
Bug reproduction with overlapping transactions
packages/db/tests/auto-index-snapback.test.ts (lines 71–179)
Models concurrent transactions from rapid successive drags where the first transaction resolves before optimistic shape delivery and the second remains persist-pending. Asserts expected fixed behavior (collection and live-query correctness) with explicit documentation that assertions currently fail pending bug resolution.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • samwillis

Poem

🐰 A test suite hops into the fold,
Twin scenarios—brave and bold,
Snapback snapshots, transactions race,
Optimistic dreaming keeps the pace,
Live queries dance through buggy space. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description lacks the required checklist section and does not confirm testing or changeset generation as specified in the template. Add the required ✅ Checklist section with testing confirmation and 🚀 Release Impact section indicating this is dev-only (no release).
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and specifically describes the main change: adding a test that reproduces a stale buffered sync issue when optimistic snapback races a persisting transaction.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 27, 2026

More templates

@tanstack/angular-db

npm i https://pkg.pr.new/@tanstack/angular-db@1561

@tanstack/browser-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/browser-db-sqlite-persistence@1561

@tanstack/capacitor-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/capacitor-db-sqlite-persistence@1561

@tanstack/cloudflare-durable-objects-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/cloudflare-durable-objects-db-sqlite-persistence@1561

@tanstack/db

npm i https://pkg.pr.new/@tanstack/db@1561

@tanstack/db-ivm

npm i https://pkg.pr.new/@tanstack/db-ivm@1561

@tanstack/db-sqlite-persistence-core

npm i https://pkg.pr.new/@tanstack/db-sqlite-persistence-core@1561

@tanstack/electric-db-collection

npm i https://pkg.pr.new/@tanstack/electric-db-collection@1561

@tanstack/electron-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/electron-db-sqlite-persistence@1561

@tanstack/expo-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/expo-db-sqlite-persistence@1561

@tanstack/node-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/node-db-sqlite-persistence@1561

@tanstack/offline-transactions

npm i https://pkg.pr.new/@tanstack/offline-transactions@1561

@tanstack/powersync-db-collection

npm i https://pkg.pr.new/@tanstack/powersync-db-collection@1561

@tanstack/query-db-collection

npm i https://pkg.pr.new/@tanstack/query-db-collection@1561

@tanstack/react-db

npm i https://pkg.pr.new/@tanstack/react-db@1561

@tanstack/react-native-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/react-native-db-sqlite-persistence@1561

@tanstack/rxdb-db-collection

npm i https://pkg.pr.new/@tanstack/rxdb-db-collection@1561

@tanstack/solid-db

npm i https://pkg.pr.new/@tanstack/solid-db@1561

@tanstack/svelte-db

npm i https://pkg.pr.new/@tanstack/svelte-db@1561

@tanstack/tauri-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/tauri-db-sqlite-persistence@1561

@tanstack/trailbase-db-collection

npm i https://pkg.pr.new/@tanstack/trailbase-db-collection@1561

@tanstack/vue-db

npm i https://pkg.pr.new/@tanstack/vue-db@1561

commit: f2eff04

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (7)
packages/db/tests/auto-index-snapback.test.ts (7)

11-11: ⚡ Quick win

Use descriptive parameter name for the resolve callback.

The parameter r is not descriptive. Consider renaming it to resolve for clarity. As per coding guidelines, prefer descriptive variable names that describe role or purpose.

♻️ Proposed fix
-const flushTasks = () => new Promise((r) => setTimeout(r, 0))
+const flushTasks = () => new Promise((resolve) => setTimeout(resolve, 0))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/db/tests/auto-index-snapback.test.ts` at line 11, Rename the
anonymous resolve parameter in the flushTasks helper to a descriptive name:
change the arrow function new Promise((r) => setTimeout(r, 0)) to use new
Promise((resolve) => setTimeout(resolve, 0)) so the role of the parameter is
clear; update any references inside the promise callback accordingly in the
flushTasks definition.

117-124: ⚡ Quick win

Use descriptive parameter name for the resolve callback.

The parameter r in the Promise constructor is not descriptive. Consider renaming it to resolve for clarity. As per coding guidelines, use descriptive variable names that describe role or purpose.

♻️ Proposed fix
 let resolveT1!: () => void
 const t1 = createTransaction<Row>({
   autoCommit: false,
   mutationFn: async () => {
-    await new Promise<void>((r) => {
-      resolveT1 = r
+    await new Promise<void>((resolve) => {
+      resolveT1 = resolve
     })
   },
 })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/db/tests/auto-index-snapback.test.ts` around lines 117 - 124, Rename
the anonymous Promise constructor parameter `r` to a descriptive name (e.g.,
`resolve`) inside the mutationFn where `resolveT1` is assigned: locate the
createTransaction<Row> block that sets `resolveT1` via `new Promise<void>((r) =>
{ resolveT1 = r })` and change the parameter to `resolve` so it reads `new
Promise<void>((resolve) => { resolveT1 = resolve })` to improve clarity.

37-44: ⚡ Quick win

Use descriptive parameter name for the resolve callback.

The parameter r in the Promise constructor is not descriptive. Consider renaming it to resolve for clarity. As per coding guidelines, use descriptive variable names that describe role or purpose.

♻️ Proposed fix
 let resolveMutation!: () => void
 const tx = createTransaction<Row>({
   autoCommit: false,
   mutationFn: async () => {
-    await new Promise<void>((r) => {
-      resolveMutation = r
+    await new Promise<void>((resolve) => {
+      resolveMutation = resolve
     })
   },
 })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/db/tests/auto-index-snapback.test.ts` around lines 37 - 44, The
Promise constructor inside the mutationFn uses a non-descriptive parameter name
`r`; rename it to `resolve` (or another clear name) to improve clarity: update
the Promise callback in the test where `resolveMutation` is assigned (inside
createTransaction<Row> with autoCommit: false and mutationFn) to use `resolve`
instead of `r`, and ensure the existing `resolveMutation = resolve` assignment
and any references remain correct.

114-114: ⚡ Quick win

Avoid any type in map callbacks.

The map callback parameters are typed as any. Consider providing proper type annotations. As per coding guidelines, avoid using any types and provide proper type annotations.

♻️ Proposed fix
-expect(liveA.toArray.map((r: any) => r.id).sort()).toEqual([`P`, `Q`])
+expect(liveA.toArray.map((r: Row) => r.id).sort()).toEqual([`P`, `Q`])

 // ... later in the test ...

-expect(liveA.toArray.map((r: any) => r.id)).toEqual([])
+expect(liveA.toArray.map((r: Row) => r.id)).toEqual([])

 // ... later in the test ...

-expect(liveA.toArray.map((r: any) => r.id).sort()).toEqual([`Q`])
+expect(liveA.toArray.map((r: Row) => r.id).sort()).toEqual([`Q`])

Also applies to: 147-147, 177-177

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/db/tests/auto-index-snapback.test.ts` at line 114, The test uses an
`any` typed map callback in expressions like `liveA.toArray.map((r: any) =>
r.id)` (also at the other occurrences around the same test), so replace the
`any` with a specific type for the record returned by `toArray` (for example the
entity shape used in this suite, e.g. `{ id: string }` or the actual
model/interface name you have for these rows) or import and use the appropriate
test fixture type; update all three instances (the one using
`liveA.toArray.map`, and the similar lines at the other two locations) so the
callback signature is strongly typed (e.g. `(r: { id: string }) => r.id`)
instead of `(r: any) => r.id`.

28-34: ⚡ Quick win

Avoid any types in query callbacks.

The query parameter types are declared as any. Consider providing proper type annotations or using unknown with type guards for type safety. As per coding guidelines, avoid using any types and provide proper type annotations.

♻️ Proposed fix
 const liveA = createLiveQueryCollection({
-  query: (q: any) =>
+  query: (q) =>
     q
       .from({ p: collection })
-      .where(({ p }: any) => inArray(p.stage, [`A`])),
+      .where(({ p }) => inArray(p.stage, [`A`])),
   startSync: true,
 })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/db/tests/auto-index-snapback.test.ts` around lines 28 - 34, The
query callbacks use unsafe any types (parameters q and the destructured ({ p }:
any) in the createLiveQueryCollection call); replace those anys with concrete
types or unknown plus type guards: give createLiveQueryCollection the proper
generic/document interface (e.g., a Document type that includes stage), type the
query parameter (q) with the library's Query/QueryBuilder generic, and change
the where callback to accept a strongly typed parameter like ({ p }: { p:
Document }) or validate/guard unknown before use so there are no any annotations
in createLiveQueryCollection, query, or the where callback.

68-68: ⚡ Quick win

Avoid any type in map callback.

The map callback parameter is typed as any. Consider providing a proper type annotation. As per coding guidelines, avoid using any types and provide proper type annotations.

♻️ Proposed fix
-expect(liveA.toArray.map((r: any) => r.id).sort()).toEqual([`Q`])
+expect(liveA.toArray.map((r: Row) => r.id).sort()).toEqual([`Q`])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/db/tests/auto-index-snapback.test.ts` at line 68, The map callback
uses the `any` type for `r`; replace it with a concrete type such as `{ id:
string }` (or the actual element interface used by `liveA.toArray()`), e.g.
change `(r: any) => r.id` to `(r: { id: string }) => r.id` (or use the
appropriate model/interface) so the test `expect(liveA.toArray.map((r: any) =>
r.id).sort()).toEqual([`Q`])` avoids `any` while preserving the same behavior.

106-113: ⚡ Quick win

Avoid any types in query callbacks.

The query parameter types are declared as any. Consider providing proper type annotations or using type inference for type safety. As per coding guidelines, avoid using any types and provide proper type annotations.

♻️ Proposed fix
 const liveA = createLiveQueryCollection({
-  query: (q: any) =>
+  query: (q) =>
     q
       .from({ p: collection })
-      .where(({ p }: any) => inArray(p.stage, [`A`])),
+      .where(({ p }) => inArray(p.stage, [`A`])),
   startSync: true,
 })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/db/tests/auto-index-snapback.test.ts` around lines 106 - 113, The
query callback currently uses `any` for the query and predicate parameters (in
createLiveQueryCollection's `query: (q: any) => ...` and `where(({ p }: any) =>
...)`); remove the `any` and either let TypeScript infer types (e.g. `query: q
=> ...` and `where(({ p }) => ...)`) or annotate with the proper
collection/query types used in your codebase (for example a `QueryBuilder<T>` or
the concrete collection row type) so both the `q` parameter in
`createLiveQueryCollection` and the `p` destructured predicate are typed instead
of `any`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/db/tests/auto-index-snapback.test.ts`:
- Line 92: The test declaration for "snapback + buffered sync remains stale
while another transaction is persisting" is currently a regular it(...) and
should be marked as an expected failure; change the test invocation to
it.fails(...) (keeping the same test title string and async callback body) so
Vitest treats this as a known failing test and CI stays green; ensure you only
replace the `it(` call for that test with `it.fails(` and do not alter the test
implementation or assertions inside the async function.

---

Nitpick comments:
In `@packages/db/tests/auto-index-snapback.test.ts`:
- Line 11: Rename the anonymous resolve parameter in the flushTasks helper to a
descriptive name: change the arrow function new Promise((r) => setTimeout(r, 0))
to use new Promise((resolve) => setTimeout(resolve, 0)) so the role of the
parameter is clear; update any references inside the promise callback
accordingly in the flushTasks definition.
- Around line 117-124: Rename the anonymous Promise constructor parameter `r` to
a descriptive name (e.g., `resolve`) inside the mutationFn where `resolveT1` is
assigned: locate the createTransaction<Row> block that sets `resolveT1` via `new
Promise<void>((r) => { resolveT1 = r })` and change the parameter to `resolve`
so it reads `new Promise<void>((resolve) => { resolveT1 = resolve })` to improve
clarity.
- Around line 37-44: The Promise constructor inside the mutationFn uses a
non-descriptive parameter name `r`; rename it to `resolve` (or another clear
name) to improve clarity: update the Promise callback in the test where
`resolveMutation` is assigned (inside createTransaction<Row> with autoCommit:
false and mutationFn) to use `resolve` instead of `r`, and ensure the existing
`resolveMutation = resolve` assignment and any references remain correct.
- Line 114: The test uses an `any` typed map callback in expressions like
`liveA.toArray.map((r: any) => r.id)` (also at the other occurrences around the
same test), so replace the `any` with a specific type for the record returned by
`toArray` (for example the entity shape used in this suite, e.g. `{ id: string
}` or the actual model/interface name you have for these rows) or import and use
the appropriate test fixture type; update all three instances (the one using
`liveA.toArray.map`, and the similar lines at the other two locations) so the
callback signature is strongly typed (e.g. `(r: { id: string }) => r.id`)
instead of `(r: any) => r.id`.
- Around line 28-34: The query callbacks use unsafe any types (parameters q and
the destructured ({ p }: any) in the createLiveQueryCollection call); replace
those anys with concrete types or unknown plus type guards: give
createLiveQueryCollection the proper generic/document interface (e.g., a
Document type that includes stage), type the query parameter (q) with the
library's Query/QueryBuilder generic, and change the where callback to accept a
strongly typed parameter like ({ p }: { p: Document }) or validate/guard unknown
before use so there are no any annotations in createLiveQueryCollection, query,
or the where callback.
- Line 68: The map callback uses the `any` type for `r`; replace it with a
concrete type such as `{ id: string }` (or the actual element interface used by
`liveA.toArray()`), e.g. change `(r: any) => r.id` to `(r: { id: string }) =>
r.id` (or use the appropriate model/interface) so the test
`expect(liveA.toArray.map((r: any) => r.id).sort()).toEqual([`Q`])` avoids `any`
while preserving the same behavior.
- Around line 106-113: The query callback currently uses `any` for the query and
predicate parameters (in createLiveQueryCollection's `query: (q: any) => ...`
and `where(({ p }: any) => ...)`); remove the `any` and either let TypeScript
infer types (e.g. `query: q => ...` and `where(({ p }) => ...)`) or annotate
with the proper collection/query types used in your codebase (for example a
`QueryBuilder<T>` or the concrete collection row type) so both the `q` parameter
in `createLiveQueryCollection` and the `p` destructured predicate are typed
instead of `any`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 16905e74-86cf-4598-ba4c-5ae6051c0c13

📥 Commits

Reviewing files that changed from the base of the PR and between be656be and f2eff04.

📒 Files selected for processing (1)
  • packages/db/tests/auto-index-snapback.test.ts

//
// This test asserts the *correct* behaviour and is expected to FAIL until the bug is
// fixed; the failing CI is intentional.
it(`snapback + buffered sync remains stale while another transaction is persisting`, async () => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add .fails modifier to mark this test as expected to fail.

The PR description states this test should be "marked it.fails", but the code uses a regular it() call. Vitest's .fails modifier is the standard pattern for bug reproduction tests because:

  1. It marks the test as expected to fail, keeping CI green
  2. When the bug is fixed and the test passes, Vitest will fail the test run, prompting you to remove the .fails marker
  3. It clearly documents that this is a known failing case

Without .fails, CI will be red, which blocks other PRs and obscures real failures.

🐛 Proposed fix
-  it(`snapback + buffered sync remains stale while another transaction is persisting`, async () => {
+  it.fails(`snapback + buffered sync remains stale while another transaction is persisting`, async () => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it(`snapback + buffered sync remains stale while another transaction is persisting`, async () => {
it.fails(`snapback + buffered sync remains stale while another transaction is persisting`, async () => {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/db/tests/auto-index-snapback.test.ts` at line 92, The test
declaration for "snapback + buffered sync remains stale while another
transaction is persisting" is currently a regular it(...) and should be marked
as an expected failure; change the test invocation to it.fails(...) (keeping the
same test title string and async callback body) so Vitest treats this as a known
failing test and CI stays green; ensure you only replace the `it(` call for that
test with `it.fails(` and do not alter the test implementation or assertions
inside the async function.

@kevin-dp
Copy link
Copy Markdown
Contributor Author

Closing this issue because the behaviour is expected as we don't provide transactional guarantees across shapes. The problem here comes from the fact that each column has its own shape. And moving an item between different columns is a transactions that span 2 shapes. This means that the tx ID will be observed on both shape streams and when the first one notices it it marks that tx ID as seen but actually we have only seen the changes of that transaction to one of the shapes and not yet for the second shape. So this can cause these kind of snapbacks as optimistic state is being dropped etc.

@kevin-dp kevin-dp closed this May 27, 2026
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.

1 participant