Skip to content

PT-4026: conflict-note card (Accept/Reject with dynamic Result preview)#2484

Merged
rolfheij-sil merged 20 commits into
mainfrom
pt-4026-conflict-note-card
Jul 7, 2026
Merged

PT-4026: conflict-note card (Accept/Reject with dynamic Result preview)#2484
rolfheij-sil merged 20 commits into
mainfrom
pt-4026-conflict-note-card

Conversation

@rolfheij-sil

@rolfheij-sil rolfheij-sil commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

What & why

Part of epic PT-4024 (Donna resolves merge conflicts), stacked on #2482 (the PT-4028 read-contract — this PR's base is pt-4028-conflict-note-fields). It adds the presentational conflict-resolution card so a translator can see what is rejected, what is accepted, and how the verse will read after their choice (NN3), and accept or reject it (NN4).

ConflictNoteCard — a body fragment in the shared comment-list lib — renders:

  • a conflictType-keyed description,
  • an Accept/Reject selector,
  • Rejected & Accepted diff regions (PT9 <u>/<s> markup, colored via theme tokens),
  • a read-only Result preview that tracks the selectionresultText on accept, rejectedResultText on reject.

PT9-anchored behavior

Matches PT9's AppendConflictNoteDetails: the structured card renders when conflictType === 'verseText' && !!resultText; the Accepted region is shown only when acceptedText is present. So no-common-ancestor conflicts — two translators independently drafting a previously-absent verse, which have no accepted-side diff — still get the full resolution UI, just without the Accepted panel (the selector + dynamic Result preview still work). Genuinely non-verseText notes fall back to the existing contents render.

The dynamic Result preview relies on rejectedResultText (the loser's plain USFM via PT9's public GetDiffVerseUsfm), which was added to the read-contract in the base PR #2482 (along with a no-ancestor converter test and softened acceptedText docs).

Scope

  • Presentation only — no PAPI, no live data. The card emits the choice via onResolutionChange; canAcceptReject gates the selector.
  • verseText conflicts only (v1).
  • Integration (rendering conflict threads with this card, wiring resolve to PT-4029, contributing localized string values) is PT-4030. ConflictNoteCard + CONFLICT_NOTE_STRING_KEYS are exported from platform-bible-react for that ticket.

Testing

  • Vitest: 3-region render, dynamic Result toggle (including a real Radix Select interaction firing onResolutionChange), the no-ancestor case (Accepted omitted, selector + Result still work), non-verseText fallback, disabled selector, and read-only Result.
  • Storybook (Advanced/ConflictNoteCard): Default, NoAncestor, RestrictedPermissions. Sample field values are captured verbatim from the PT-4028 C# converter — not hand-written.

🤖 Generated with Claude Code


This change is Reviewable


Review summary (/code-review max + /review-paratext, 2026-07-03)

Reviewed with a max-effort multi-agent /code-review (32 agents → 15 distinct defects) plus the four
/review-paratext analyzers. All Critical and Important findings were fixed in-review (commit
9f0c52a). Branch is green: typecheck, lint, format:base:check, unit 230/230, Storybook 3/3, C#
23/23. Full findings (incl. dismissed/deferred) are in the GitHub review on this PR.

⚠️ Supersedes "PT9-anchored behavior" above: the structured card now renders when
conflictType === 'verseText' alone (the && !!resultText gate was removed). A two-agent spike
against PT9 source confirmed PT9 gates only on isFirstComment && Contents?.FirstChild != null and
never on result-emptiness; that blank-resultText state is not even producible from a real merge
(a deleted verse creates no note; an emptied verse keeps its \v N marker). The card now degrades
gracefully per-region instead of hiding the whole resolution UI.

Key changes made in review:

  • Robustness / PT9 parity: gate on conflictType alone; gate the Rejected region on
    !!rejectedText (like Accepted); neutral empty-state in the Result preview when the chosen side has
    no result USFM.
  • Ship-blockers: regenerated the tracked platform-bible-react dist/ (new exports were missing);
    fixed the prettier failure that would have red-CI'd the branch.
  • Dark-mode contrast: new per-scheme --diff-deleted theme token (deletion text was
    background-grade --destructive, failing AA in Platform dark).
  • Localization: frozen as const key tuple + derived type + optional localizedStrings prop
    (guide-conforming); keys renamed to camelCase %conflictNote_*% before PT-4030 locks values in.
  • A11y / quality: aria-live on the Result preview; shared prose classes with CommentItem;
    controlled-mode state-leak guard; min-w selector; renamed the both-sides C# fixture/test/sample;
    added edge-case + regression tests (11 unit tests).

