Repository navigation
PT-4730: Limit how much text can be copied from a chapter - #2868
Conversation
katherinejensen00
left a comment
There was a problem hiding this comment.
Full review sent directly to avoid sharing potentially sensitive info.
@katherinejensen00 reviewed 54 files and all commit messages, and made 2 comments.
Reviewable status: all files reviewed, 1 unresolved discussion (waiting on irahopkinson).
extensions/src/platform-scripture-editor/src/copy-limit/resolve-copy-limit.util.ts line 18 at r1 (raw file):
}): number | undefined { if (isLoading || isPlatformError(value)) return 0; return value?.[chapterNum] ?? undefined;
2. Chapter 0 — book front matter — is copyable without limit
Severity: Medium (fail-open)
Attach to: extensions/src/platform-scripture-editor/src/copy-limit/resolve-copy-limit.util.ts:18 — return value?.[chapterNum] ?? undefined;
Index 0 is documented unused on both sides, so value[0] is always a hole, and a hole resolves to undefined — no limit — in a design where every other unknown resolves to 0.
chapterNum: 0 is reachable, and your own code says so. platform-scripture-editor.utils.ts:1558-1571: "It is reachable rather than theoretical: calculateTopMatch('GEN 0') yields chapterNum: 0 and handleTopMatchSelect passes it through with ?? 1, which a 0 survives." The same file notes chapter 0 addresses \id, \h, \toc, \mt, \ip — "real content the reader can see", and substantial in study Bibles and commentaries. There is a front-matter branch at platform-scripture-editor.web-view.tsx:3737.
The C# side already guards this: LimitAt is limits != null && chapter >= 1 && chapter < limits.Length. The TS side dropped the same bound. Same hole for any chapterNum > GetLastChapter(book).
Note that resolve-copy-limit.util.test.ts:20 currently pins the permissive behaviour (chapterNum: 9 → toBeUndefined()), so the test will need to change with the fix.
0866858 to
23ddc3e
Compare
irahopkinson
left a comment
There was a problem hiding this comment.
Thanks, Katherine, and thanks for sending the details privately. The PR is rebased onto main. There's now a lockfile commit (the editor's new unicode-segmenter dependency) and a commit addressing your review. Using your numbering:
- 1: the versification mapper is gone. Limits are indexed like the chapter-text endpoints, by the project's own chapter numbers, so the limit and the text always match. 14 and 21 go with it.
- 2: chapter 0 now uses chapter 1's limit. See the thread on it.
- 6 and 20:
ResourceCopyLimit's remarks state the length and don't-change-it-later obligations. There's a per-provider test seam, and a reflection test that pins the frozen signature. - 10:
ICopyLimitProjectDataProviderdeclares its members, with TSDoc. - 11:
ExpandDataUpdateScopenow takes and returnsList<string>. The base class decodes"*", blank and empty scopes before the hook. It's out of the Scripture region, and it's recorded as a pattern inadr-data-update-scope-hook. - 16: a footnotes-pane copy that fits is now left to the browser. Note that you can't copy from the footnotes pane in the app at the moment, though, because the selection is dropped as soon as it's made. That's the same on
main, so it's a separate, pre-existing bug. The change is covered by unit tests. - 4: confirmed covered. A new test shows a copy spanning two cells is shortened by the editor's page guard.
- 9, 17, 18 and 23: done: the segmentation contract, a shared reset of the lookup cache, the zoom test pinning
contentRef, and the grid cell's corrected loading flag. - 8 and 15: the description wording is fixed.
- 3, 5, 12 and 13: these are traced in code but not observed, so I've left them for now. We'll fix any lookup-failed case we find, which also takes
0back to meaning only "loading" or "a real limit" (your point in 19). I've skipped 19's new message, because the Product Owner chose silence. - 7: the editor blocks a cut at
0, so nothing is removed. - 22: left as it is, at about 1 ms per subscriber per write.
- 24: the unit wording, the
try/catchguards, the cataloglocationsand the test comment are done. The other nits are left.
The CI will fail until platform-yalc moves. I'll do that once this is approved.
@irahopkinson+AI made 2 comments.
Reviewable status: 18 of 61 files reviewed, 1 unresolved discussion (waiting on katherinejensen00).
extensions/src/platform-scripture-editor/src/copy-limit/resolve-copy-limit.util.ts line 18 at r1 (raw file):
Previously, katherinejensen00 wrote…
2. Chapter 0 — book front matter — is copyable without limit
Severity: Medium (fail-open)
Attach to:extensions/src/platform-scripture-editor/src/copy-limit/resolve-copy-limit.util.ts:18—return value?.[chapterNum] ?? undefined;Index 0 is documented unused on both sides, so
value[0]is always a hole, and a hole resolves toundefined— no limit — in a design where every other unknown resolves to0.
chapterNum: 0is reachable, and your own code says so.platform-scripture-editor.utils.ts:1558-1571: "It is reachable rather than theoretical:calculateTopMatch('GEN 0')yieldschapterNum: 0andhandleTopMatchSelectpasses it through with?? 1, which a 0 survives." The same file notes chapter 0 addresses\id,\h,\toc,\mt,\ip— "real content the reader can see", and substantial in study Bibles and commentaries. There is a front-matter branch atplatform-scripture-editor.web-view.tsx:3737.The C# side already guards this:
LimitAtislimits != null && chapter >= 1 && chapter < limits.Length. The TS side dropped the same bound. Same hole for anychapterNum > GetLastChapter(book).Note that
resolve-copy-limit.util.test.ts:20currently pins the permissive behaviour (chapterNum: 9→toBeUndefined()), so the test will need to change with the fix.
Thanks, fixed. Chapter 0 (the introduction) is shown as the start of chapter 1, so while the cursor is in it the views now use chapter 1's limit. That's in resolveCopyLimit, with a test for chapter 0 that failed before the fix. A chapter past the end of the book has no text, so a missing entry there still means no limit, and the permissive test is split to say so.
katherinejensen00
left a comment
There was a problem hiding this comment.
Thanks for making those changes, Ira!
@katherinejensen00 reviewed 43 files and all commit messages, made 1 comment, and resolved 1 discussion.
Reviewable status:complete! all files reviewed, all discussions resolved.
Adds the public mechanism for a per-chapter copy limit. The public build answers "no limit" everywhere; a downstream implementation supplies the answer. - C#: `ResourceCopyLimit.GetBookCopyLimits` placeholder (the seam a downstream patch replaces) exposed as a new read-only project interface, `platformScripture.CopyLimit` / `BookCopyLimits`, counted in UTF-16 code units. The selector names the book and the caller's versification; `CopyLimitVersificationMapper` re-indexes the per-project-chapter answer into that versification, mapping in both directions and across books, and taking the smallest limit when a requested chapter spans several project chapters. Subscribers are refreshed with every update the provider sends, through a new overridable `DataProvider.ExpandDataUpdateScope` hook. - Views: `useChapterCopyLimit` resolves the chapter's limit from the project's copy-limit provider (0 while unknown or failed), and `useIsProjectDataLoading` keeps it at 0 until the chapter text on screen matches it; `useChapterUsjWithCopyLimit` combines them with the chapter text for the resource text panel and grid cells. The Scripture editor (including its footnotes pane and the footnote editor's Copy button), resource text panel, Scripture Text Grid cells (including the grid's own right-click Copy) and model text panel pass it to the editor as `EditorOptions.copyLimit`. - `truncateToCopyLimit` in platform-bible-utils cuts plain-text copies at character-cluster boundaries (unicode-segmenter). - Keyboard shortcuts catalog entry for the blocked Select All shortcut. - ADR `adr-copy-limit-rule-lives-downstream`. Requires the `EditorOptions.copyLimit` release of the scripture-editors platform editor. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
C# data provider
- BookCopyLimits is indexed like the chapter-text endpoints, by the
project's own chapter numbers: GetBookCopyLimits passes only the book
to ResourceCopyLimit.GetBookCopyLimits and returns its answer as it is.
The selector's versification, chapter and verse are ignored.
CopyLimitVersificationMapper and its tests are removed.
- GetBookCopyLimits calls the seam through an internal
BookCopyLimitsSource property, so tests pin the book it asks for and
that it returns the seam's answer unchanged; another test pins that
the property defaults to ResourceCopyLimit.GetBookCopyLimits.
- ResourceCopyLimit's remarks state what an implementation owes: an
array covering every chapter of the book, and one it will not change
after returning it. A reflection test pins the class's name and
namespace and the method's name, static-ness, parameter types and
nullable return type.
- DataProvider.ExpandDataUpdateScope takes and returns List<string>.
SendDataUpdateEventAsync decodes the scope first: "*" is sent without
reaching the hook, nothing is sent for a blank data type or an empty
list (before or after the hook), and the hook gets a copy of the
caller's list. New tests cover the decoding. ParatextProjectDataProvider's
override moves to the provider-wide region and chains to the base.
Views
- A reference in chapter 0 takes chapter 1's limit, since chapter 0 is
shown as the start of chapter 1. A chapter past the end of the book
has no entry, and so no limit, like any other missing entry.
- useChapterCopyLimit subscribes once per book, whatever the reference's
versification.
- The footnotes pane lets a copy within the limit through natively, with
all its clipboard formats; only a copy that needs shortening is
written, as plain text, and one with no clipboard data stays blocked.
- useChapterUsjWithCopyLimit also returns isChapterTextLoading, and the
Text Collection cell derives its state from it, so a cell never shows
the previous chapter's or resource's text as ready.
- The grid cell's selection reader returns '' for a selection it cannot
read, so the right-click menu still opens with Copy disabled.
- The footnote editor's Copy button awaits the clipboard write and logs
a failure instead of leaving it unhandled.
- New test: a copy whose selection runs from one Text Collection cell
into another, each with a real editor and a limit, is shortened to
the smaller limit.
Types, docs and tests
- ICopyLimitProjectDataProvider declares getBookCopyLimits, the
always-rejecting setBookCopyLimits and subscribeBookCopyLimits, with
TSDoc, including what a limit of 0 means.
- truncateToCopyLimit's TSDoc states its segmentation contract.
- Limits are described in UTF-16 code units in the footnotes pane and
grid cell prop docs and in the ADR.
- ADR: adr-copy-limit-rule-lives-downstream records the indexing and
chapter 0; new entry adr-data-update-scope-hook.
- A shared copy-limit test util resets the lookup cache before each
test in the grid cell and resource text panel suites.
platform-scripture-editor's tsconfig excludes *.test-utils files, as
platform-scripture's does, since they import vitest.
- The zoom contract test pins ref={contentRef} in both cell layouts.
- The keyboard shortcuts catalog entry lists
use-chapter-usj-with-copy-limit.hook.ts.
- Rebuilt platform-bible-utils and platform-bible-react dist.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
23ddc3e to
3bc290c
Compare
Main, since the branch was last rebased, added a `contentRef` prop to the grid cell view and an editable row editor to the footnotes pane: - The grid cell view's own copy ref is now `copyAreaRef`; the header layout's content area takes it and the caller's `contentRef` both. The first commit does not typecheck on its own without this (squash-merged). - The footnotes pane's row editor gets the copy-limited editor options, as the footnote popover's editor does. - The footnotes pane leaves alone a copy a row editor has already handled with its own copy guard, rather than rewriting it as plain text. - The copy-limit wiring and Text Collection zoom-marker contract tests follow: both footnote editors take the copy-limited options, and the cell's scroll box takes `copyAreaRef`. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
3bc290c to
40ee822
Compare
irahopkinson
left a comment
There was a problem hiding this comment.
@irahopkinson+AI made 1 comment.
Reviewable status: 27 of 67 files reviewed, 1 unresolved discussion (waiting on irahopkinson and katherinejensen00).
a discussion (no related file):
Thanks for the approval, Katherine. main had moved a long way while this waited on platform-yalc, so I've rebased it and force-pushed, which is why your files show as unreviewed again. Nothing in the first two commits changed beyond what the rebase needed. The one exception is the footnotes pane, which #2805 restructured, so our copy handling there was ported into its new FootnotesPane component rather than merged.
What's new is one commit on top, 40ee822631c, fitting the copy limit to what main changed:
- Grid cell view:
mainadded acontentRefprop. The view's own copy ref is nowcopyAreaRef, and the header layout's content area takes both. - Footnotes pane row editor (new in #2805): it gets the copy-limited editor options, as the popover's editor does.
- Footnotes pane: it leaves alone a copy a row editor has already handled with its own copy guard, instead of rewriting it as plain text.
The lockfile commit dropped out, since main already has that entry. The PR description has a new "Changes after the rebase" section with the same detail. Could you take a look at that commit and the footnotes pane port?
irahopkinson
left a comment
There was a problem hiding this comment.
@irahopkinson+AI made 1 comment.
Reviewable status: 27 of 67 files reviewed, 1 unresolved discussion (waiting on irahopkinson and katherinejensen00).
a discussion (no related file):
Previously, irahopkinson+AI (Agent AI for Ira Hopkinson) wrote…
Thanks for the approval, Katherine.
mainhad moved a long way while this waited onplatform-yalc, so I've rebased it and force-pushed, which is why your files show as unreviewed again. Nothing in the first two commits changed beyond what the rebase needed. The one exception is the footnotes pane, which #2805 restructured, so our copy handling there was ported into its newFootnotesPanecomponent rather than merged.What's new is one commit on top,
40ee822631c, fitting the copy limit to whatmainchanged:
- Grid cell view:
mainadded acontentRefprop. The view's own copy ref is nowcopyAreaRef, and the header layout's content area takes both.- Footnotes pane row editor (new in #2805): it gets the copy-limited editor options, as the popover's editor does.
- Footnotes pane: it leaves alone a copy a row editor has already handled with its own copy guard, instead of rewriting it as plain text.
The lockfile commit dropped out, since
mainalready has that entry. The PR description has a new "Changes after the rebase" section with the same detail. Could you take a look at that commit and the footnotes pane port?
This auto-merged on your earlier approval just before my comment went out, so the new commit hasn't had your eyes on it. A look is still very welcome. If you spot anything, I'll fix it in a follow-up PR.
Resolves resource-reference.utils.ts, where this branch's isSameResource and PT-4730's getModelResourceProjectId (#2868) were added at the same place: both kept. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ollows-versification Resolve the conflict with PT-4730's copy-limit tests (#2868) in resource-text-panel.web-view.test.tsx. The panel now follows the scroll group through useScrollGroupScrRef with the resource's project id, so the copy-limit helper sets the chapter on that mock instead of through the useWebViewScrollGroupScrRef prop, which the panel no longer reads. Without that, "blocks copying on the render that changes chapter" never changed chapter and failed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks, @irahopkinson. I looked at 40ee822 and the footnotes pane port against the head I approved. The port is faithful, the defaultPrevented gate is sound (a selection reaching outside the row editor still falls to the pane), and the copyAreaRef/contentRef split matches where main put contentRef. Nothing needs a follow-up from me. |
Code Review Summary
Branch: pt-4730-resource-copy-limit
Base: origin/main
Date: 2026-09-28, updated 2026-09-30 after review
Review model: Claude Opus 5.5
Files changed: 54 (9 generated: the
lib/platform-bible-reactandlib/platform-bible-utilsdist/files)Overview
PT-4730 protects the rights of text owners by limiting how much text can be copied at once from protected resource texts, as P9 does. This PR is the second of a set of three:
EditorOptions.copyLimit.Here the limit is only enabled, never applied: the public
ResourceCopyLimitalways answers "no limit".The data type: a new project interface,
platformScripture.CopyLimit, with a read-onlyBookCopyLimitsdata type. Its selector names a book. It returns the per-chapter limits in UTF-16 code units, indexed like the chapter-text endpoints, by the project's own chapter numbers.The C# side:
ParatextProjectDataProviderserves it straight fromResourceCopyLimit. Every data update the provider sends also refreshesBookCopyLimits, through a new overridableDataProvider.ExpandDataUpdateScopehook (ADRadr-data-update-scope-hook).The extension side:
useChapterCopyLimitresolves a chapter's limit, and it is wired into the places text can be copied from:Copying is blocked (limit
0) while a chapter or its limit is loading, or when the limit can't be read. While the cursor is in a chapter's introduction (chapter 0, shown as the start of chapter 1), chapter 1's limit applies.Other changes: the Select All suppression is added to the keyboard-shortcuts catalog, and the decision to keep the rule downstream is recorded in the ADR
adr-copy-limit-rule-lives-downstream.Merge order: scripture-editors#23 is merged and
platform-yalchas moved, somainalready has the new editor and itsunicode-segmenterlockfile entry. The branch is rebased ontomain, and the lockfile commit dropped out as empty.Changes after review
The changes are in their own commit, on top of the feature commit:
ResourceCopyLimit's remarks state what an implementation must do: cover every chapter, and return an array it won't change later.GetBookCopyLimitspasses and returns.List<string>, with the base class decoding"*", blank and empty scopes first. It moved out of the Scripture-methods region and is recorded as a pattern inadr-data-update-scope-hook.clipboardDatastays blocked rather than throwing. This is covered by unit tests only: when tested on 30 Sep, before PT-4189: Edit footnotes in the footnotes pane in Standard view #2805 reworked the pane, it dropped a selection as soon as it was made, onmaintoo, so nothing could be copied from it. That was a separate, pre-existing bug.ICopyLimitProjectDataProviderdeclares its members, with TSDoc.ref={contentRef}.truncateToCopyLimit.try/catch.0until it remounts. None of those cases has been observed, so they'll be fixed when one is.Changes after the rebase
mainmoved a long way while this waited forplatform-yalc, so the branch is rebased onto it. #2805 restructured the footnotes pane, so the pane's copy handling was ported into its newFootnotesPanecomponent, not merged textually. One new commit fits the copy limit to whatmainchanged:mainadded acontentRefprop. The view's own copy ref is nowcopyAreaRef, and the header layout's content area takes both.API Changes
extensions/src/platform-scripture/src/types/platform-scripture.d.ts(platform-scripturemodule): addedCopyLimitProjectInterfaceDataTypes, with one data type,BookCopyLimits: DataProviderDataType<SerializedVerseRef, (number | undefined)[] | undefined, never>. Its TSDoc states the unit, UTF-16 code units.platform-scripturemodule: addedICopyLimitProjectDataProvider, declaringgetBookCopyLimits,setBookCopyLimits(always throws) andsubscribeBookCopyLimits, with TSDoc. The selector's versification, chapter and verse are ignored.papi-shared-typesaugmentation: added'platformScripture.CopyLimit': ICopyLimitProjectDataProvidertoProjectDataProviderInterfaces.lib/platform-bible-utils: added the exporttruncateToCopyLimit(text: string, copyLimit: number | undefined): string. It returnstextunchanged whencopyLimitisundefined.lib/platform-bible-react: no signature or export changes.FootnoteEditor's Copy button now shortens its text toeditorOptions.copyLimitwhen one is set.platformScripture.CopyLimitis listed inprojectInterfacesfor every Paratext project, published and unpublished. It registersgetBookCopyLimits, and asetBookCopyLimitsthat always throws.ParatextProjectDataProvideralso namesBookCopyLimits.NetworkObjects/DataProvidergainsprotected virtual List<string> ExpandDataUpdateScope(List<string> dataTypes). The base class decodes the scope first:"*"never reaches the hook, and nothing is sent for a blank data type or an empty list.lib/papi-dts/papi.d.ts: unchanged.Findings
Critical — Must address before merge
None.
Important — Should address before merge
SendDataUpdateEventstayed callable. The wrapper's comment was also wrong about the"*"update. (fixed during review:DataProviderhas a newprotected virtual ExpandDataUpdateScope, called on every update's scope, andParatextProjectDataProvideroverrides it once to addBookCopyLimits."*", an empty list and a blank string pass through unchanged;mainagain;"*", and disabling the override fails the three refresh tests.)BookCopyLimitsTSDoc and the C#ResourceCopyLimitandGetBookCopyLimitssummaries now say UTF-16 code units.)Copying is blocked in the user's own editable project while its limit loads, and when a lookup fails.(Intentional, per the spec: "Unknown limit: blocks copying for every text while loading or on failure." The author: "Intentional for now to give the most protection where it's needed for resources. We can look again if it proves to be an issue.")Author response: the author chose the base-class hook over a source-scan test or an analyzer, because it makes skipping the refresh impossible and shrinks the diff. They asked that reviewers look at the base-class change (see Suggested Review Focus).
Minor — Consider
The same
copyLimit === undefined ? text : truncateToCopyLimit(...)ternary appeared three times. (fixed during review:truncateToCopyLimitacceptsnumber | undefined, with a new test. The three call sites drop the ternary, and both libraries'dist/are rebuilt.)About 20 lines of chapter and copy-limit wiring were repeated in the resource text panel and the grid cell. (fixed during review: a new
copy-limit/use-chapter-usj-with-copy-limit.hook.ts, with tests, is used by both. It passes the same provider toChapterUSJanduseIsProjectDataLoading, and its tests fail when it doesn't.)optionscarried a partly appliedcopyLimitinplatform-scripture-editor.web-view.tsx, and a TSDoc block there wasn't attached to anything. (fixed during review:copyLimitis now a local fromuseChapterCopyLimit, andeditorOptionsWithCopyLimitis{ ...options, copyLimit };copy-limit-wiring.contract.test.tspins the new form;getModelResourceProjectIdlived in a component file that the web view imported from. (fixed during review: moved unchanged intoresource-reference.utils.ts.)(Superseded: the mapper was removed after review. See "Changes after review". At the time: left as it is. Neither cheap fix is safe:CopyLimitVersificationMapperrebuilds its versification map on every request when either versification is customized.ManageBooksService) and have no change token;Measured during manual testing (see "Other notes for reviewers"): about 5–22 ms per request, and only when a view sends a different versification than the text's own, which none of the current views does.)
(Kept. See "Other notes for reviewers".)getResourceChapterinmodel-text-panel.web-view.tsxnow fetches the USJ and the text direction in parallel.No story showed the disabled Copy item. (fixed during review: a new
CopyLimitedMenuOpenstory renders a cell withcopyLimit={0}, selects a verse, opens the right-click menu and checks that Copy is disabled.)Author response: the author asked for fixes to five of the seven, and for other reasonable deduplication. The subagent's decisions on the other candidates:
truncateToCopyLimit.Template Propagation
Shared Regions Modified
None. Seven changed files contain the words "shared with", but only in ordinary comments, not
#region shared withmarkers.Extension Config Changes
None.
Other notes for reviewers
getResourceChapterin the model-text web view now fetches the chapter's USJ and text direction in parallel (Promise.all), which shortens the time copying is blocked while a chapter loads.useProjectDataconsumer still reads the previous selector's or provider's data as settled for the render that changes it. That's pre-existing increate-use-data-hook.util.ts. The copy-limit paths work around it withuseIsProjectDataLoading.Positive Observations
ResourceCopyLimit.csis a single placeholder that always answers "no limit". Its remarks look forward: they warn not to change the signature, because the downstream patch depends on it. TheBookCopyLimitsTSDoc describes only what a caller can observe.never, andSetBookCopyLimitsthrowsNotSupportedException;ProjectInterfaces/ProjectDataTypelayout;nullC# array entry arrives asundefinedin TypeScript, matching the declared type.truncateToCopyLimit, including Khmer conjuncts under a segmenter that splits them;resolveCopyLimit,useChapterCopyLimit(with its lookup cache and failure retry),useIsProjectDataLoadingand the new shared hook;copy-limit-wiring.contract.test.tschecks the JSX props of a web view that can't be mounted in jsdom, using the TypeScript AST.useChapterCopyLimitfollowsadr-provider-lookup-is-a-fan-out: the promise is cached per project, and a failure is retried after 30 seconds while the view stays open.commandleft unset;COPY_KEY;Interview Notes
SendReceiveWriteLockCoverageTests, a Roslyn analyzer, and an overridable base-class hook likeDataProvider's existingprotected virtualmethods. They chose the hook, and asked that reviewers look at the base-class change.In-Review Quality Check
These checks ran after all the in-review changes, judged by exit code. No fixes were needed.
Types:
npm run typecheckexits 0 (all four sub-checks).Lint:
npm run lintexits 0: no errors, and one warning in a file this change doesn't touch.Formatting: prettier left every changed file unchanged.
C#:
dotnet test c-sharp-tests/passes 1,788 tests, with 7 skipped and none failing.dotnet csharpier --check .passes.TypeScript tests:
npm testexited 1 on this machine, but not because of the code. Two things on the machine caused it:platform-bible-react.platform-bible-utils. The committeddist/is current.In that run, every other workspace passed. That includes core (6,280 tests),
platform-bible-utils(678),platform-scripture(679) andplatform-scripture-editor(3,366).platform-bible-react's tests were run separately:The new story:
CopyLimitedMenuOpenis an extension story, and no automated suite runs those. The author checked it by hand in Storybook: selecting text and right-clicking opens the menu with Copy disabled.papi.d.ts: unchanged.Suggested Review Focus
DataProvider.ExpandDataUpdateScopehook. It changes the shared base class of every C# data provider (Time, Inventory, DBL, Internet settings,CheckRunnerand the project providers). The base class now decodes"*", blank and empty scopes before the hook, and onlyParatextProjectDataProvideroverrides it. Check that the other providers are unaffected.useChapterCopyLimitanduseIsProjectDataLoading: when the limit is0, the lookup cache and retry, and the same-provider rule in the new shared hook.BookCopyLimitsTSDoc, and the C#ResourceCopyLimitsummary, enough for a downstream implementation?🤖 Generated with Claude Code
This change is