Studio binding/bus: indicators, bus pane, URI refs, declarative defaults (M0–M5)#86
Merged
Merged
Conversation
…gger_ids Authored bindings now live only at node-def roots (see ADR). PlaylistDef gains the root ByKey trigger slot; PlaylistEntry loses its bindings and per-entry trigger map and gains trigger_ids config (lowest entry index wins duplicate claims). The loader's playlist entry-binding special case is deleted; trigger registers like time. detect_triggered_entry resolves one root slot and edge-detects per message id. Supporting: U32List slot value newtype + SlotValue derive support for Vec<u32> newtype fields; examples and checked-in schemas regenerated. ADR: docs/adr/2026-07-06-bindings-at-node-roots.md Plan: ~/.photomancer/planning/lp2025/2026-07-06-studio-binding-bus (M0) Co-Authored-By: Claude Fable 5 <[email protected]>
…ublish routes Fixes the long-standing 'unbound when bound' indicator: SlotController now parses the def root's bindings map into per-slot binding facts and the node controller distributes them across def (consumed source) and state (published target) roots on every refresh, clearing on binding removal. Bound state wins over an unset authored fallback in ui_source(). UI: compact accent BindingChip inline on config rows and produced value/product rows (channel name, bus# in tooltip/popover; publish direction wears a → glyph); popovers say Bound from / Published as via the existing aspect wording. New published-value stories; baselines regenerated (chips now appear across node/project/shell stories). Plan: ~/.photomancer/planning/lp2025/2026-07-06-studio-binding-bus (M1) Co-Authored-By: Claude Fable 5 <[email protected]>
…ments
Binding/bus-linked state is a system concept, not a success state — green
was overloaded (accent, good-text, and bound all shared mint). New
--studio-status-bound-{bg,border,text} violet tokens; IconMenuTone::Bound,
DetailSectionTint::Bound, AspectTone::Bound; bound row tint, chips, popover
sections and headings now wear violet while Valid/No-changes stay green.
Story baselines regenerated.
Plan: ~/.photomancer/planning/lp2025/2026-07-06-studio-binding-bus (M1 gate)
Co-Authored-By: Claude Fable 5 <[email protected]>
…bus channels New BindingGraphProbeRequest/WireBindingGraph surface (ADR): one probe returns every registered binding (authored and default, including implicit runtime consumed slots like fixture.input) plus bus channel summaries with providers/consumers as indices into the binding list and on-demand resolved values. Identity travels as NodeId + slot path; origin derives from priority (authored 0 / default_fallback). Deletes the never-implemented ExplainSlot probe. Engine: NodeBindingIndex gains a symmetric bus_sources map and channel enumeration; NodeTree exposes bindings_with_refs/consumers_for_bus/ bus_channels; Engine::resolve_bus_channel_value resolves outside the tick reusing the frame cache. Probe covered by harness tests (topology, value resolution, priority ordering, origin) and a fyeah-sign end-to-end test (two trigger writers, implicit fixture input consumer, default clock provider). Studio-core: ProjectSync carries a binding-graph subscription — reads attach the probe while subscribed, results cache on the sync, unsubscribe drops the snapshot; ProjectController exposes binding_graph()/ set_binding_graph_subscribed for the M3 bus pane. ADR: docs/adr/2026-07-06-binding-graph-probe.md Plan: ~/.photomancer/planning/lp2025/2026-07-06-studio-binding-bus (M2) Co-Authored-By: Claude Fable 5 <[email protected]>
…rows New Bus pane (temporary home: main column under the Project pane; final placement is an open UX question per the roadmap). UiBusView projects the binding-graph snapshot through ProjectController: channels with kind and live value, writers → readers as violet site chips (node label + slot, DEF badge for default-origin), primary visual.out highlighted, and every site dispatches the node's Focus action — the bus links back to nodes (D7). Empty state explains the bus concept. The binding-graph probe now rides along on every loaded-project read. Node cards gain binding-derived rows: effective bindings anchored to slots with no backing row (implicit runtime consumed slots like fixture.input, output.input) render as read-only bound rows — a node's visible surface is its authored slots, state slots, and its wiring. Tests: bus view projection (labels, focus, origins, primary flag), binding-derived rows (synthesis + no-duplicate), pane registration. Stories: fyeah-shaped bus, empty state; baselines regenerated. Plan: ~/.photomancer/planning/lp2025/2026-07-06-studio-binding-bus (M3) Co-Authored-By: Claude Fable 5 <[email protected]>
The minimal examples/basic demo (clock→shader→fixture→output) exercised only three bus channels. fyeah-sign adds a button + radio bridge (both writing bus#trigger) and a playlist switching idle/blast visuals, so the demo now shows off the trigger channel, multi-writer arbitration, and playlist switching — a much fuller tour of the system. Button/radio are virtual in the browser sim (nothing physically fires), but every binding registers, so the bus pane shows the real topology. STUDIO_DEMO_PROJECT_ID renamed to examples/fyeah-sign; demo file set and its tests updated (storage id 'studio' unchanged). Plan: ~/.photomancer/planning/lp2025/2026-07-06-studio-binding-bus Co-Authored-By: Claude Fable 5 <[email protected]>
…-design # Conflicts: # lp-app/lpa-studio-core/src/app/node/ui_config_slot.rs # lp-app/lpa-studio-core/src/app/studio/studio_controller.rs # lp-app/lpa-studio-web/assets/tailwind.css # lp-app/lpa-studio-web/src/app/node/config_slot_row.rs # lp-app/lpa-studio-web/story-images/studio__layout__studio-shell__overview__lg.png # lp-app/lpa-studio-web/story-images/studio__layout__studio-shell__overview__md.png # lp-app/lpa-studio-web/story-images/studio__layout__studio-shell__overview__sm.png # lp-app/lpa-studio-web/story-images/studio__layout__studio-shell__simulator-ready__lg.png # lp-app/lpa-studio-web/story-images/studio__layout__studio-shell__simulator-ready__md.png # lp-app/lpa-studio-web/story-images/studio__layout__studio-shell__simulator-ready__sm.png # lp-app/lpa-studio-web/story-images/studio__node__config-slot-row__all-states__lg.png # lp-app/lpa-studio-web/story-images/studio__node__config-slot-row__all-states__md.png # lp-app/lpa-studio-web/story-images/studio__node__config-slot-row__all-states__sm.png # lp-app/lpa-studio-web/story-images/studio__node__config-slot-row__editable-clean-controls__md.png # lp-app/lpa-studio-web/story-images/studio__node__config-slot-row__live-detail-popup__lg.png # lp-app/lpa-studio-web/story-images/studio__node__config-slot-row__live-detail-popup__md.png # lp-app/lpa-studio-web/story-images/studio__node__config-slot-row__map-add-immediate__lg.png # lp-app/lpa-studio-web/story-images/studio__node__config-slot-row__optional-scalar-included__lg.png # lp-app/lpa-studio-web/story-images/studio__node__config-slot-row__overview__lg.png # lp-app/lpa-studio-web/story-images/studio__node__config-slot-row__overview__md.png # lp-app/lpa-studio-web/story-images/studio__node__config-slot-row__overview__sm.png # lp-app/lpa-studio-web/story-images/studio__node__config-slot-row__unsaved-detail-popup__lg.png # lp-app/lpa-studio-web/story-images/studio__node__config-slot-row__unsaved-detail-popup__md.png # lp-app/lpa-studio-web/story-images/studio__node__config-slot-row__unsaved-detail-popup__sm.png # lp-app/lpa-studio-web/story-images/studio__node__config-slot-row__unset-value__lg.png # lp-app/lpa-studio-web/story-images/studio__node__node__dirty-live-header-tint__lg.png # lp-app/lpa-studio-web/story-images/studio__node__node__overview__lg.png # lp-app/lpa-studio-web/story-images/studio__node__node__overview__md.png # lp-app/lpa-studio-web/story-images/studio__node__node__overview__sm.png # lp-app/lpa-studio-web/story-images/studio__node__produced-product__overview__lg.png # lp-app/lpa-studio-web/story-images/studio__node__produced-product__overview__md.png # lp-app/lpa-studio-web/story-images/studio__node__produced-product__overview__sm.png # lp-app/lpa-studio-web/story-images/studio__node__produced-value__overview__lg.png # lp-app/lpa-studio-web/story-images/studio__node__produced-value__overview__md.png # lp-app/lpa-studio-web/story-images/studio__node__produced-value__overview__sm.png # lp-app/lpa-studio-web/story-images/studio__node__slot-record-editor__nested-record__lg.png # lp-app/lpa-studio-web/story-images/studio__node__slot-record-editor__nested-record__md.png # lp-app/lpa-studio-web/story-images/studio__node__slot-record-editor__nested-record__sm.png # lp-app/lpa-studio-web/story-images/studio__node__slot-record-editor__overview__lg.png # lp-app/lpa-studio-web/story-images/studio__node__slot-record-editor__overview__md.png # lp-app/lpa-studio-web/story-images/studio__node__slot-record-editor__overview__sm.png # lp-app/lpa-studio-web/story-images/studio__project__project-pane__change-list-overflow__md.png # lp-app/lpa-studio-web/story-images/studio__project__project-pane__overview__lg.png # lp-app/lpa-studio-web/story-images/studio__project__project-pane__overview__md.png # lp-app/lpa-studio-web/story-images/studio__project__project-workspace__overview__lg.png # lp-app/lpa-studio-web/story-images/studio__project__project-workspace__overview__md.png # lp-app/lpa-studio-web/story-images/studio__project__project-workspace__overview__sm.png # lp-app/lpa-studio-web/story-images/studio__project__project-workspace__project-ready__lg.png # lp-app/lpa-studio-web/story-images/studio__project__project-workspace__project-ready__md.png # lp-app/lpa-studio-web/story-images/studio__project__project-workspace__project-ready__sm.png
Rebuilds the generated artifacts against the merged tree: tailwind.css from source (main's classes + our violet status-bound tokens) and 38 story baselines reflecting main's option-presence rows and our violet binding treatment + bus pane rendering together. Co-Authored-By: Claude Fable 5 <[email protected]>
The binding-graph subscription (set at mark_ready so the bus pane's probe rides every read) was wiped by begin_initial_sync, which reset the whole ProjectSync to defaults. In the demo flow (mark_ready → sync_loaded_project → begin_initial_sync) the subscription was set and then immediately nuked before the first read, so the probe never went out and the bus pane showed 'Reading' forever with 'No bus channels yet'. The subscription is a UI preference (the bus pane ships with every loaded project), not per-read data, so begin_initial_sync now preserves it while still resetting the mirror/previews/overlay. Regression test added: binding_graph_subscription_survives_begin_initial_sync. Co-Authored-By: Claude Fable 5 <[email protected]>
Union-merges the two feature threads through the node walk: ui_node_with_product_previews now takes both extra_config (M3 binding-derived rows) and asset_editor (main's inline GLSL editor). Produced value/product views keep main's SlotPane structure and regain the branch's binding surface: bus-published slots wear SlotPaneTreatment::Bound with the publish BindingChip in the body. SlotPaneTreatment::Bound is re-pointed from the green status-good family to the violet status-bound family (bound=violet convention; green is reserved for good/valid). assets/tailwind.css regenerated (tailwindcss 4.1.5) — additive: status-bound tokens + branch utilities. Story-image conflicts resolved to main's copies pending baseline regen. Co-Authored-By: Claude Fable 5 <[email protected]>
Captures the merged SlotPane surfaces: bound produced values/products wear the violet status-bound treatment (border, header wash, publish chip); bus-pane and node stories re-baselined against main's SlotPane layout. Co-Authored-By: Claude Fable 5 <[email protected]>
Implements ADR 2026-07-08-binding-ref-syntax-and-channel-naming (the D9 norms conversation outcome): - bus:<channel> replaces bus#…; BusSlotRef stores ChannelName (the bus is no longer a pretend node with slots); # reserved in bus refs for future field-within-channel fragments. - node:<path>#<slot.path> replaces slashless dot refs: RelativeNodeRef is filesystem-style (../shader, ..#, child/grandchild, multi-hop ../..), slot paths stay dotted. Unknown schemes fail loudly. - Channels go unitless: time (was time.seconds); unit truth lives in slot metadata + the future well-known registry. ChannelName docs now describe the real convention (purpose[.in|.out][/instance]) instead of the dead <kind>/<dir>/<index> letter. - Hard cutover: examples, test projects, embedded test JSON, story fixtures, smoke project, loader default-binding helpers, UI display strings. No dual-parse (no wire/disk compat during heavy dev). Validation: lpc-model 351, lpc-engine 287, studio-core 384, wire/ registry/shared suites, fw-tests scene_render_emu, schema-gen no drift, just check green. Co-Authored-By: Claude Fable 5 <[email protected]>
…rename Also: raise the story ready-state cap to 30s (polls every 50ms, so fast stories pay nothing; heavy popover stories kept blowing the 10s cap and killing baseline runs) and note the syntax change atop the two ADRs that still show the old forms. Co-Authored-By: Claude Fable 5 <[email protected]>
Each channel now wears the shared slot-pane language (D10 — one binding treatment everywhere): violet Bound frame, channel name + kind/PRIMARY badges in the title bar, live value as the centered hero, wiring chips (writers → readers, DEF badges, click-to-focus) beneath. The channel detail popup gains UiBusChannelView::visible_aspects: a Channel info section (bus:<name>, kind, value, primary role) and a Wiring section that spells out multi-writer merge semantics (D11 — merge modes and multiple sources clearly indicated). SlotPane gains an optional `badges` title-bar slot — the shared home for kind tags and PRIMARY/DEF badges across binding surfaces (M5's honest indicator will reuse it). Co-Authored-By: Claude Fable 5 <[email protected]>
…o popup Gate-review feedback: the main display shows just the value hero; the writers → readers mapping lives in the detail popup. To keep D11 (no dead ends), UiSlotAspectRow gains an optional `action` and the popup renders action-carrying rows as clickable violet focus chips — on_action now threads SlotPane → SlotDetailButton → rows, which is the same conduit M4's popover actions will use. The Wiring aspect renders as a list section instead of the single-endpoint "Bound from" header. Adds a channel-detail story (popup open on the two-writer trigger channel) so the wiring popup has baseline coverage. Co-Authored-By: Claude Fable 5 <[email protected]>
…ault ADR-2 for the binding/bus roadmap M5, user-approved at the gate: default_bind as a first-class field-shape descriptor field (attr for static fields, data field for dynamic slot-defs), bus:-only endpoints with direction derived from slot direction, D8 declared fixture/output inputs with defaults, one generic unconditional materialization pass, and the poc helper deletion list. Co-Authored-By: Claude Fable 5 <[email protected]>
ShaderState.output and FixtureState.output were direction Local — latent wrong metadata that direction-driven default policy (ADR 2026-07-09) would turn into wrong wiring. Marked #[slot(produced)] and added the missing default_policy container attrs; shape dumps regenerated. Two guardrails so it cannot rot again: - shape_guardrails test walks the entire static catalog and asserts every product-carrying field declares a direction (verified it catches the original mislabel). - load-time assertion in the project loader: a binding draft sourcing a produced slot (or targeting a consumed slot) must match the declared shape direction; undeclared/dynamic slots pass through. Co-Authored-By: Claude Fable 5 <[email protected]>
Pins the five loader helpers' observable behavior: minimal-clock time default (seconds only), authored targets/sources suppressing defaults, shader time + visual.out defaults, the shader-without-clock case, the fluid has_default_time_bus gate (flagged inline as deliberately flipping under ADR 2026-07-09), and playlist entry children being ownership-suppressed from default-publishing visual.out. Co-Authored-By: Claude Fable 5 <[email protected]>
…s (M5) Implements ADR 2026-07-09-declarative-default-bindings: - #[slot(default_bind = "bus:…")] on static fields surfaces into the slot shape (new first-class field on SlotFieldShape / StaticSlotFieldShape beside semantics+policy); the derive validates the bus: prefix lexically, the shape-walk guardrails validate the real grammar and require a declared direction. - ShaderSlotDef gains default_bind (typed BindingRef); the create templates and all convention-reliant example shaders declare "default_bind": "bus:time" explicitly — the global "any unbound f32 time" convention is retired. - Declared defaults: clock seconds → bus:time; shader/fluid/playlist outputs → bus:visual.out; fixture output → bus:control.out; NEW fixture.input ← bus:visual.out and output.input ← bus:control.out (D8: the last implicit slots become declared schema; authored inputs now optional — basic nodes wire themselves). - One generic materialization pass replaces the five loader helpers: authored wins, entry-owned children suppress produced defaults, everything registers unconditionally at fallback priority (the fluid has_default_time_bus gate is deleted — an unfilled channel is surfaced on the bus instead of hidden; fluid node tests gain the clock they always semantically required). - origin: default on the wire is priority-derived — M2 contract untouched. Characterization tests updated where the ADR deliberately changed behavior; schemas + shape dumps regenerated. Co-Authored-By: Claude Fable 5 <[email protected]>
…(M5)
Per-slot indicators now tell the truth about defaults: after every
applied view, ProjectController overlays graph-derived origin:default
bindings onto the node controllers through the existing binding-facts
channel. Slots the authored pass left direct/unpublished get a
default-origin endpoint — authored facts always win, so authored
overrides drop the DEF automatically.
UiBindingEndpoint carries default_origin: the BindingChip wears a DEF
badge (tooltip explains), the config-slot popover gains an "Origin:
default binding" row ("declared by the slot (default_bind); authoring a
binding overrides it"), and produced popups append the same detail.
Controller tests cover fill + authored-wins; bound_default story added;
baselines regenerated.
Co-Authored-By: Claude Fable 5 <[email protected]>
…try (M4) - lpc-model gains the well-known channel registry (name, kind, doc for time/trigger/visual.out/control.out) — the D9 norms outcome: channels stay lazily created, the picker teaches the norm. - Bindable rows (root def config slots, produced values/products, and M3 binding-derived rows) carry UiBindingAuthoring: binding key, derived direction (consumed → source, produced → target), the bindings-map address, and the authored endpoint when one exists — default-origin wiring offers Bind, not Retarget, and unbinding an authored entry re-enables the default. - ProjectEditorView carries channel choices (observed ∪ well-known). - Derived rows fixed along the way: default-origin wiring now wears the DEF endpoint flag (closes the idle-shader DEF gap from M5's DoD) and literal-sourced loader plumbing (the output demand constant) no longer leaks a row. Co-Authored-By: Claude Fable 5 <[email protected]>
The slot detail popover gains a Bind section for every bindable row (config slots, produced values/products, binding-derived rows): Bind… opens the channel picker; authored entries offer Retarget…/Unbind. Everything dispatches the existing slot-edit gestures against the node's bindings map (EnsurePresent entry → EnsurePresent endpoint option → SetValue; Unbind = RemoveValue, which re-enables any slot-declared default) — no new wire surface, edits ride the normal overlay/save path. The picker lists observed ∪ well-known channels (kind tags, registry docs, WK markers, kind-mismatch hints against the current channel) and accepts validated free text — channels stay lazily created; the picker teaches the norm (D9). Choices flow from ProjectEditorView through a workspace-provided context so every popover shares one list. Stories: unbound / authored / open-picker states. Co-Authored-By: Claude Fable 5 <[email protected]>
Unbound / authored / open-picker states, plus popover-affected story refreshes. Co-Authored-By: Claude Fable 5 <[email protected]>
Bindings were load-time materializations: the incremental apply's
deliberate `NodeDefChangeKind::Body => {}` no-op is right for
resolver-read values but silently ignored `bindings` map edits, so M4's
bind/unbind/retarget (and any default_bind change) only took effect on
reload — a new channel never appeared on the bus, unbinding never
stopped motion.
Option C (user-approved): the loader's binding registration is split
out of the attach arms into a dedicated binding phase
(register_projected_bindings), and apply_project_changes now ends every
non-empty apply by clearing the binding index and re-running that phase
against current defs. Cheap (dozens of entries), no per-node diffing,
runtime node state untouched, and by construction identical to a fresh
load — pinned by a load-parity assertion.
Covers the selective-reattach path too (reattached nodes previously
re-registered via their arms; now the rebuild does it). Error-projected
defs keep their attach-arm tolerance. Tests: authored binding applies
incrementally + suppresses the declared default + node not recreated +
parity with fresh load; unbind re-materializes the slot-declared
default. Full characterization suite unchanged and green.
Co-Authored-By: Claude Fable 5 <[email protected]>
…t locking Semantic conflict resolutions beyond the textual ones: - studio_controller: main's home-gallery view() with the bus pane inserted (panes = project, bus, device, firmware). - embedded_example: main hardcoded the demo as "Basic"/examples/basic, but this branch's demo is examples/fyeah-sign — gallery entry renamed to "Fyeah Sign" and home tests updated to match. - studio-story-pngs.mjs: main's BeginFrame kick for headless capture kept alongside this branch's 30s CDP call cap. - tailwind.css regenerated with pinned [email protected]; drifted story baselines re-captured. Co-Authored-By: Claude Fable 5 <[email protected]>
…iltins Conflicts were three story-baseline PNGs (both sides re-captured) — resolved by a fresh capture. The auto-merged generated tailwind.css had lost a closing brace; regenerated with pinned [email protected]. Co-Authored-By: Claude Fable 5 <[email protected]>
CI-only breakage (schema_gen is feature-gated, so plain cargo test never compiled it): a lpc-view test initializer missed the new default_bind field, and relative_node_ref_schema still described the old dotted grammar. The pattern now mirrors RelativeNodeRef::parse (optional ./, leading .. hops, slash-separated names), and the conformance cases match the parser's actual accept/reject behavior. Co-Authored-By: Claude Fable 5 <[email protected]>
The build script walked up from OUT_DIR to find the workspace Cargo.toml,
which breaks under a configured `build.build-dir` (cargo ≥1.89): OUT_DIR
then lives outside the workspace entirely and the walk panics
("workspace root"). CARGO_MANIFEST_DIR is always inside the workspace,
so walk up from there instead; the two-levels-up OUT_DIR heuristic goes
away. The embedded lps-builtins-emu-app ELF is a final artifact and is
still read from <workspace>/target/, which build-dir does not relocate.
Co-Authored-By: Claude Fable 5 <[email protected]>
Eight items from Yona's 2026-07-15 gate review (record in Planning/lp2025/2026-07-15-1000-continue-binding-design/): - DEF badge leaves the main-UI chip; the detail popup explains default wiring explicitly (shared Origin row: "Declared by the slot itself; authoring a binding overrides it"). - Incoming bindings mirror the outgoing treatment: `⟷ → chan` publishes, `⟷ ← chan` reads. - New bus glyph (Lucide Waypoints) on binding chips, bus channel card titles (SlotPane title_icon), and picker rows. - "Bound from" → "Reading from" on chips and popup headings. - Bus card values use the shared fixed-decimal display (new value_display.rs) so live readings stop jittering. - The slot popup's binding story is one block: BindingAuthoringSection now folds in the current Binding aspect (headline + origin + routes) above Bind…/Edit…/Unbind. Retarget… is renamed Edit…; default wiring offers Edit-to-override. - Enter submits the free-text channel field. - Produced-value popups drop the live Value row (the pane hero owns the changing reading); popup keeps identity/shape/unit. Also fixes the WK-badge clipping in picker rows (overflow containment). Co-Authored-By: Claude Opus 4.8 <[email protected]>
Adds the new binding-authoring default-bound story captures; the wider churn matches the usual whole-set regen behavior. Co-Authored-By: Claude Opus 4.8 <[email protected]>
… editor UX Brings in PR #98 (fw-browser: enable naga feature so the standalone sidecar compiles shaders — fixes every non-clock node erroring in studio-dev), PR #95 (DeviceSession refactor), PR #78 (GLSL editor UX), and the interp.f32 filetest work. Conflicts: import unions in lpa-studio-core lib.rs/studio_controller.rs (main dropped the old link types; bus-pane exports kept, UiPaneView re-added for bus_pane()); lpvm-cranelift build.rs (same fix on both sides — kept the commented HEAD copy); story-baseline PNGs taken from main and re-captured after the merge. The auto-merged generated tailwind.css was discarded and regenerated with pinned [email protected] (braces verified balanced). Co-Authored-By: Claude Opus 4.8 <[email protected]>
Every non-clock node erroring with no visible cause (gate follow-up): the runtime always carried the message (NodeRuntimeStatus::Error/ InitError/Warn(String)) and node_status_view kept it, but ui_node_view dropped the detail when building UiNodeHeader — the popover already rendered header.detail and the tree-row tooltip already appended it. Populate header.detail from the status detail so the popup answers WHY. Adds the error-detail-popup story (open popup on an erroring shader) and a controller test pinning the pipeline. Also carries the post-merge UiPaneView import fix and the tailwind class the new story needs. Co-Authored-By: Claude Opus 4.8 <[email protected]>
…l story Includes the new error-detail-popup captures and refreshes the panes the main merge and error-detail work touched. Co-Authored-By: Claude Opus 4.8 <[email protected]>
Clicking anything on home could kill the whole app with "keyed siblings
must each have a unique key": device cards were keyed by NAME, and names
repeat — erasing and re-provisioning a board registers a new dev_… uid
under the same name. Dioxus only diffs on re-render, so the gallery
mounted fine and died on the first interaction, which read as "delete
does nothing / example cards do nothing".
- Key device cards by uid (render_key: uid, falling back to name for
the single identity-less live card).
- Defensively dedupe device/project cards by render key at view build
(warn + drop) so a corrupt registry or store degrades to a missing
card, never a dead UI.
Verified in the sim: remix create → gallery → menu → Delete works
("Project deleted", card gone).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
…guarded Co-Authored-By: Claude Opus 4.8 <[email protected]>
Yona-Appletree
marked this pull request as ready for review
July 16, 2026 07:18
…gate Brings in PR #92 (shader auto-apply + split-bar editor chrome), PR #102 (popover chrome as one SVG merged-outline path), PR #101 (auto-apply ADR), PR #100 (path-gated wgpu CI job), PR #99 (interp.f32 in DEFAULT_TARGETS). Conflicts were story-baseline PNGs only (both sides re-captured) — taken from main and re-captured from the merged tree in the follow-up commit. The auto-merged generated tailwind.css was discarded and regenerated with pinned [email protected] (braces verified balanced). Co-Authored-By: Claude Opus 4.8 <[email protected]>
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Brings in PR #105 (GLSL editor autocomplete — generated builtin manifest + uniforms) and PR #97 (device-link M5: native espflash-lib manage(), unified cli_connect, fwcheck/calibrate on shared abstractions). Conflicts: lpa-studio-core lib.rs export list — a pure union (this branch's binding-authoring types + main's UiShaderUniform); one story baseline PNG taken from main and re-captured in the follow-up commit. Co-Authored-By: Claude Opus 4.8 <[email protected]>
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Brings in PR #103 (weld popover panel to the trigger's visible edge) and PR #104 (deployed projects survive reboot + host disconnect). Conflicts were story-baseline PNGs only (main's popover change re-captured them) — taken from main and re-captured from the merged tree in the follow-up commit. Co-Authored-By: Claude Opus 4.8 <[email protected]>
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Yona-Appletree
added a commit
that referenced
this pull request
Jul 16, 2026
…rapper The inner fn owns them now; the path-gated clippy-gfx job (-D warnings) rejected the leftovers. Also merges origin/main (PR #86 landed). Co-Authored-By: Claude Opus 4.8 <[email protected]>
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.
Executes the 2026-07-06 studio-binding-bus roadmap M0–M5 + M4 plus the D9 naming/syntax outcome. Every milestone is validated (model/engine/studio suites, fw emu render, schema drift, story baselines); M5's visual gate passed on the live fyeah-sign demo and M4's flows were driven end-to-end in the simulator.
Highlights
trigger_idsper entry); "bindings live at node-def roots" is now a model-wide norm.status-boundfamily (bound ≠ green).origin: authored|default(ADR 2026-07-06-binding-graph-probe).bus:<channel>+node:<path>#<slot.path>; slashful node paths; channels unitless (time, wastime.seconds). Hard cutover, one atomic commit.#[slot(default_bind = "bus:…")]/ShaderSlotDef.default_bind); the five loader helpers are deleted; defaults register unconditionally (unfilled channels surface on the bus);fixture.input/output.inputbecome declared consumed slots with defaults — a blank project with basic nodes wires itself. Node cards show DEF-badged wiring with popover origin explanations, backed by catalog-wide direction guardrails.bindingsmap (no new wire surface). The channel picker lists observed ∪ well-known channels (new registry inlpc-model) with kind tags, registry docs, kind-mismatch hints, and validated free text. Unbinding re-enables slot-declared defaults.Known follow-ups (recorded in the roadmap)
visual.out/2instance proposals;transport.*channels — all M4-adjacent, syntax-ready.Busstruct, docs) remain.🤖 Generated with Claude Code