Repository navigation
Add a Share button and dialog for sharing a collection (BL-16673) - #8394
JohnThomson wants to merge 23 commits into
Conversation
The Collection tab gets a Share button that opens a dialog where a collection's admins invite people by email as Admins or Editors, change their roles, and remove them; editors see the list read-only. People are identified by their BloomLibrary.org sign-in, so the dialog asks a signed-out user to sign in first. Inviting the first person is what shares a collection, and in a Team Collection only its administrators can do that. For a Team Collection, the people its history shows have worked in it are offered as suggestions to invite. The cloud (Supabase) backend is not deployed yet, so SharingApi talks to an ICollectionSharingService whose one implementation keeps the record in sharing.local.json in the collection folder (a folder TC does not sync it) and enforces the rules the backend will: only admins change anything, the last admin can be neither demoted nor removed, and nobody is invited twice. TopBarButton and BloomButton can now take an icon element (here the MUI Share icon) instead of an image file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…16673) Fixes from Devin's review of the Share dialog: - Inviting several people (the "Invite Selected" suggestions) is now one operation: the service checks every invitation against the record before adding any, so one that is already a member no longer leaves the earlier ones saved and the open dialogs unrefreshed. - A failed invitation no longer dismisses the suggestions the admin left unchecked. postJson reports a failure and then resolves anyway, so invite() now resolves to whether it worked. - "Last seen" is recorded when the collection opens, not only when someone opens the Share dialog. - sharing/state runs on the UI thread, like teamCollection/getHistory, because its suggestions read the collection's book list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Devin's re-review found that the first invitation, which is what shares a collection, saved the new sharing record and the invitees separately; if adding the invitees failed, the collection was left shared with only the admin. StartSharing now takes the first invitations and writes the record once, after checking every invitation, so a bad one leaves the collection unshared. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From Devin's third review: - The sharing file is now written to a temporary file that replaces the real one only when complete, so Bloom stopping part way through a save can't leave a truncated file that breaks the Share dialog. - Both ways of inviting (the email box and Invite Selected) are disabled while an invitation is on its way, so a double click can't send the same people twice, and the email box keeps what was typed if the invitation fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A successful invitation clears the email box, so a next address typed while it was on its way was wiped out (found by Devin). The box is now disabled until the answer comes back, like the Invite button. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
[Claude Opus 5.5 during preflight] Consulted Devin on 2026-09-23 12:05 up to commit eb0801d. Re-review clean: all 7 bugs it raised across the rounds are marked fixed, and its remaining points repeat ones already answered in their threads. |
Decisions from the preflight report: - An admin can no longer change their own role or remove themselves; another admin has to. So an admin can only step down once someone else has actually taken over, and a shared collection always keeps an admin, which makes the separate last-admin checks unnecessary. In the dialog, your own role shows as plain text with a tooltip explaining why. - The dialog's title bar now has the "Learn about sharing" link from the mockups. For now it opens the Team Collections introduction on docs.bloomlibrary.org, in the web browser. The Sharing.ShareDialog.LastAdmin string is no longer used and is removed. It was added on this branch, was always translate="no", has never been on master, and appears in no translated file, so removing it loses no translations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
[Claude Opus 5.5 during preflight] Consulted Devin on 2026-09-23 15:13 up to commit c65cc3f (John's decisions: no changing your own role; the Learn about sharing link). Re-review clean: no new bugs, all 7 earlier ones marked fixed; its other points repeat ones already answered. |
JohnThomson
left a comment
There was a problem hiding this comment.
@JohnThomson reviewed 15 files and all commit messages, made 6 comments, and resolved 2 discussions.
Reviewable status: 0 of 18 files reviewed, 17 unresolved discussions.
src/BloomBrowserUI/sharing/ShareDialog.tsx line 45 at r3 (raw file):
const kSecondaryTextColor = "#757575"; const kDangerColor = "#d32f2f";
If these colors are close to colors Bloom already uses, they are probably meant to be the same as those colors, and should use existing constants.
src/BloomBrowserUI/sharing/ShareDialog.tsx line 290 at r3 (raw file):
); const notSharedNote = useL10n( "This collection is not shared yet. Only an administrator of this Team Collection can share it.",
We may not continue to use the term "Team Collection", thinking of this more as a shared collection. I think in this context we can just say, "...of this Collection..." There may be other new strings where we can avoid using the old term.
src/BloomBrowserUI/sharing/ShareDialog.tsx line 358 at r3 (raw file):
setEmail(""); setTriedInvalid(false); }
I think the intention is that Invite, besides doing something in the cloud to record that this person is allowed to use the collection, would add this entry to the list. Should we do that here, or do we get an updated list of members as a side effect of calling props.onInvite?
src/BloomBrowserUI/sharing/ShareDialog.tsx line 431 at r3 (raw file):
"These people have worked on this Team Collection. Do you want to invite them?", "Sharing.ShareDialog.SuggestionsHeading", );
I don't think we need this heading. The people we discover in history don't need to be suggestions; they simply are the people who have permission to use this cloud collection, unless the admin removes them. (Of course the admin can also add more.)
(This might change if the UI designer decides that Invite actually sends some kind of invitation. I think the intent is that it just puts someone in the list of those permitted access to the collection in whatever way the admin specified. The invitation is 'accepted' only in the sense that at some point the invitee opens the collection from the cloud. We might need a backend change to know that has happened.)
src/BloomBrowserUI/sharing/ShareDialog.tsx line 594 at r3 (raw file):
props.now, props.uiLanguage, );
If my interpretation is right, we don't need member.invited; anyone in the list of MemberRows is invited.
If someone is known from the database to have accessed the collection, lastSeen would be the time they last did so (that is, the last time this user opened the collection somewhere or made a change to it). If they have never done so but are known from history to have checked a book in, lastSeen would be the last time they did that. If they have not done either, then they are considered to have been invited at the time the admin gave them permission to access the collection.
src/BloomBrowserUI/sharing/sharingApi.ts line 25 at r3 (raw file):
// Someone the old Team Collection's history shows has worked in this collection, whom an // admin may want to invite. export interface ISharingSuggestion {
We might not need this if historic users are automatically made sharing members
…nd member status (BL-16673) Reworks the Share dialog per John's review of PR #8394. The current goal is sharing a collection that is not already a Team Collection; migration comes later. Team Collection history seeds the members instead of being offered as suggestions: - The history scan (SharingSuggestions.Find, renamed TeamCollectionHistoryMembers.Find) now gives the people who should simply have access. When a signed-in person who may manage sharing (the existing CanManage rule) first asks for sharing/state on a (folder) Team Collection with no sharing record, the server shares it then (TeamCollectionHistoryMembers.StartSharingIfTeamCollection): the admin plus everyone the history shows, as Admin if the TC's Administrators list has them, otherwise Editor, all in one write. StartSharing takes these history members (name, role, last activity) alongside the invitations; the signed-in admin is never added twice. An ordinary collection is unchanged: it starts being shared with the first invitation. The admin removes anyone who should not be there. - Removed everything that existed only for the suggestions panel: the SuggestionsPanel UI, `suggestions` in the state, ISharingSuggestion / SharingSuggestion, dismissSuggestions (TS API, the sharing/dismissSuggestions endpoint, the service's DismissSuggestions, and the record's DismissedSuggestions), and their tests. No member status: everyone in the list is invited. - Removed SharingMemberStatus / status / data-status. Each member has invitedAt and a nullable lastSeen: RecordVisit sets it, and history-seeded members start with their last history activity. A row shows "Last seen <when>" if lastSeen is set, otherwise "Invited <when>"; the faded avatar/name for someone never seen now follows from lastSeen being missing. Other review points: - Colors: the dialog's own #757575 and #d32f2f are replaced by Bloom's kMutedTextGray and kBloomRed; while there, the #f1f3f4 note background and #e0e0e0 dividers use kFormBackground and kBannerGray. - "Team Collection" is gone from the Sharing strings: OnlyAdminsCanShare now reads "This collection is not shared yet. Only an administrator of this collection can share it.", and the matching C# exception message says "collection" too. - A new member row comes only from the server: a successful invite makes SharingApi send the sharing/stateChanged websocket event, and useSharingState (useWatchApiData) then fetches sharing/state again. Noted in a comment at the invite; no optimistic insert. XLF (DistFiles/localization/en/BloomMediumPriority.xlf, all translate="no"): - Deleted Sharing.ShareDialog.SuggestionsHeading, NotNow and InviteSelected, which only the removed panel used. They were added on this branch (fef96dd) and never shipped: `git log -G '<id>' origin/master -- DistFiles/localization/en/BloomMediumPriority.xlf` and the same over origin/Version6.5 find no commits for any of the three, and no translated folder under DistFiles/localization has them, so deleting loses nothing. - Changed the OnlyAdminsCanShare text and note, and updated the LastSeen and InvitedWhen notes to say what each time means. Tests: C# FullyQualifiedName~BloomTests.Sharing (31 pass) now covers seeding (a TC with history is shared at the first admin look with the right roles and lastSeen; an ordinary collection stays unshared and reads no history; an already-shared TC is left alone so removed people do not come back; the admin is not duplicated) and lastSeen after a visit. The vitest dialog tests check the "Last seen"/"Invited" text, using a useL10n mock that returns the English with its parameter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
JohnThomson
left a comment
There was a problem hiding this comment.
@JohnThomson+AGNT made 6 comments and resolved 6 discussions.
Reviewable status: 0 of 20 files reviewed, 11 unresolved discussions.
src/BloomBrowserUI/sharing/ShareDialog.tsx line 45 at r3 (raw file):
Previously, JohnThomson (John Thomson) wrote…
If these colors are close to colors Bloom already uses, they are probably meant to be the same as those colors, and should use existing constants.
[Claude Opus 5.5] Done in 122b0cd. No local colors are left. #757575 is now kMutedTextGray and the danger red is now kBloomRed (#d65649, the same color kWarningColor uses). I also switched the note background to kFormBackground and the dividers to kBannerGray.
src/BloomBrowserUI/sharing/ShareDialog.tsx line 290 at r3 (raw file):
Previously, JohnThomson (John Thomson) wrote…
We may not continue to use the term "Team Collection", thinking of this more as a shared collection. I think in this context we can just say, "...of this Collection..." There may be other new strings where we can avoid using the old term.
[Claude Opus 5.5] Done in 122b0cd. The note now reads "This collection is not shared yet. Only an administrator of this collection can share it." The matching C# error message changed too. None of the other new sharing strings say "Team Collection".
src/BloomBrowserUI/sharing/ShareDialog.tsx line 358 at r3 (raw file):
Previously, JohnThomson (John Thomson) wrote…
I think the intention is that Invite, besides doing something in the cloud to record that this person is allowed to use the collection, would add this entry to the list. Should we do that here, or do we get an updated list of members as a side effect of calling props.onInvite?
[Claude Opus 5.5] We get the updated list as a side effect. A successful invite on the C# side writes the record and then sends the sharing/stateChanged websocket event. The dialog gets its data from useWatchApiData, which listens for that event and fetches sharing/state again, so the new row comes from the server. There is no optimistic insert here. I added a comment at tryInvite saying so. When the real backend arrives, the same event (or its realtime equivalent) will do this job.
src/BloomBrowserUI/sharing/ShareDialog.tsx line 431 at r3 (raw file):
Previously, JohnThomson (John Thomson) wrote…
I don't think we need this heading. The people we discover in history don't need to be suggestions; they simply are the people who have permission to use this cloud collection, unless the admin removes them. (Of course the admin can also add more.)
(This might change if the UI designer decides that Invite actually sends some kind of invitation. I think the intent is that it just puts someone in the list of those permitted access to the collection in whatever way the admin specified. The invitation is 'accepted' only in the sense that at some point the invitee opens the collection from the cloud. We might need a backend change to know that has happened.)
[Claude Opus 5.5] Agreed, and done in 122b0cd. Suggestions are gone completely: the panel, the heading, dismissing, and their strings. The first time an administrator opens Share on a Team Collection that is not shared yet, it is shared right away. The members are that admin plus everyone the history shows. They are Admin if the TC lists them as an administrator, otherwise Editor, and the admin can remove anyone. An ordinary collection still starts being shared with the first invite. Invite only adds someone to the list of people allowed in, as you describe. A backend change to notice when an invitee first opens the collection can come later.
src/BloomBrowserUI/sharing/ShareDialog.tsx line 594 at r3 (raw file):
Previously, JohnThomson (John Thomson) wrote…
If my interpretation is right, we don't need member.invited; anyone in the list of MemberRows is invited.
If someone is known from the database to have accessed the collection, lastSeen would be the time they last did so (that is, the last time this user opened the collection somewhere or made a change to it). If they have never done so but are known from history to have checked a book in, lastSeen would be the last time they did that. If they have not done either, then they are considered to have been invited at the time the admin gave them permission to access the collection.
[Claude Opus 5.5] Done in 122b0cd. The invited/active status is gone. Each member has invitedAt and an optional lastSeen. lastSeen is set when they open the collection, which for now means this Bloom recording a visit. For people brought in from history, it starts as their last history event. A row shows "Last seen ..." when lastSeen is known, otherwise "Invited ...". One small difference from what you wrote: the history time is their last recorded event of any kind, not only a check-in. The faded look for someone never seen now follows from lastSeen being empty.
src/BloomBrowserUI/sharing/sharingApi.ts line 25 at r3 (raw file):
Previously, JohnThomson (John Thomson) wrote…
We might not need this if historic users are automatically made sharing members
[Claude Opus 5.5] Right. ISharingSuggestion and the rest of the suggestions API were removed in 122b0cd.
|
[Claude Opus 5.5] Consulted Devin on 2026-09-25 up to commit 122b0cd. It raised one new bug (a first invite arriving before the dialog loads, which the dialog can't do) and two points to check (history members added without asking, which is intended; pseudo-localized dates, which work). All three are answered in their own resolved threads with no code change. Everything it raised earlier is fixed. |
Design/CloudTeamCollections.md describes the design as it now stands, for a developer new to cloud Team Collections, and says where the detailed docs live (bloom-core-supabase PR #13's team-collections/docs, and the Design folder on the #8052 branch), since this file reaches master first. It covers the goals and architecture; identity, membership and roles, with the Share dialog as built on this branch and the sharing UI planned on the Sharing cards (BL-16672/4/5/6, BL-16527), each labelled as such; the book lifecycle with the client-made checkout GUID replacing the seat (write-ahead .checkout, hash on the server, obsolete-record cancellation), two-phase check-in transactions, send-only locks, unconfirmed check-ins, receive, polling, Lost and Found, force unlock; transferring a local checkout to a new login through checkout_book_takeover, with what is decided and what is open; the tc schema as a Mermaid ER diagram checked against 03_tables.sql; the RPC and edge-function surface in summary; open questions (including where the server does not yet match the Share dialog's rules); and a short section on what changed since the July version. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A semicolon ends a statement in a Mermaid sequence diagram, so the message "keep the file; next poll/open decides" split in two and the diagram failed to parse. All four diagrams in the doc now render with mermaid-cli. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Inviting only adds an address to the collection's allowed list; no email is sent. A signed-in person allowed to use a cloud collection they haven't joined sees an invitation card in Open/Create Collections. Dropped 'should invitations be emailed' from the open questions. - Cloud Team Collections aren't in 6.5 and are to be fully live (perhaps still experimental) in 6.6; nothing is gated by the cloudCollections environment variable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The backend (bloom-core-supabase #13, 4d1eb61) now records when each member last had a collection open, set by get_collection_state and get_changes at most every 10 minutes. Add it to the schema diagram and the members notes, and replace the open question about last seen with the two follow-ups that remain (history times on migration; a touch point if realtime replaces polling). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…6676) Add section 5, "Starting a cloud collection: initial upload and migration", marked designed but not built. It covers sharing an ordinary collection and moving a folder Team Collection to the cloud, done by the admin's 6.6 Bloom: - freeze the old shared folder with AllowSharedFolderChanges=False (the BL-16928 6.5 patch) plus AllowCheckouts=False for older Blooms; - create the cloud collection with an initial-upload flag and send books in the background as ordinary first check-ins, so the admin is never frozen; - for books others hold in the old system, write a Migration Keys/<instanceId>.json key (checkout GUID, old email, old machine) and lock the book to a legacy:<old email> placeholder with the GUID's hash; - clear the flag and write the cloud id into the old shared settings, after which each member's Bloom switches over and moves its checkouts with checkout_book_takeover; - recovery by a support script if an upload is abandoned. Renumber the later sections, point the BL-16676 planned-UI bullet and the open-questions list at the new section (which replaces the mockup's wait-for-everyone-to-check-in phase), list the server and client work it needs, and drop a stale note saying tc.members has no last-seen column. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…migration In section 5, step 1, right after AllowSharedFolderChanges and AllowCheckouts are set, the old shared folder is made read-only for everyone except the migrating admin (Dropbox "Can view" set by the folder's owner, or LAN file-system permissions), as a safeguard against Blooms too old to honor those settings or MinimumBloomVersion. It is a manual checklist step Bloom cannot do or verify; later writes to the old folder are all the admin's, and switched 6.6 members only read it. The recovery step restores write access too. Section 9 gets the open question of whether this makes an old Bloom fail fast: a LAN share refuses the write, but a Dropbox "Can view" folder may accept the local write and never upload it, which needs testing with two Dropbox accounts (and OneDrive/Google Drive if supported). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ead-only test Folder Team Collections don't work over OneDrive or Google Drive. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In Bloom 6.6 an administrator will be able to move a folder Team Collection to Bloom's cloud collections (BL-16531, BL-16676), and while that happens the old shared folder must stop changing. AllowCheckouts (BL-16691) only stops new checkouts, and MinimumBloomVersion (BL-16690) would lock people out of books they have checked out. This 6.5 patch adds an AllowSharedFolderChanges collection setting, on the same pattern as AllowCheckouts: - It defaults to true, is written to the .bloomCollection only while false, and is picked up mid-session. The shared folder's copy of the settings decides; the local copy is used only when the shared one can't be read, and a shared folder with no settings yet isn't paused. So an administrator's hand edit is pushed up as usual and then pauses everyone, the administrator included. - While it is false, nothing writes to the shared folder: check in (including first check-ins), check out, rename, delete, Forget Changes, Force Unlock, collection files and color palettes. TeamCollection's public write methods refuse (SharedFolderChangesPausedException) before doing anything, the low-level FolderTeamCollection writes check again, the API layer refuses and asks the status panel to redraw, and SyncAtStartup reports a refused per-book fix-up as a warning rather than an error. - Editing books already checked out keeps working, as does deleting a book that was never checked in. Collection Settings and the reader-tool settings refuse to open, since their changes couldn't be shared and would be replaced by the shared copy at the next start. - The book status panel disables the affected buttons and explains why. If the shared settings carry a CloudCollectionId (which the 6.6 migration will write), the message says the collection has moved to Bloom's cloud sharing and Bloom 6.6 is needed; otherwise that the administrator has paused changes. The two new strings (TeamCollection.SharedFolderChangesPaused and TeamCollection.MovedToCloud) and BL-16691's paused-checkouts note (TeamCollection.CheckoutsPaused, previously English-only) are live, translatable entries in Bloom.xlf. - A new Team Collection made from a copy of a paused collection drops the pause and cloud id before its settings go up, so it starts out unpaused with its books. There is no UI for setting it; the 6.6 migration will set it (see Design/CloudTeamCollections.md, PR #8394). 38 new C# tests cover the settings round trip, each write path being refused when paused (with the shared folder unchanged) and working when allowed, the shared folder's authority, and the mid-session pickup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eeze PR Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…NTRACTS v1.12) Name the RPCs as built in bloom-core-supabase #13, say the placeholder is guarded by a CHECK on members.user_id rather than by account ids never containing ':', record that the placeholder CheckOut event names the admin with the placeholder in lock_info, and note that my_collections also hides a collection from the uploading admin's other computers and that the support delete should wait until the admin's Bloom has stopped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When an administrator moves a folder Team Collection to Bloom's cloud collections (BL-16531, BL-16676), the old shared folder must stop changing while the books are uploaded. Blooms older than 6.6 are shut out of the collection by the existing MinimumBloomVersion gate (BL-16690), which the migration sets to 6.6; this setting is for 6.6 Blooms that are still on the old system until the upload finishes. It adds an AllowSharedFolderChanges collection setting, on the same pattern as AllowCheckouts (BL-16691): - It defaults to true, is written to the .bloomCollection only while false, and is picked up mid-session. The shared folder's copy of the settings decides; the local copy is used only when the shared one can't be read, and a shared folder with no settings yet isn't paused. So an administrator's hand edit is pushed up as usual and then pauses everyone, the administrator included. - While it is false, nothing writes to the shared folder: check in (including first check-ins), check out, rename, delete, Forget Changes, Force Unlock, collection files and color palettes. TeamCollection's public write methods refuse (SharedFolderChangesPausedException) before doing anything, the low-level FolderTeamCollection writes check again, the API layer refuses and asks the status panel to redraw, and SyncAtStartup reports a refused per-book fix-up as a warning rather than an error. - Editing books already checked out keeps working, as does deleting a book that was never checked in. Collection Settings and the reader-tool settings refuse to open, since their changes couldn't be shared and would be replaced by the shared copy at the next start. - The book status panel disables the affected buttons and explains why. If the shared settings carry a CloudCollectionId (which the migration writes when the upload is done), the message says the collection has moved to Bloom's cloud sharing; otherwise that the administrator has paused changes. The two new strings (TeamCollection.SharedFolderChangesPaused and TeamCollection.MovedToCloud) and BL-16691's paused-checkouts note (TeamCollection.CheckoutsPaused, previously English-only) are in Bloom.xlf, translate="no" while the feature is in development on master. - A new Team Collection made from a copy of a paused collection drops the pause and cloud id before its settings go up, so it starts out unpaused with its books. There is no UI for setting it; the migration will set it (see Design/CloudTeamCollections.md, PR #8394). 38 new C# tests cover the settings round trip, each write path being refused when paused (with the shared folder unchanged) and working when allowed, the shared folder's authority, and the mid-session pickup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e is master-only The AllowSharedFolderChanges freeze (BL-16928, #8414) no longer ships as a 6.5 patch. The migration now sets MinimumBloomVersion=6.6 in the old shared settings, which 6.4 and 6.5 already honor by shutting the collection, so their users upgrade before continuing, checked-out books included; AllowSharedFolderChanges=False is for 6.6 Blooms still on the old system during the upload, and AllowCheckouts is no longer needed. Every collection that becomes a cloud collection, Team Collection or not, also gets MinimumBloomVersion=6.6 in its own settings, so every member's copy shuts out older Blooms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dialog has no Cancel, so opening it must not start sharing, and a collection should not become shared as a side effect of its first invitation. Now, before a collection is shared, the dialog is a preview and saves nothing: - It explains what sharing does, lists read-only who will have access (you as Admin, plus, for a folder Team Collection, everyone its history shows, Admin if in its administrators list, otherwise Editor), and for a Team Collection states the consequences: frozen for everyone, people keep their checkouts but need Bloom 6.6, older Blooms will not open it, and it can't be undone from Bloom. - Start sharing is enabled only for someone signed in who may manage the collection (existing CanManage rule); otherwise the sign-in prompt or the "only an administrator can share it" note says why. SharingApi.CanStart is the seam for later conditions (subscription, BL-16672; being online). - For a Team Collection, Start asks for confirmation in a small BloomDialog (Start sharing / Cancel); an ordinary collection starts at once. - The invite box appears only after sharing has started. API: sharing/state no longer starts sharing a Team Collection; when unshared it writes nothing and returns isTeamCollection, canStart and previewMembers. New POST sharing/start (UI thread, as it reads history) requires CanManage and is refused if already shared. sharing/invite on an unshared collection is now an error (the service already throws). ICollectionSharingService.StartSharing drops its invitations parameter; CollectionSharingRecord.StartingMembers builds the members for both the preview and the saved record. TeamCollectionHistoryMembers .StartSharingIfTeamCollection is gone. The new CollectionSharingStarter runs the steps of starting in the order of Design/CloudTeamCollections.md section 5, with empty, documented methods for the parts not built yet: FreezeOldTeamCollection (needs AllowSharedFolderChanges from #8414), RequireNewerBloomForCloudCollection (MinimumBloomVersion=6.6 in the collection's own settings; deliberately not done by the stand-in, since without a real cloud collection it would only lock older Blooms out of this collection here and, for a Team Collection whose settings get pushed to the shared folder, lock every 6.5 teammate out), and StartInitialUpload. No upload progress is shown. Strings: nine new Sharing.ShareDialog.* entries in BloomMediumPriority.xlf, all translate="no" (branch-only, master target), with notes. No existing string became unused, so none are removed or marked obsolete. Tests: new CollectionSharingStarterTests (preview writes nothing and lists admin + history people / just the admin; start saves what was previewed; start requires CanManage; starting twice is refused and re-adds nobody); service tests updated for the new StartSharing signature. ShareDialog.test.tsx covers the preview, the Team Collection consequences and confirmation (confirm and cancel), disabled Start with its reason, and the invite box only once shared. The design doc's Sharing UI section describes the Start sharing flow and the seams. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mmary The summary table left out finish_initial_upload, lock_book_for_legacy_checkout and support_delete_collection, and create_collection's initial-upload flag, all described in section 5. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| /> | ||
| </div> | ||
| )} | ||
| {props.state.isTeamCollection && <TeamCollectionConsequences />} |
There was a problem hiding this comment.
[Devin] Investigate: Sharing dialog promises unavailable migration
The dialog promises cloud access and a frozen Team Collection, but Start currently writes only a local membership file. Review the user-facing wording or availability before release.
There was a problem hiding this comment.
[Claude Opus 5.5 during preflight] Fair point, left open for John to decide. The wording describes what Start will do once the backend and the freeze (#8414) land; the stand-in behind it is known. What's worth a decision is that the Share button isn't behind any flag, so anyone running a master build can press Start and be told their Team Collection is frozen when nothing has happened to it.
|
[Claude Opus 5.5 during preflight] Consulted Devin on 2026-09-29 up to commit ee7a170 (the Start sharing change, a merge of master, and a design-doc fix). It re-reviewed the whole PR and raised two new bugs and four new points to check, each in its own thread:
Everything else it raised repeats points already answered, or bugs it now considers fixed. |
Adds three "Proposed" subsections to section 9, plus a diagram of the schema with all of them applied: - Users with their own identity: a core.users table (id, authentication_id, email, name) that the ten identity columns point at, so an email change or a new Firebase account doesn't orphan memberships, locks and history. Decided: we need it, and the name lives on it, taken from Bloom's Registration dialog at every sign-in, with no UI for editing other people's names. Unclaimed user rows replace the legacy:<email> lock holders of migrated checkouts. - Book names that needn't be unique: each copy picks its own folder name, adding a suffix when needed, and the main .htm is stored as index.htm so folder names never show up in the manifest. - Schema simplifications: drop tc.versions (the CheckIn event already records it) and number versions per book; drop redundant ids and indexes; resume a check-in attempt only if identical, else abort it, and drop revision; name books in the API by instance id; rename checkin_transactions, events and version_files to checkin_attempts, history_events and book_files, and delete attempt rows once nothing needs them; one set of collection files instead of three groups, sent only by an admin. Also fixes section 6 and 4, which said check-in transaction rows are reaped; they are only marked expired and never deleted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When an administrator moves a folder Team Collection to Bloom's cloud collections (BL-16531, BL-16676), the old shared folder must stop changing while the books are uploaded. Blooms older than 6.6 are shut out by the existing MinimumBloomVersion gate (BL-16690), which the migration sets to 6.6. This setting is for 6.6 Blooms still on the old folder Team Collection until the upload finishes: they must not write anything to the shared folder, but their users keep editing the books already checked out to them and any new local books. That is exactly what Disconnected mode already provides, so this reuses it: - AllowSharedFolderChanges (default true, written to the .bloomCollection only while false) and CloudCollectionId (written only when present) are new collection settings, read and written like AllowCheckouts, so Save() never drops them and an administrator's hand edit is pushed up as usual. - FolderTeamCollection.CheckConnection(), which runs at startup and before every check-out, check-in, Forget Changes and Force Unlock, reads them from the shared folder's copy of the settings after its existing folder, network and Dropbox checks. A pause is reported as the connection problem: TeamCollection.SharedFolderChangesPaused, or TeamCollection.MovedToCloud once a cloud id is present (close and reopen the collection to switch over). Missing or unreadable settings never cause a disconnect. - TeamCollectionManager.MakeDisconnected recognizes those two messages, records the reason on the DisconnectedTeamCollection (as it does for a lapsed subscription) and leaves out the usual "resolve this problem and click Reload" line. - A running Bloom picks the pause up when a teammate's settings change arrives (HandleCollectionSettingsChange, on the UI thread in the Idle handler, where a newly required minimum version is already handled). That path checks only the pause, not the whole connection, so a momentary network or Dropbox blip can't leave an ordinary collection disconnected. - The book status panel shows the pause or moved message in place of the generic disconnected wording. BL-16691's paused-checkouts note is now localized too (TeamCollection.CheckoutsPaused). - The three strings are translate="no" in Bloom.xlf while the feature is in development on master. There is no UI for setting the flag; the migration (the Start sharing work on #8394) will set it. Tests cover the settings round trip, CheckConnection's answers for unpaused, paused, moved and unreadable settings, MakeDisconnected for each reason, and the mid-session pickup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Problem. Bloom already lets a team work on a collection together through Team Collections, but only by having an administrator share a folder with a file-sync service such as Dropbox. Bloom has no control over that sharing: who can reach the folder, and what they may do there, is up to the sync service and the administrator's skill with it. Cloud Team Collections (BL-16531) keep the same way of working, but put the shared copy on a backend Bloom controls (Supabase), which we hope will also be more reliable. That control is what makes it possible for Bloom to have its own UI for sharing: administrators invite people from inside Bloom and decide what each may do. This PR adds that UI, which will eventually replace the Team Collection settings panel. The backend ships separately, from the
bloom-core-supabaseproject, and isn't deployed yet.What the PR does.
CollectionSharingStarter, stubbed until the backend and the freeze (BL-16928 Honor no-shared-folder-changes flag #8414) land.SharingApitalks to anICollectionSharingService. Its only implementation keeps the record insharing.local.jsonin the collection folder (a folder TC doesn't sync it) and enforces the rules the backend's RPCs will, with invitations all-or-nothing and safe writes. Invitations aren't sent anywhere yet; migrating TC members and uploading books wait for the backend.TopBarButtonandBloomButtoncan take an icon element (the MUI Share icon) instead of an image file.Design/CloudTeamCollections.mdis a new, current overview of Cloud Team Collections as a whole: architecture, checkout and check-in, the database, the server API, how a collection starts being a cloud collection (initial upload and migrating a folder Team Collection), and what is still open. It says where each part is being built (the server, the Cloud Team Collections (S3 + Supabase) #8052 client, this dialog, the BL-16928 Honor no-shared-folder-changes flag #8414 freeze).Ref: https://issues.bloomlibrary.org/youtrack/issue/BL-16673
🤖 Generated with Claude Code
Devin review
This change is