Phase J (2026-08-03) bumped lib-core.composition 0.3.0->0.4.0 and
lib-core.crafting 0.3.0->0.5.0, but only vagrant-skeleton and
lib-core.crafting themselves were updated. Dependent manifests kept the
old pins, so dep-fetcher saw disagreeing pins on the same lib and
refused to start both spine-prototype (ENGINE_ERR_LIB_DEP_MISSING,
rc=19) and vagrant-skeleton ("dep check failed", conflict on
lib-core.composition + lib-core.crafting).
Unify all pins on the on-disk versions. No code changes required:
composition 0.4.0 is purely additive (the quality/condition loud-error
was removed; no signature changed), and crafting-display only consumes
crafting.can_craft + crafting.list_recipes, both still present, and
never referenced the `form` field retired by ADR-0055.
README "Requires:" lines synced to match the manifests (Gate 3).
milestone-check.sh: GREEN (build, ctest, smoke, devwrap-ci,
test-all-modules + anomaly scan). vagrant-skeleton headless run clean
at 60 CI frames.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Recipe rows gain an icon column on the left (row_height-4 box,
fit-to-bounds, centered). The renderer calls widget._icon_resolver(
recipe) and renders the returned {atlas, uv} via
draw_sprite_transform when non-nil. Default resolver returns nil →
text-only layout preserved for modules that don't wire icons; modules
opt in by passing opts.icon_resolver to create() or via
set_icon_resolver after.
Module-side mapping (recipe.output.template → {atlas, uv}) is the
caller's responsibility — composition has no template-property
getter today (ADR-0001 keeps the read-side domain-free), so the
domain owner is the right place to hold the lookup.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Engine resolver does exact strcmp on dep versions, so consumer manifests
must pin the new lib-core.panel 0.4.0 to keep load-time resolution green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Track lib-core.panel v0.2.0 (multi-active panels with layout-slots).
No behavioral change in this consumer — the v0.1.1 call sites remain
compatible via the panel framework's bw-compat shim.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
M.create now accepts either a function (locale_factory, called per
frame to resolve the current crafting locale) or a bare entity-handle
(wrapped internally as a constant factory for v0.1 bw-compat). The
factory's return value is forwarded to crafting.can_craft as the new
multi-source / single-sink locale, enabling dynamic topologies
(adjacent-container sets that change as the actor moves) without
recreating the widget.
Action callbacks now receive ctx_inner with both `locale` (the
currently-resolved locale, either the Form-2 table or the Form-1 bare
handle) and `container` as a bw-compat alias pointing at locale.sink
(Form-2) or the handle itself (Form-1). Existing v0.1 callers using
ctx.container keep working unchanged.
New backdoor _test_get_locale exposes the locale_factory's current
return value. README documents both Form-1 and Form-2 with examples
plus the updated callback contract.
Depends on lib-core.crafting v0.2.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
UI layer for recipe-based crafting. Reads recipes from
lib-core.crafting, renders the known-subset as a vertical row-list
inside a panel managed by lib-core.panel, and dispatches right-click
context-menu actions registered by the consuming module.
Surface:
create(container_entity, opts) -> widget_def
register_action(widget, label, callback)
unregister_action(widget, label)
set_icon_resolver(widget, fn)
set_label_resolver(widget, fn)
set_summary_resolver(widget, fn)
Row-build is rebuilt per render frame from crafting.list_recipes():
- Visibility gate: recipe.is_known(ctx); errors hide row + emit [WARN]
- Availability gate: crafting.can_craft(id, container, ctx).ok
drives row colour (full vs dim text_color).
ctx comes from opts.ctx_factory() — default returns {}; override to
feed actor/skill/faction state into both predicates. label_resolver
defaults to recipe.name or recipe.id; summary_resolver joins input
template-ids with " + " and suffixes "xN" for count>1.
Right-click hit-tests row-rects captured during render, then opens
the menu via panel.show_context_menu(x, y, actions). Action
callbacks receive (recipe_id, {container, close_menu, refresh}).
Registration order is preserved (array-based, not pairs-based) so
context-menu rendering matches registration sequence.
Depends on lib-core.crafting 0.1.0, lib-core.panel 0.1.1,
lib-core.inventory-list 0.1.0, lib-core.composition 0.3.0. The last
two are not called directly but are required as direct deps so
modules consuming this widget satisfy crafting's transitive needs
(engine resolver is per-module non-transitive).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>