Dismissed: whitespace-only-diff invisibility (degenerate conflict); missing pr-twp (family
consistency). Deferred to PT-4030: reset uncontrolled state on comment change (key the card by
comment id) + contributing the 9 %conflictNote_*% string values. Follow-up: shared jsdom test
setup (4th copy).

@rolfheij-sil rolfheij-sil left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Automated code review — /code-review max + /review-paratext

Reviewed 2026-07-03 with a max-effort multi-agent /code-review (32 agents, 36 verified findings → 15 distinct) and the four /review-paratext analyzers (api / style / compliance / ux). Where I1/I2/I3 hinged on PT9 behavior, a two-agent spike established ground truth against PT9 source (ParatextBase/ParatextData) and the PT10 converter before any behavior change.

Everything found is listed below — including items dismissed or deferred — with how each was handled. All Critical and Important findings were fixed in-review (commit 9f0c52a). Branch is green: typecheck · eslint · format:base:check · Vitest unit 230/230 · Storybook (chromium) 3/3 · C# PlatformCommentConverterTests 23/23. The tracked dist/ and themes.data.json were regenerated/synced.

Critical

# Finding Handling
C1 Stale tracked dist/ — new exports were added to src/index.ts but the git-tracked build output consumers resolve was not rebuilt, so PT-4030's import { ConflictNoteCard } would fail TS2305 / crash at runtime; no CI step catches it. Fixed — regenerated platform-bible-react dist; exports now present in dist/index.d.ts/index.js.

Important

# Finding Handling
I1 !!resultText gate hid the entire resolution UI when the merged result verse is blank, diverging from PT9 (AppendConflictNoteDetails gates only on isFirstComment && Contents?.FirstChild != null, never on result-emptiness). Fixed — gate on conflictType === 'verseText' alone. Spike finding: the trigger state is not producible from real merge data (a deleted verse creates no note — BookFileMerger.cs:441-445; an emptied verse keeps its \v N marker so Comment.Verse is non-blank). So this was a defensive-but-wrong gate, not a live stranding bug — but the fix restores PT9 parity and hardens the card.
I2 Rejected region heading rendered unconditionally → orphan "Rejected" heading over an empty body when rejectedText is absent. Fixed — gated on !!rejectedText, symmetric with the Accepted region (matches PT9, which prints the reject label only when the rejected side is non-null).
I3 Blank <p> in the Result preview when Reject is selected and rejectedResultText is absent. Fixed — neutral empty-state string ("The verse will be empty.") instead of an empty paragraph.
I4 Deletion text below AA contrast in Platform dark mode<s> used background-grade --destructive (~2–2.5:1), while insertions used the text-grade --success-foreground. Fixed — added a per-scheme text-grade --diff-deleted token (red-600 light / red-400 dark) in index.css; themes.data.json auto-synced; <s> now uses it.
I5 CI prettier/format gate would fail on the component + sample data (root --check misses it via .prettierignorerun; the lib workspace check exits 1). Fixed — reformatted; whole-lib format:base:check passes.
I6a Localization contract deviated from the Localization-Guide — mutable LocalizeKey[] + required localizedStrings prop (guide/newer components use a frozen tuple + derived type + optional prop). FixedObject.freeze([...] as const), derived ConflictNoteCardLocalizedStrings, optional prop.
I6b Snake_case key prefix %conflict_note_*% (guide prefers camelCase; keys are immutable once shipped). Fixed — renamed to %conflictNote_*% before PT-4030 contributes values.
I6c ?? 'English' fallbacks are dead through the documented hook path (useLocalizedStrings returns missing keys as the key itself). Addressed by the contract change; the fallbacks remain a defensive default. Contributing string values is PT-4030's job.
I7 Fallback path skipped trimDiffSpanWhitespace while styling with diff classes → potential dangling strikethrough. Obsoleted by the I1 fix — the fallback now renders only non-verseText conflicts, which carry message-only contents with no <s>/<u> markup.
I8 Result preview not announced to assistive tech — no aria-live; screen-reader users don't hear the Accept↔Reject swap. Fixedaria-live="polite" region + aria-labelledby to the Result label, keyed with useId for list uniqueness.
I9 DIFF_HTML_CLASSES copy-pasted CommentItem's prose/blockquote stack — silent drift risk. Fixed — extracted COMMENT_BODY_PROSE_CLASSES; both consume it.
I10 Confusing C# fixture naming — new CreateVerseTextConflictCommentWithReplacement vs base's …Replacement; TS sample named after the wrong fixture. Fixed — renamed the fixture, its test, and the sample to …ReplacementBothSides.
I11 Missing edge-case tests for the new gating/absent-field logic. Fixed — added tests for verseText-without-resultText, absent-rejectedText, absent-rejectedResultText-on-reject, aria-live, and the controlled-mode state-leak (11 unit tests total).

