cmux-layout-reducer-ffi: bookmarks_apply, the bookmark op reducer for the app (V-11, step A)

Merged
LA
3d ago
+96-2

Step A of a two-step FFI move (layer-ownership.md V-11, bead cx-e59e). The app keeps a hand copy of the daemon's bookmark rules (CmuxNextBookmarks/Model/BookmarkTree+Apply.swift) to show an edit before the daemon's echo. #31818 put those rules in cmux-bookmarks-core (the daemon uses it, with a parity test against the daemon's SQL ops). This PR exposes the crate's reducer to the app FFI. There is no Swift change.

Check the app FFI pin: expected, step A of a two-step FFI move. It clears with the re-pin PR that app-ffi-release.yml opens after this lands. Step B (after that re-pin is on feat) makes the app keep a confirmed tree from daemon listings plus a pending-edit log, compute the visible tree through this op, and delete BookmarkTree+Apply.swift, with its Mac suites.

What changes

  • cmux_layout_reducer_json op bookmarks_apply: {browser_profile_id, tree: [Bookmark], ops: [BookmarkOp], now_ms, seed?} -> {tree, results: [{result, changed} | {error_code, reason, message}]} (cmux_bookmarks_core::preview::respond). The ops use the daemon's op JSON. A pending op the visible tree refuses changes nothing and reports why, so an edit that the echo already holds does not move the view. reason names the rule, so the app keeps its localized refusal texts.
  • cmux-layout-reducer-ffi depends on cmux-bookmarks-core without its url feature: the app links no URL parser (no url/idna/ICU, about 30 packages, in cmux-app-ffi/Cargo.lock). Without it, validate_url checks the scheme shape, which accepts every URL the parser accepts in the crate's test corpus; so the app never refuses an edit the daemon takes, and one the daemon refuses comes back as the command's error.
  • Wiring: the cmux-app-ffi/Cargo.lock entry (written by hand in cargo's format; the cmux-tui.yml step cmux-layout-reducer-ffi (own workspace) checks it with cargo metadata + git diff --exit-code Cargo.lock), the release push path in app-ffi-release.yml (required by scripts/cmux-next/tests/app-ffi-release-paths.test.sh for every FFI input crate), and the app crate license gate's first-party GPL list.

Risk: high only because of the one push-path line in app-ffi-release.yml (a release workflow). The product change is one new op nothing calls yet.

Known gaps between the crate's Tree and the daemon (in tree.rs): it cannot check that a profile exists in browser_profiles or that an id is free in every profile, and it does not check a folder icon's syntax or stored asset. A daemon refusal of these reaches the app as the command's error.

Tests

  • cmux-layout-reducer-ffi tests/bookmarks_apply.rs: two pending edits on a confirmed tree both show, a refused third reports {error_code, reason, message}; a malformed request is ERR_INVALID.
  • The reducer itself is proved by #31818's tests (fleet step 17a8481bae447211ebc8f64b: 44 run, 44 passed, including parity::the_daemon_and_the_shared_reducer_give_the_same_tree_result_and_refusal).

No red step: the op did not exist on the base.

Observed / Not run

cmux-layout-reducer-ffi is its own Cargo workspace, so the fleet runs its tests on never-merge proof refs that build it inside the cmux-tui workspace (listed in stopped.txt):

Command: scripts/ci/cmux-tui-rust-check.sh test bookmarks_apply (linux, through proof-step.py).

  • Red at b4b597c55a6f: step 140374ec848488eee8294e83, failed on its own check: bookmarks_apply::two_quick_edits_both_show_on_the_confirmed_tree FAIL (the op is unknown, so the call returns ERR_INVALID), also alone on the rerun; a_malformed_request_is_invalid passes there too (an unknown op is also ERR_INVALID).
  • Green at f3182416a23f: step 0b07137f01046993fd688beb, passed (2 tests run, 2 passed).
  • Clippy at f3182416a23f (the FFI crate inside the workspace): step 9cd3870ed526af8b6ba6f1e2, passed.
  • The head is now a4eb6e66ee9390b141357487eecaec0dee60f430: cmux-bot merged feat-cmux-next, which brought two other moves' FFI changes (#31925 sidebar face, #31934 agent cursor visibility). The merge keeps the bookmarks_apply arm, the lock entries and the release path line; app-ffi-release-paths.test.sh ok at this head.
  • Panic ratchet at the exact head a4eb6e66ee93: step 5ab614a9940ba072525b0b29, pass (83 crates checked, 33 at deny). cmux-layout-reducer-ffi is its own Cargo workspace, so the ratchet's workspace run does not see it; the non-test lines this PR adds there are one match arm with map_err(..)? only (no indexing, unwrap, expect, panic, unreachable, todo or unimplemented).

Local (Python only): app-ffi-release-paths.test.sh ok; python3 -m unittest tests.test_app_crate_licenses ok; tests.test_cmux_next_route -k app_ffi ok; scripts/cmux-next/prepush-gate.py no new violations.

Not run: the cmux-app-ffi/Cargo.lock check (cargo metadata + git diff --exit-code Cargo.lock); the entry was written by hand in cargo's format. No Swift compile (no Swift change). No pre-merge FFI release exists, so the app half is not built against this op yet.

Changelog

none