| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
shell: Expose the keyboard, pointer, actions, window and five base components to scripts (#2847) ## Why these, and not others The gaps were picked by counting real API use, and the count that mattered turned out not to be the obvious one. `crates/ui` is the library's own internals — its drag-and-drop is all inside `table`, `list` and `dock`. `crates/story` is an application written *with* gpui-component, which is what a script author is. The two orderings disagree sharply: | API | `crates/ui` | `crates/story` | | --- | --- | --- | | `on_action` | 45 | **94** | | `on_drag` / `on_drop` | 18 | **1 each** | So this follows `crates/story`. Drag-and-drop is not here; actions are. Two things that looked like gaps are not. Every no-argument style (`text_right`, `font_medium`, `shadow_sm`, `cursor_pointer`) already arrives through GPUI's reflection table — 3207 of them — so the style surface needed nothing. And `Icon`, `Label`, `Badge` and `Kbd` are high-use but gpui-component's own; base has no such types, and by the layering a script draws them with `div()` and styles. ## What a script can now do ### Keyboard | API | Mirrors | | --- | --- | | `on_key_down(handler)` | `InteractiveElement::on_key_down` | | `on_key_up(handler)` | `InteractiveElement::on_key_up` | | `KeyEvent` | `keystroke` (the whole chord, `"cmd-shift-s"`), `key`, `key_char`, `modifiers`, `is_held` | The chord is handed over already unparsed, because that is the form a comparison is written against and reproducing it from the parts is the fiddly half. **The platform modifier is spelled `cmd` on every platform**, including Linux and Windows, and that is a deliberate departure from `Keystroke::unparse`. GPUI spells it for the platform it was built for — `cmd-`, `super-`, `win-` — which is right for a keymap a person reads and wrong for a string a program compares. One script file runs on all three, so the first version of this shipped an `event.keystroke === "cmd-s"` that worked on macOS and silently did nothing elsewhere; CI caught it and the second commit fixes it. `cmd` is the choice because `Keystroke::parse`, which `cx.bind_keys` goes through, accepts `cmd`/`super`/`win` on every platform — so a binding and the event it produces agree by construction, which `the_spelling_round_trips_through_gpui_parse` asserts. ### Event propagation | API | Mirrors | | --- | --- | | `cx.stop_propagation()` | `App::stop_propagation` | | `cx.propagate()` | `App::propagate` | ### Pointer | API | Mirrors | | --- | --- | | `on_mouse_down(button, handler)` | `InteractiveElement::on_mouse_down` | | `on_mouse_up(button, handler)` | `InteractiveElement::on_mouse_up` | | `on_mouse_down_out(handler)` | `InteractiveElement::on_mouse_down_out` | | `on_scroll_wheel(handler)` | `InteractiveElement::on_scroll_wheel` | | `MouseButtonEvent` | `button`, `click_count`, `position`, `local_position?`, `bounds?`, `modifiers` | | `ScrollWheelEvent` | `delta` (pixels), `delta_lines?`, `touch_phase`, `position`, `local_position?`, `bounds?`, `modifiers` | `on_mouse_down_out` is how a surface a script drew itself gets dismissed by a press elsewhere — the listener base's own components close on. **Where this family is wired, and where it is not.** The keyboard handlers, the four pointer handlers, `on_action` and `key_context` are installed on a plain `div`, `h_flex` or `v_flex` — the elements the shell owns outright. Every other component builds its own base type and hangs its own listeners on it, so `Button.new("save").on_key_down(...)` is recorded in the description and never reaches GPUI. That was silent in the first two commits and is the second defect the review fixed: it is reported now, the way `tooltip` already reports the same shape of gap, and stated in the declarations and both locales. Widening it is one call per component rather than a new mechanism. ### Actions and key bindings | API | Mirrors | | --- | --- | | `cx.bind_keys(bindings)` → count | `App::bind_keys` | | `key_context(context)` | `InteractiveElement::key_context` | | `on_action(action, handler)` | `InteractiveElement::on_action` | | `window.dispatch_action(action)` | `Window::dispatch_action` | | `KeyBinding` | `{ keystroke, action, context? }` | | `ActionEvent` | `{ action }` | New Rust type `gpui_shell::action::ShellAction` — one action type carrying the script's id, because a script cannot produce a Rust type. ### Window Measurements, all legal from `render`: | API | Mirrors | | --- | --- | | `window.rem_size()` | `Window::rem_size` | | `window.line_height()` | `Window::line_height` | | `window.viewport_size()` | `Window::viewport_size` | | `window.bounds()` | `Window::bounds` | | `window.mouse_position()` | `Window::mouse_position` | | `window.appearance()` | `Window::appearance`, reduced to `"light"` / `"dark"` | | `window.is_window_active()` | `Window::is_window_active` | | `window.is_fullscreen()` | `Window::is_fullscreen` | | `window.is_maximized()` | `Window::is_maximized` | Changes, refused from `render`: | API | Mirrors | | --- | --- | | `window.set_rem_size(size)` | `Window::set_rem_size` | | `window.refresh()` | `Window::refresh` | | `window.focus_next()` / `window.focus_prev()` | `Window::focus_next` / `focus_prev` | | `window.activate_window()` | `Window::activate_window` | | `window.minimize_window()` | `Window::minimize_window` | | `window.zoom_window()` | `Window::zoom_window` | | `window.toggle_fullscreen()` | `Window::toggle_fullscreen` | ### Components | API | Notes | | --- | --- | | `Avatar.new()` | Renders its `image` slot, or `fallback` when there is none | | `AvatarImage.new(path)` | Slot type; not an element on its own | | `AvatarFallback.new()` | Slot type | | `Accordion.new(id)` | The group | | `AccordionItem.new()` | Owns `open`, passes it to both halves | | `AccordionHeader.new(trigger)` | Takes the trigger at construction, as `Popup.new` does | | `AccordionPanel.new()` | Out of the tree while shut, unless `keep_mounted(true)` | | `AccordionTrigger.new(id)` | Announces the state, `on_change` asks for the other | | `Pagination.new(id)` | The navigation landmark; the buttons are the script's | | `pagination_items(current, total, visible?)` | The ellipsis layout — the part a script cannot write | | `CalendarState.new()` | Retained: `month_days()`, `year()`, `month()`, `today()`, `value()`, `set_value()`, `next_month()`, `prev_month()`, `on("change")`, `release()` | A date is `null`, `"YYYY-MM-DD"`, or a `[start, end]` pair. The pair form is preserved even when the end is unset: `["2026-08-03", null]` stays a pair rather than collapsing to its start, because a half-finished range and a single day hold the same one date and base branches on the difference in `is_single`, `is_complete` and `is_in_range`. Getting that wrong in both directions was the first defect the review fixed. New element methods: `image`, `fallback`, `header`, `panel` (slots); `aria_level`, `keep_mounted`. New types: `PaginationEntry`, `CalendarDate`, `CalendarStateHandle`, `Size`. ## Two components deliberately not bound **`Calendar`** (the element). It walks the month grid calling a renderer once per cell — up to forty-two crossings into JavaScript per frame, from inside GPUI's layout pass, for cells that carry no behavior at all. `CalendarState` is bound instead, and `month_days()` gives a script the one thing it cannot work out for itself. Reading the grid and drawing the cells is the same work minus the crossings. **`AlertDialog`**'s four parts (`Backdrop`, `Popup`, `Title`, `Description`) are `div`s with fixed ids — `div().id("alert-dialog-title")` is the same element. `window.open_dialog` already exists. Binding them would add names without adding behavior. `ColorPicker` is not here either: its state is an entity holding five more (an `InputState` and four `SliderState`s), which is a piece of work on its own rather than a corner of this one. ## The one thing worth reviewing closely `ShellAction` collapses every script action into one Rust type. GPUI matches an action listener by `TypeId` and, during the bubble phase, **stops after the first listener that matches** — an action is handled once, by the innermost element claiming it. That is right when every action is its own type. It is wrong when they share one: two `on_action` registrations on an element would be two listeners with the same `TypeId`, and the first would swallow every action including the ones meant for the second. So `materialize` installs **one** GPUI listener per element however many actions were registered, and does by id what GPUI does by type — finding the handler, or calling `cx.propagate()` so an unclaimed action carries on outward. `ShellAction::partial_eq` compares ids for the same reason: comparing types would make every script action equal to every other, which is the question GPUI asks when it looks up the chord bound to one. Two tests pin both halves: `a_script_binds_a_chord_and_the_action_reaches_its_handler` (two actions on one element, both fire) and `an_unclaimed_action_carries_on_to_an_outer_element`. ## What the review changed The first two commits were reviewed against `gpui-base` and GPUI source after the fact, and the third commit is what that found. Four defects, none of which any test in the first two commits could have caught: 1. **A single calendar date was stored as a half-open range.** Both encode as one date and render as the same string, so the round trip a script can see agreed either way — only `state.date()` disagreed. The read side had the same bug in mirror image. 2. **Input handlers were dropped in silence off a plain element** (above). 3. **Five slot resolvers dropped motion.** `AccordionPanel.new().transition("height", ...)` recorded a transition and animated nothing. They share one resolver now, which samples motion the way every other node does. 4. **Two readers cost three crossings each.** `__window_state` and `__calendar_position` returned three values together while the prelude spelled each reader as its own method, so every reader paid for all three. Both are three host functions now. Plus: an accordion's malformed-header placeholder is keyed by slot address rather than a fixed string, so two broken items no longer share an element id; and a comment claiming base's `Pagination` announces from its state now says what base does with it, which is nothing. **One fix has no test, deliberately.** The motion one: `apply_motion` shows up only in the refinement after materialize, and this suite can read the description tree but not that. A test that asserted the description still carries `:transition(...)` would have passed before the fix as well. Its correctness rests on sharing the resolver with `NumberInput`, which had it right. ## Tests Eleven new tests. Ten are end-to-end through a real window, a real focus path and real input: - `a_script_hears_the_keys_typed_at_a_focused_element` - `stop_propagation_keeps_a_key_event_at_the_element_that_handled_it` - `a_script_hears_presses_releases_and_presses_outside` - `the_window_answers_its_measurements_and_refuses_changes_during_render` - `a_script_binds_a_chord_and_the_action_reaches_its_handler` - `an_unclaimed_action_carries_on_to_an_outer_element` - `an_avatar_renders_its_image_or_its_fallback` - `pagination_items_lay_out_the_pages_and_their_gaps` - `an_accordion_item_passes_its_open_state_down_to_its_trigger` - `a_calendar_state_answers_the_month_grid_and_moves_it` The eleventh, `every_added_script_api_is_reachable_under_its_documented_name`, is a breadth test: it touches every name in the tables above from a script, under the name this description uses. It catches the failure the ten behavioral tests cannot — a member bound in the host but unreachable from the prelude, or reachable under a different name. That mistake compiles and passes everything else, and is otherwise found only by a reader typing what the docs say. Two more came with the keystroke fix, and they are written to be platform-independent on purpose: they build a chord from `Modifiers` rather than from a simulated key press, because taking it from a key press is exactly what made the original bug invisible to a suite running on one platform. Three more came with the review, and two of them read `state.date()` from Rust rather than through the script — because reading it through the script is exactly what could not tell the two `Date` variants apart. 512 pass on `--release` with `-D warnings`, clippy clean, `cargo machete` clean, English and Chinese docs in step. All eight CI checks green. ## Breaking Changes None. Every addition is a new name; no existing signature changed. ## AI assistance Implemented with Claude Code. The API surface, the `ShellAction` dispatch design and the not-bound decisions were reviewed against `gpui-base` and GPUI source; every claim in this description is backed by a test or by the cited source. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> | 1 个月前 | |
shell: Ship gpui-shell as an installable runtime with run, new and packaging (#3464) Makes `gpui-shell` a runtime you install and ship applications with, rather than only a development host run through `cargo run`. Releases now publish a prebuilt `gpui-shell` binary, installed applications live under `~/.gpui-shell/apps`, and an application can be packaged as the binary plus its script directory. ## What changes **One package, one command.** `gpui-component-shell` is merged into `gpui-shell` as the default `components` feature (`gpui_shell::components`). Cargo forbids the cycle a feature on a separate crate would need, so the catalog moved in. Without the feature, the runtime still does not depend on `gpui-component`, and CI checks that build. **Command line.** It runs one application per process and has no interface of its own: ```text gpui-shell run <name> | -d <dir> [--watch] [--dev] # ~/.gpui-shell/apps/<name>, or a directory gpui-shell # the application packaged beside the executable gpui-shell new <dir> # scaffold a blank application gpui-shell # with nothing packaged beside it: the help gpui-shell list # installed applications and their requested capabilities gpui-shell check | types | agents ... ``` - An installed or packaged application keeps its storage under its manifest `id`; a directory run with `-d` stays keyed by its path. - With no arguments the binary runs `Contents/Resources/app` inside a macOS bundle, or `app/` beside itself. This is what lets a `.app` or a Linux package be the binary plus a directory of scripts. - `GPUI_SHELL_HOME` replaces `~/.gpui-shell`, both for applications and for the Git dependency cache. - `agents` prints, or writes as `AGENTS.md`, development instructions for coding agents. They are generated from the running binary, including its module and component list. - The window is brought forward on launch, and Cmd-Q / Alt-F4 quits. - The manifest gains an optional `window` field: `{ "width": 360, "height": 520 }` sets the opening size. **Release and install.** `release.yml` builds `gpui-shell` for aarch64/x86_64 macOS, x86_64 Linux and x86_64 Windows. Asset names carry no version, e.g. `gpui-shell-aarch64-apple-darwin.tar.gz` plus `.sha256`, so `releases/latest/download/<asset>` always resolves. `curl -fsSL https://gpui-kit.com/shell/install.sh | sh` installs it to `~/.local/bin`; the script source is `website/public/shell/install.sh`. **Catalog.** - New `TitleBar` binding. - Catalog windows use `TitleBar::window_options()`, so Sheets and notifications line up with the title bar. - `js_story`, `js_todolist` and `js_dock` now render a `TitleBar`. **Fixes.** - `localStorage.getItem` / `key` return `null` rather than `undefined` for a missing entry. This is what made `js_todolist` warn "storage unavailable". - OtpInput repaints on every keystroke. Its state was a plain entity, so its notify dirtied no view, and the cached content replayed until the pointer moved. - Importing `gpui-component` without a catalog now explains how to get one, instead of listing it as a built-in module. - The load-failure page's copy action is an icon button (copy, then check), so it can no longer overflow the panel. **Docs.** - Getting Started, en + zh-CN: installing, `run` / `new` / `list` / `agents`, and installed and packaged applications. - New Packaging page, en + zh-CN: macOS `.app` with `Info.plist`, `lipo`, signing and notarization; a Linux archive and `.desktop` entry; a Windows folder. ## Public API ### gpui-shell - Feature `components` (default): the `gpui-component` catalog. Optional dependency on `gpui-component`. - `pub mod components` (with `components`): - `pub fn init(cx: &mut App)`: initializes `gpui-component` and the runtime (was `gpui_component_shell::init`). - `pub fn components() -> Result<FrozenComponentRegistry, RegistryError>`: the frozen catalog (was `gpui_component_shell::components`). - `pub fn new_isolated_runtime() -> anyhow::Result<Rc<ShellRuntime>>`: an isolated runtime with the catalog (was `gpui_component_shell::new_isolated_runtime`). - `pub struct plugin::WindowSize` with `width(&self) -> f32` and `height(&self) -> f32`: a manifest's opening window size. - `PluginManifest::window(&self) -> Option<WindowSize>`: the manifest's `window`, if any. - `ManifestProblem::Window(String)`: an invalid `window` field. - `host::InvocationKind::{List, Agents, New}`: classifications for the new commands. - Manifest field `window: { width: number, height: number }`: logical pixels, positive, no other keys. - JavaScript: `window.start_window_move(): void` (GPUI `Window::start_window_move`), so a script-drawn title bar can drag the window. - JavaScript, `"gpui-component"`: `TitleBar` (`new TitleBar()`, with styles, `child`/`children` and `on_close_window(callback: (cx) => void)`). It is declared in the generated `gpui-kit.d.ts`. ## Validation - `cargo test -p gpui-shell --no-fail-fast`: 34 test binaries, 990 tests, no failures. - `cargo clippy -p gpui-shell --all-targets -- -D warnings`, `cargo fmt --check -p gpui-shell`, `cargo machete`, `typos`: clean. - `cargo check -p gpui-shell --no-default-features --features quickjs`: the runtime builds without the catalog. - `gpui-shell check -d` passes for `examples/js_todolist`, `examples/js_dock` and `examples/js_story`. - `npm --prefix crates/shell/tests/types test` and `node examples/js_story/fixtures/verify-coverage.mjs` pass. - Manually: `new` → `check` on a scaffolded application; a hand-built `Demo.app/Contents/{MacOS,Resources/app}` starts its bundled application with no arguments; OtpInput repainting on every keystroke was confirmed by eye in the running gallery. Not verified: - The release job and the install script have not run against a real release. - Bringing the window forward could not be confirmed from a background shell; macOS will not let it take focus from another app. It needs a check from a terminal launch. - Cmd-Q / Alt-F4 was not pressed in testing. - The manifest `window` size was unit-tested only, not opened end to end. - The TitleBar in `js_story`, `js_todolist` and `js_dock`, and whether a Sheet now opens flush under it, has not been looked at in a running window. ## AI-generated This change was written with Claude Code (one main session, two sub-agents for TitleBar and OtpInput, and a separate session for the manifest `window` field), reviewed and directed by the author. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> | 1 天前 | |
chore: Use gpui-kit (#2929) ## Summary Zed owns the `gpui` crate names on crates.io and publishes them only occasionally. This PR adds `script/bump-gpui.ts` (Bun) to publish a snapshot of any Zed commit to crates.io under our own names, a weekly `Release GPUI` workflow that runs it, and switches the workspace, README and docs to those crates. | Zed crate | Published as | | --- | --- | | `gpui` | `gpui-pre` | | `gpui_platform` | `gpui-pre-platform` | | `gpui_macros` | `gpui-pre-macros` | | `reqwest_client` | `gpui-pre-reqwest-client` | | `gpui_<x>` / other internal crates | `gpui-pre-<x>` | What the script does: - Fetches the requested Zed revision (`--rev`, default `main`) into `target/gpui-pre/zed`, or reuses a checkout passed with `--zed`. - Walks the workspace path dependencies of the four root crates and publishes the whole closure (25 crates today) at one version, `<VERSION>.<N>` (e.g. `0.3.12`): the `VERSION` constant at the top of the script (`0.3`) plus a patch number that continues from the highest one crates.io already has (`0.3.0` first). A run that stopped part-way resumes the same number. A positional argument publishes an explicit version instead. - Keeps each crate's original name as `[lib] name`, so consumers write `gpui = { package = "gpui-pre", version = "0.3.0" }` and `use gpui::*` unchanged; `actions!` and `#[derive(Action)]` keep working. - Drops optional dependencies that come from git without a crates.io version (`proptest`, `async-tar`) together with the features that enable them, then removes optional dependencies nothing can enable any more. This keeps Zed's `util` crate (which needs Zed's git-patched `async-process`) out of the publish set. - Swaps the workspace `reqwest` dependency (Zed's git fork) for the hand-published `gpui-pre-reqwest` through `DEPENDENCY_OVERRIDES`. - Vendors the gpui source files that `gpui_apple`'s build script reads from `../gpui`, since a crate unpacked from crates.io has no such sibling. - Verifies everything with `cargo publish --workspace --dry-run`, then publishes with `cargo publish --workspace`, re-checking crates.io before each attempt and waiting out the new-crate rate limit so a run can be resumed. Workflow: - `.github/workflows/release-gpui.yml` runs the script every other Sunday (even ISO weeks, gated by a small `cadence` job since cron cannot express two weeks) at 18:00 Beijing time (`0 10 * * 0` UTC) and on `workflow_dispatch` with optional `rev` and `version` inputs, on `macos-latest` so `gpui_apple`'s Metal shader build script is verified. It uses the `CARGO_REGISTRY_TOKEN` secret and writes the published crate table to the job summary. Workspace and docs: - `Cargo.toml` now depends on `gpui-pre` 0.3.0 (`gpui`, `gpui_platform`, `gpui_web`, `gpui_macros`, `reqwest_client`, `sum-tree`) and `gpui-pre-reqwest` 0.12.15 instead of the Zed git repository, `zed-sum-tree` and Zed's reqwest fork. The `[profile.dev.package]` overrides follow the new package names. - README (en/zh-CN), the site docs (en/zh-CN), the gpui-component skill, and the crate READMEs show the `gpui-pre` dependency lines. - CONTRIBUTING documents the workflow and version scheme. ## gpui-kit `crates/kit` (published as `gpui-kit`, names already reserved on crates.io) is the one crate applications depend on. It pins the matching `gpui-pre-*` set and re-exports every layer: | Path | Crate | Feature | | --- | --- | --- | | `gpui_kit::gpui` | `gpui` | always | | `gpui_kit::platform` | `gpui_platform` | always | | `gpui_kit::base` | `gpui-base` | always | | `gpui_kit::component` | `gpui-component` | `component` (default) | | `gpui_kit::assets` | `gpui-kit-assets` | `assets` (default) | | `gpui_kit::shell` | `gpui-shell` | `shell` (default) | | `gpui_kit::webview` | `gpui-wry` | `webview` | `gpui_kit::application()` and `gpui_kit::init()` cover startup, and `gpui_kit::prelude::*` also brings the crate names into scope, so `gpui::…` paths and the `actions!` / `#[derive(Action)]` macros (which expand to `gpui::…`) work without a direct `gpui` dependency. The `gpui-component` features (`inspector`, `decimal`, `tree-sitter*`) are forwarded by name. README, the site docs, the skill, the crate READMEs and `hello_world` now show `gpui-kit = "0.6"` alone; the docs no longer link GPUI to gpui.rs. Workspace crates are bumped to 0.6.0. ## Breaking Changes `gpui-component-assets` is renamed `gpui-kit-assets`; the `links` key is unchanged so `gpui-component`'s build script still finds the icon directory. ```diff -gpui-component-assets = "0.5" +gpui-kit-assets = "0.6" ``` ```diff -use gpui_component_assets::Assets; +use gpui_kit_assets::Assets; ``` Applications that listed GPUI themselves can drop those lines: ```diff -gpui = { package = "gpui-pre", version = "0.3.0" } -gpui_platform = { package = "gpui-pre-platform", version = "0.3.0", features = ["font-kit"] } -gpui-component = "0.5" +gpui-kit = "0.6" ``` ## Release gate Before anything is uploaded, `script/bump-gpui.ts` builds and tests this repository against the staged crates (mirrored outside the checkout and injected with `--config patch.crates-io…`; `Cargo.lock` is scratch-updated and restored, and `cargo metadata` proves the patch resolved). CI's `check`, `clippy` and `test` must pass, so a Zed change that no longer fits `gpui-component` fails the release instead of reaching applications through their caret `gpui-pre` requirement on `cargo update`. `--skip-kit-check` bypasses it. The workflow installs the system dependencies for that step. The facade-aware macro rewrite now copies `#[cfg]` gates onto the wrapped bodies (the published 0.3.1 `gpui-pre-macros` breaks release builds without `inspector`; 0.3.2 fixes it) and the `actions!` rewrite is scoped to the `macro_rules!` block. `gpui-shell` is not published for now (its `llrt_*` dependencies are git-only) and is no longer part of `gpui-kit`; `gpui-fps` is. `release.yml` publishes our crates with one ordered `cargo publish -p …`. ## Verification - `./script/bump-gpui.ts --dry-run --zed <local zed checkout>`: all 25 crates package and build; `gpui-pre-reqwest` resolves from crates.io. - With a temporary `[patch.crates-io]` pointing at the staged workspace (not committed), `cargo check --workspace` passes for every crate, story, story-web and example. - A scratch consumer crate depending on the staged crates via `package = "gpui-pre"` passes `cargo check`, including `actions!`, `#[derive(Action)]` and `collections::` by its original lib name. - `gpui-kit`: `cargo check` with default, no, `base`-only, `component`-only and `webview,inspector,tree-sitter-rust` features, its doc tests, `cargo check --workspace`, the CI clippy set plus `gpui-kit` and `hello_world` with `--deny warnings`, and `cargo machete` all pass against the staged `gpui-pre`. - `tsc --strict` with `@types/bun`, `typos`, and a YAML parse of the workflow pass. `gpui-pre` 0.3.0 is not on crates.io yet, so CI cannot resolve dependencies until the `Release GPUI` workflow has run (by hand, or on the next even ISO week Sunday). A requirement of `0.3.0` accepts every later `0.3.x` as well. `Cargo.lock` is left for that first resolution. ## AI assistance The script, workflow, dependency switch and documentation were written with Claude Code and reviewed by hand. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01D2PVGzukYKuXnb3LB5s53T --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> | 1 个月前 |
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 1 个月前 | ||
| 1 天前 | ||
| 1 个月前 |