Minor

# Finding Handling
M1 Whitespace-only diff invisibletrimDiffSpanWhitespace collapses <s> </s> to an empty element. Dismissed — a whitespace-only conflict is degenerate; the trim is deliberate for the common case.
M2 Controlled-mode internal-state leakhandleChange wrote internalResolution even when controlled, so a declined choice could resurface if the parent later stopped controlling. Fixed — write internal state only when uncontrolled; test added.
M3 Uncontrolled state not reset on comment change — a reused instance leaks the prior conflict's choice. Deferred to PT-4030 — the list integration should key the card by comment id (not a presentational-card concern).
M4 w-32 clips long localized selector values (no tooltip fallback). Fixedmin-w-32 keeps the stable width without clipping.
M5 Missing pr-twp on the root element. Dismissed — the whole comment-list family omits it and the card only renders inside CommentList; matching the family is correct.
M6 4th verbatim copy of the jsdom NoopResizeObserver/pointer stubs across test files. Deferred to a follow-up — a shared setupFiles touches 4 test files + vitest config, out of scope here.
M7 "captured verbatim / do not hand-edit" JSDoc contradicted by hand-renamed id/thread. Fixed — JSDoc now scopes the verbatim claim to the HTML/USFM fields and notes the readability rename.

The workflow also surfaced ~6 additional lowest-severity cleanup candidates that fell under its 15-decision cap; the ones that mattered are captured above (I9, M6).

Positive observations

PT9 anchoring verified against real PT9 source (region order, accepted-region gating, default 'accept', permission gating); purely additive public API with all referenced types exported; DOMPurify on every HTML injection; RTL-safe logical properties; redundant color encoding (semibold/strikethrough) for color-blind users; strong verified-green tests including a real Radix Select interaction; sample data cross-anchored to a matching C# fixture + converter test.

Suggested reviewer focus

  • The I1 gate change (drop && !!resultText) — confirm you agree it's the right PT9-parity call; the spike concluded the blank-resultText state is unproducible from real merges.
  • The new --diff-deleted theme token — verify the red-600/red-400 values and that nothing else expected <s> to use --destructive.
  • The localization key rename (%conflictNote_*%, camelCase) — effectively immutable once PT-4030 ships values.

Automated review by Claude Fable 5 (analysis) and Claude Opus 4.8 (spike + fixes).

Base automatically changed from pt-4028-conflict-note-fields to main July 6, 2026 09:43
@rolfheij-sil
rolfheij-sil force-pushed the pt-4026-conflict-note-card branch from 9f0c52a to af2987e Compare July 6, 2026 09:54
rolfheij-sil and others added 15 commits July 7, 2026 16:33
…PT9)

Gate the structured verseText render on resultText presence (not acceptedText),
matching PT9's per-block conditional. When acceptedText is absent (no common
ancestor conflict), the Accepted region is omitted but the selector, Rejected
region, and Result preview all remain functional. Adds a test for this case.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ey9TG13YAyRhgqmKEk8BC
…oose aria-label

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ey9TG13YAyRhgqmKEk8BC
…estor, restricted)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ey9TG13YAyRhgqmKEk8BC
Address final-review minors:
- Export ConflictNoteCard, CONFLICT_NOTE_STRING_KEYS, and its prop types from
  platform-bible-react's index.ts so the extension can consume the card and pass
  the string keys to useLocalizedStrings (unblocks PT-4030 integration).
- Gate the Accepted region on `!!acceptedText` (was `!== undefined`) for
  consistency with the resultText gate and empty-string safety.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Diff highlight now matches PT9's default RedGreen style (by insertion/deletion,
  same in both regions): inserted text green (text-success-foreground) + bold,
  deleted text red (text-destructive) + strikethrough. Theme tokens only.
- Result preview rendered in normal text-foreground (was muted) — it's the
  most important line ('how the verse will read'), so no longer de-emphasized.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add CreateVerseTextConflictCommentWithReplacement fixture (town→village/city),
