[00135] Implement Rust Widget APIs for Blade Navigation and Advanced Layout Components - #132
Merged
rorychatt merged 7 commits intoAug 10, 2026
Conversation
…r widgets Five navigation/layout widgets that the React frontend already ships but Rust could not build (issue #125). Each is a #[derive(Widget)] struct with a fluent builder, wire props matching the frontend's expectations (numPages camelCased, per-item hasOnClick on a breadcrumb, PascalCase ToolbarItemVariant because ToolbarWidget compares it verbatim on a nested value), and state event callbacks. BreadcrumbItem and ToolbarItem are plain serde props rather than child widgets: they carry no id, so the widget fires one event carrying the index or tag.
ivy_widget gains the five mechanical entries, and the two hardcoded counts (>= 38, assert_eq!(.., 38)) move to 43 alongside the five new constructors. IVY_EVENT_NAMES grows to 12 with OnClose/OnRefresh (BladeWidget.tsx), OnItemClick (BreadcrumbsWidget.tsx) and OnSelect (ToolbarWidget.tsx), each of which some frontend widget really reads via events.includes(..).
EventName gains Close, Refresh, ItemClick and Select, and normalize now strips a leading `On` as well as `on`. Without both, a widget registered under the derive's `close` was unreachable from the `OnClose` the Ivy frontend sends and that shared::ivy_node emits in its events array -- canonicalize fell through to the raw string and the lookup missed, despite ivy_node's module doc claiming `OnClick`, `onClick` and `click` were all accepted. The uppercase-letter guard still applies to both prefixes, so `online` and `Online` keep their `on`.
Adds a widget_harness app per widget, the matching render arms in the e2e harness page, and navigation.spec.ts. The harness page mirrors the frontend's own conditionals rather than rendering everything the node carries: BladeWidget hides Close on index 0, BreadcrumbsWidget never links the last crumb, and ToolbarWidget ignores an item with no tag. A spec that asserted otherwise would pass against the harness and fail against Ivy. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four pages under 03_widgets, following 27_list.md. Each records where the Rust API departs from Ivy's: there is no use_blades hook (the stack is view state), and crumbs and toolbar items are props rather than widgets, so a single widget-level handler receives an index or tag instead of one closure per item. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Combines this plan's blade/breadcrumbs/pagination/toolbar widgets with main's animation/confetti/stacked_progress/wireframe widgets (PR #131), re-derives the widget_names.rs mapped-type count (38 -> 48) from the actual merged list, and renumbers this plan's doc pages (34-37 -> 37-40) to avoid colliding with main's newly added 34-36.
rorychatt
deleted the
tendril/00135-ImplementRustWidgetAPIsForBladeNavigationAndAdvancedLayoutCo
branch
August 10, 2026 09:36
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #125
00135 — Implement Rust Widget APIs for Blade Navigation and Advanced Layout Components
Issue #125. Worktree:
Worktrees/Rusty-Framework, branched fromc528820onmain.What shipped
Five Rust widgets that previously existed only as React components, plus the adapter, harness,
test and doc work that makes them usable and gated.
rusty/src/widgets/blade.rsBlade,BladeContainerrusty/src/widgets/breadcrumbs.rsBreadcrumbs,BreadcrumbItemrusty/src/widgets/pagination.rsPaginationrusty/src/widgets/toolbar.rsToolbar,ToolbarItem,ToolbarItemVariantrusty/src/widgets/mod.rspub mod+ fourpub uselinesrusty/src/shared/widget_names.rsivy_widgetentries; three counts 38 → 43, 25 → 30rusty/src/shared/ivy_node.rsIVY_EVENT_NAMES8 → 12; fiveto_ivy_nodetestsrusty/src/core/event_registry.rsEventNamevariants + case-insensitiveOnstrip (deviation, below)rusty-server/src/bin/widget_harness.rsWidgetKindvariants, arms andimpl Viewappse2e/app/index.htmlrenderWidgetcasearms + CSSe2e/tests/widgets/navigation.spec.tsrusty-docs/docs/03_widgets/34_blade.md…37_toolbar.mdCommits:
59eeb8e(widgets),659f5dc(adapter),45bfa47(event names),4d079bc(e2e),d81cc62(docs),fb73f7c(cargo fmt).Design decisions worth knowing
Items are props, not widgets.
BreadcrumbItemandToolbarItemare plain serde structs, sothey carry no widget id and cannot hold a closure. The frontend fires one event on the widget —
OnItemClickwith an index,OnSelectwith a tag — and reads per-item booleans(
hasOnClick) to decide how to render.ToolbarItem.childrenisVec<ToolbarItem>, notVec<Element>, which is also what keeps it clear ofwidget_checks.rs's rule that aVec<Element>field not named
childrensilently loses ids.ToolbarItemVariantserializes PascalCase on purpose.ToolbarWidgetcomparesitem.variant === "Group"/"Separator", and that comparison is on a nested value.ivy_node'sENUM_PROPSrecasing only reaches top-level props, so arename_allhere wouldproduce
"group"and silently break group rendering.pageis 1-based,0means nothing selected. That is howPaginationWidgetreads it (ittests
!page), soPagination::new(0, n)is a meaningful state, not a bug.No
BladeHeaderslot and nouse_blades.BladeWidget's header slot needs anIvy.Slotchild node and Rust has no
Slotwidget; Ivy'sUseBladespush/pop controller is the app's ownuse_state. Both are recorded inblade.rs's doc comments, as the plan required.Deviation from the plan:
EventNameThe plan stated that no
EventNamevariant was needed, because "the custom-name fallback inevent_registry.rs covers it" and a browser
OnItemClick"normalizes toitemclickand reaches thehandler".
That was wrong about the code as it stood.
normalizeuseds.strip_prefix("on"), which iscase-sensitive:
"OnClose"lowercased to"onclose", matched no variant, andcanonicalizereturned the raw
"OnClose"— never equal to the registered"close". The plan's own Testssection requires
registry.dispatch("w-0", "OnClose", Null)to fireon_close, andivy_node'smodule doc already claimed PascalCase wire names are accepted.
45bfa47therefore:Close,Refresh,ItemClick,Selectvariants withas_str/from_strarms andextends the existing round-trip test's
allarray;Onas well ason, gated on the following character beinguppercase, so
Online/onlinekeep theiron;test_from_str_accepts_ivy_pascal_case_wire_names.This makes the plan's specified tests pass rather than rewriting them to match broken behaviour. It
also fixes the same latent gap for every pre-existing PascalCase event name (
OnClick,OnChange,…), which is why it is called out here rather than buried.
Verification
fb73f7c), re-check exit 0--workspace --all-targets --no-default-features -- -D warnings, exit 0, 0 warningssrc/frontendchange; run anyway — 563 files formatted, 548 lint-clean,tsc -bexit 0tsc -b && vp buildexit 0; only the pre-existing chunk-size warningsrc/frontendchange; 1 pre-existing failure (ci-step.test.ts), proven out of scopecheck-harness-script.jspassesThe one red test anywhere is
src/frontend/src/__tests__/ci-step.test.ts, which asserts theliteral gate commands in
.github/workflows/ci.yml. Those gained--no-default-featuresand theassertion was never updated.
git diff --name-only c528820..HEADtouches neither file, so everyinput is byte-identical to
main— it fails there too. Details inVerification/VitePlusTest.md,carried into
Artifacts/recommendations.md.Environment notes
npm ciine2e/fails withUNABLE_TO_GET_ISSUER_CERT_LOCALLYunless you pass Node's bundledCA store:
NODE_OPTIONS=--use-bundled-ca npm ciworks and is what the recorded run used.pnpm installinsrc/frontendneeds no flag (it resolves from the local store).text_input read_only › is writable when read_only is not set— a
page.gotoERR_ABORTEDinsidenavigateToHarness, passing on the configured retry. Thesame suite ran clean earlier in the session on the identical tree.
/Users/rorychatt/.tendril/Repos/Rusty-Frameworkwas read only. Worktree left on disk,
git statusclean.Commits:
Created using Ivy Tendril.