mirror of
https://github.com/block/buzz.git
synced 2026-11-08 02:11:36 +00:00
> Stacks on #8781 (`bmcanally/cli-sidebar-stars-mutes`). The diff shows only this PR's commits. Merge #8781 first. ## Summary This change adds `buzz sidebar sort`. It sets how each sidebar group orders its channels. - `sidebar sort list` shows the groups that have a sort mode. - `sidebar sort set --group <group> --mode <alpha|recent>` sets a mode. - `sidebar sort reset --group <group>` returns a group to the default mode, `alpha`. A group is `starred`, `channels`, `forums`, `dms`, `section:<id>`, or a section name. The command edits the same synced document as Buzz desktop and mobile (kind:30078, d=`channel-sort`). This PR stacks on the stars, mutes, and project pins PR. That PR stacks on the sections PR (#8759). ## Design The sort document uses the same per-field last-writer-wins register tree as the sections document. Each group key has one register. The value is `"alpha"`, `"recent"`, or `null`. A `null` register means "use the default". `sidebar_doc.rs` now works with any document shape. A `Codec` describes one document. It holds the tree shape, the import of meta-less payloads, and the legacy fields to project. Sections and sort each have one `Codec`. The decode, encode, and edit code is shared. The shared head read and the single-publish edit moved to `sidebar_sync.rs`. Sections and sort both call them. `Edit::unassign` became `Edit::clear(path)`. Both sections and sort use it. It writes a `null` register only when a register exists. Safety rules are the same as for sections: - A command makes one read of the sort head and one publish. - A section name also reads the sections document. - The CLI never publishes over a head it cannot decode. - `created_at` is the larger of the current time and the head time plus one. - Each register version is newer than every register in the head. - A dominated write exits with code 5. - A command that changes nothing publishes nothing and prints `"message": "unchanged"`. - A result with more than 104 non-null groups fails with exit code 1 before it publishes. Group names follow these rules: - A fixed group name wins over a section with the same name. - `section:<id>` must be an exact live section id when you set a mode. A section name in this form is refused. - Any `section:<id>` is accepted when you reset a mode. - A section name must be unique. Desktop does not remove the sort key of a deleted section. Only mobile removes such keys, and only in its local copy. The CLI also does not remove them. The stale key still shows in `sort list`. It still counts toward the cap of 104. `sort reset --group section:<id>` clears it. ## Testing What ran: - `cargo test -p buzz-cli`: 526 passed. - `cargo clippy -p buzz-cli --all-targets -- -D warnings`: clean. - `cargo fmt --all --check`: clean. - `just file-size-check`: passed. - All existing sections, stars, mutes, and projects tests pass without a change to their checks. The new tests use the axum fake relay. They call the production `run` function. They cover these cases: - Set and reset by fixed group, section name, section id, and `section:<id>`. - The document format: kind, `d` and `t` tags, `groups`, and `meta.g` registers. - An unchanged repeat, and a reset of a group with no register. - Bad input fails before any publish. - Ambiguous section names. - `section:<name>` and `section: <id>` are refused and publish nothing. - A deleted section: the key stays, set is refused, reset clears it. - A mobile head is imported, and its other groups survive an edit. - An unreadable sort head, an unreadable sections head, and a tampered head are never overwritten. - `created_at` and register versions are newer than the head. - The cap of 104 groups, and a reset that makes room. - A dominated write exits with the conflict error. Interop evidence: - Node 24 ran desktop's own TypeScript: `channelSortSync.ts`, `sidebarLaneStore.ts`, and `sidebarLwwMap.ts`. - Desktop's `encodeDoc` made a head document. It had a tombstone and a `section:` key. - The CLI edited that head (set, reset, and set over a tombstone). It also made a document from an empty head and from a mobile head. - For each CLI document, desktop's `decodeDoc` read it as a meta document. - For each CLI document, desktop's `projectSort` equals the CLI `groups` field. - For each CLI document, desktop's `canonical(encodeDoc(...))` equals the CLI bytes. Desktop would not publish again. - Desktop's `mergeTrees` of the desktop head and the CLI edit gives the CLI document. The reverse merge changes nothing. - The run printed `INTEROP OK`. Mutation checks. For each guard, I broke it, ran the sidebar tests, and saw a failure. Then I restored it. - Group cap check: `enforces_the_group_cap_and_refuses_bad_heads` failed. - Live-section requirement for set: the deleted-section test and the main journey failed. - Sort mode leaf validation: the sort document test failed. - Clear only an existing register: the sections unassign test and the main journey failed. - Unchanged no-op: the sections journey and the main journey failed. - Version newer than the newest register: the edit-version tests failed. - `created_at` after the head: seven tests failed. - Legacy mode filter (a `null` legacy value): the sort document test failed. I added a row for this case after the first mutation survived. - Fixed group wins over a section name: five tests failed. - Null registers excluded from the projection: six tests failed. - Refusal of an unknown `meta.v`: four tests failed. - Exact live id match for `section:<id>`: the main journey failed when the check also accepted names. ## Manual test steps Use a test identity. These commands write to your own sidebar. Run them with `BUZZ_RELAY_URL` and `BUZZ_PRIVATE_KEY` set. Open Buzz desktop on the same identity. 1. Build the CLI: `cargo build --release -p buzz-cli`. Use `./target/release/buzz` below. 2. Run `buzz sidebar sort list`. Success: it prints `{"default":"alpha","groups":{...}}`. 3. Run `buzz sidebar sections create --name "Sort test"`. Note the `section_id`. 4. Run `buzz sidebar sort set --group dms --mode recent`. Success: the output has `"group":"dms"` and an `event_id`. In desktop, the Direct Messages group changes to Recent within a few seconds. 5. Run the same command again. Success: the output has `"message":"unchanged"` and `"event_id":null`. 6. Run `buzz sidebar sort set --group "Sort test" --mode recent`. Success: the output has `"group":"section:<id>"`. The section in desktop sorts by Recent. 7. Run `buzz sidebar sort list`. Success: `groups` has `dms` and `section:<id>`, both `recent`. 8. In desktop, set the Starred group to Recent. Run `buzz sidebar sort list`. Success: `starred` shows `recent`. 9. Run `buzz sidebar sort reset --group dms`. Success: `dms` is back to A to Z in desktop. `sort list` no longer shows `dms`. 10. Run `buzz sidebar sections delete --section "Sort test"`. Run `buzz sidebar sort list`. Success: `section:<id>` is still there. 11. Run `buzz sidebar sort set --group section:<id> --mode alpha`. Success: it fails with exit code 1 and a not-found message. 12. Run `buzz sidebar sort reset --group section:<id>`. Success: it publishes. `sort list` no longer shows the key. 13. Run `buzz sidebar sort set --group nope --mode recent`. Success: it fails with exit code 1 and publishes nothing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: Blake McAnally <bmcanally@squareup.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>