a converter test that asserts both <s> (deletion) and <u> (insertion) appear in
rejectedText/acceptedText, and the verbatim-captured verseTextConflictReplacementSample
for the ConflictNoteCard Default story — making struck-red text visible in Storybook.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ey9TG13YAyRhgqmKEk8BC
…tyle comment

- trimDiffSpanWhitespace: move each diff span's trailing space outside </s>/</u> so the
  strikethrough/color stops at the word instead of dangling into the inter-word gap. PT9 keeps
  the space inside the diff token and masks it with a background highlight; our text-only
  decoration would otherwise show a stray strike over the space. + regression test.
- Correct the code comment: our green/red is a text-color treatment, NOT PT9's default (BlueGray)
  or RedGreen (both use background highlights; RedGreen has no strikethrough). Insertions now
  semibold (was medium, while the comment said bold).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rder)

PT9's AppendConflictNoteDetails renders the accepted/current text first, then the rejected
side. Leading with the winning text reads more naturally given Accept is the default resolution.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolves the /code-review max + /review-paratext findings on PT-4026:

- Gate the resolution UI on conflictType alone (PT9 parity) and degrade
  gracefully for absent result/rejected fields (empty-state, gated Rejected
  region) instead of hiding the whole card.
- Add a per-scheme --diff-deleted theme token so deletion (<s>) text meets
  contrast in the Platform dark theme; sync themes.data.json.
- Align the localization contract to the guide (frozen tuple, derived type,
  optional prop) and rename keys to camelCase %conflictNote_*%.
- Announce the Result preview via aria-live; share prose classes with
  CommentItem; guard controlled-mode internal state; min-w selector.
- Rename the both-sides C# replacement fixture/test and the TS sample.
- Regenerate platform-bible-react dist; add edge-case + regression tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JudLsqrCKzyxk2a3GmrG3A
…d contract

Folded here from the PT-4028 stack base (#2482, now merged) so that PR could
merge un-rebased; these correct code #2482 introduced.

- perf: PlatformCommentThreadWrapper.RootCommentId is read once per comment (and
  several more times per conflict root), so recomputing its scan on every read
  made serializing an n-comment thread O(n^2). Memoize it once per thread wrapper
  (invalidated on dedup merge) and use MinBy instead of OrderBy().First() (O(n),
  identical first-on-ties tie-break).
- test: Serialize_VerseTextConflict_IncludesRejectedAcceptedResultText asserted
  key PRESENCE only and read values through the wrapper getters (bypassing the
  converter), so a converter key<->value mis-binding would ship green. Assert
  each serialized value equals its getter.
- docs: LegacyComment TSDoc said these fields live on the "first comment"; after
  thread-fragment dedup the root is not necessarily comments[0]. Document that
  they live on the root (earliest-date) comment and how to locate it.

Skipped the fourth follow-up finding (verse/resultText blank divergence):
verified unreachable for genuine merges.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014CKj4FJgk9NrFtPioQbiGD
…eld doc updates

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014CKj4FJgk9NrFtPioQbiGD
Pre-existing line over the 100-col limit at the
CreateVerseTextConflictCommentReplacementBothSides call site; surfaced while
folding the review fixes onto this branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014CKj4FJgk9NrFtPioQbiGD
…to main

The rebase auto-resolved the generated dist bundles by taking the branch
side; rebuilt platform-bible-utils + -react so dist reflects the merged
source (main + ConflictNoteCard). Source (src/index.ts barrel) merged cleanly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ZurpFvP3QbVr71jzdtwKA
@rolfheij-sil
rolfheij-sil force-pushed the pt-4026-conflict-note-card branch from af2987e to 063d203 Compare July 7, 2026 14:42
@rolfheij-sil
rolfheij-sil enabled auto-merge (squash) July 7, 2026 14:43

@lyonsil lyonsil left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Follow-up review pass (automated multi-agent review, human-curated). A few non-blocking notes inline — one correctness edge case around the Result empty-state, the rest test-coverage / cleanup / consistency. None block merge for this PR's presentation-only scope.

@lyonsil lyonsil left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Claude found a few things, and I didn't see anything else in my scan of the changes. I ignored some of the Claude findings and only posted the ones that seemed potentially valid. None of them are really serious, but the ones I posted seem considering at least.

@lyonsil reviewed 22 files and all commit messages, and made 1 comment.
Reviewable status: all files reviewed, 6 unresolved discussions (waiting on rolfheij-sil).

rolfheij-sil and others added 4 commits July 7, 2026 22:59
Follow-up review pass on PR #2484. Non-blocking items:

- Result empty-state no longer asserts "The verse will be empty." It now
  reads a neutral "No result preview available." The chosen side's result
  USFM can be absent for two indistinguishable causes (the outcome decodes
  to an empty verse, or the note carries no decodable diff, per the
  rejectedResultText TSDoc), so the card must not assert an emptiness it
  cannot confirm. Renamed the key %conflictNote_resultEmpty% to
  %conflictNote_resultUnavailable% to match; values are not yet shipped
  (PT-4030 contributes them).
- Extracted DiffRegion, collapsing the three duplicated
  dangerouslySetInnerHTML + eslint-disable blocks into one audited
  sanitize+inject sink. Sanitize/trim now run inside it, so a card only
  parses the regions it actually renders (no discarded DOMPurify parse).
- Applied ConflictNoteCardLocalizedStrings to the localizedStrings prop and
  re-exported it from index.ts, so consumers get key-level type safety
  instead of the loose LanguageStrings.
- Gave the description key a sentinel test value plus an assertion, so a
  dropped or mistyped localization key fails the test instead of silently
  falling through to the fallback and staying green.

Declined the symmetric --diff-inserted token (defensible asymmetry, cosmetic).

typecheck / eslint / prettier / unit 11-11 all pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjmXr4Qf6zHm37WCVCFQNj
Rebuild after the review-feedback source changes: adds the ConflictNoteCardLocalizedStrings export and the renamed %conflictNote_resultUnavailable% key to the tracked build output.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjmXr4Qf6zHm37WCVCFQNj
…d type

Follow-up to the review note that CommentListLocalizedStrings was defined but
inert. COMMENT_LIST_STRING_KEYS was typed LocalizeKey[], so
(typeof KEYS)[number] collapsed to LocalizeKey and the mapped type was
effectively LanguageStrings (no key-level safety), and the type was not
re-exported, so consumers could not import it.

- Froze the keys as a const tuple (Object.freeze([...] as const)), matching the
  sibling components, so the derived type carries the literal key union.
- Re-exported CommentListLocalizedStrings from index.ts.

Left the three localizedStrings props as LanguageStrings on purpose: the
CommentList family indexes keys without fallbacks, so a required mapped type
breaks internal access, while an optional one rejects the index-signature
LanguageStrings that useLocalizedStrings returns (TS2740). The sibling
components (e.g. comment-editor) resolve this the same way: export the type for
consumers, keep the permissive prop. Both consumers of the keys
(legacy-comment-manager) funnel through Array.from, so the readonly tuple is
transparent to them.

lib typecheck / eslint / prettier / comment unit tests (17) all pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjmXr4Qf6zHm37WCVCFQNj
Rebuild after making CommentListLocalizedStrings a real exported type: adds the export and the readonly-tuple COMMENT_LIST_STRING_KEYS to the tracked build output.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjmXr4Qf6zHm37WCVCFQNj

@lyonsil lyonsil left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

:lgtm:

@lyonsil reviewed 13 files and all commit messages, made 1 comment, and resolved 6 discussions.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved.

@rolfheij-sil
rolfheij-sil merged commit d4d69f0 into main Jul 7, 2026
7 checks passed
@rolfheij-sil
rolfheij-sil deleted the pt-4026-conflict-note-card branch July 7, 2026 22:00
rolfheij-sil added a commit that referenced this pull request Jul 8, 2026
Brings the updated pt-4029 (now merged with main, which includes PT-4026 #2484's
original conflict-note card) into pt-4030.

Conflict resolution — pt-4030's conflict-note-card is the later iteration
(Accept/Reject/Merge + live resolve) and supersedes #2484's, so pt-4030's version
was kept for the card component/types/stories/tests, comment-item, sample data,
and the C# test fixtures.

What #2484 had that was dropped (nothing lost silently):
- CreateVerseTextConflictCommentReplacementBothSides + its converter test have
  identical pt-4030 equivalents under a different name (...WithReplacement) — no
  coverage lost.
- ConflictNoteCardLocalizedStrings — superseded by pt-4030's generic LanguageStrings.
- COMMENT_BODY_PROSE_CLASSES — main's shared prose-class constant is left unused for
  now (pt-4030 keeps inline classes; identical rendering). Adopting it resolves the
  M8 prose-class duplication — tracked as a follow-up.
- Both libraries' dist regenerated from the merged source.

Verified: platform-bible-react tsc/build clean; comment-list tests 66/66;
c-sharp-tests 1382 pass.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Session-URL: https://claude.ai/code/session_015hfnmegSuB5vVmfNCvAtqk
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.

2 participants