Lands the codex-worktree D.2b stream plus the extraction the 2026-07-02
UI architecture review mandated before commit:
- ItemInteractionController: single owner of double-click use/equip/
container-open, targeted-use mode (health kits), drag-out drop;
toolbar shortcut drags don't drop the real item. ItemEquipRules for
multi-slot (coat) coverage via equip masks.
- Cursor phase: CursorFeedbackController (semantic priority chain:
drag > resize > window-move > target-mode > text) + RetailCursorCatalog
(enums 0x27/0x28/0x29, hotspot 14,14; ClientUISystem::UpdateCursorState
0x00564630) resolved through the portal EnumIDMap chain by
RetailCursorResolver; RetailCursorManager applies dat cursor art to the
OS cursor. Register row AP-72 covers the OS standard-cursor fallback.
- Character window goes live: CharacterSheetProvider owns sheet assembly,
XP-curve/raise-cost math and the raise flow — extracted out of
GameWindow per Code Structure Rule 1 instead of committing the ~430-line
feature body there. Optimistic XP/credit debits go through eventful
store APIs (new ClientObjectTable.UpdateInt64Property +
LocalPlayerState.DebitIntProperty/DebitInt64Property) instead of raw
property-dictionary writes; register row AP-73 covers the still-missing
raise ledger (#163).
- RetailWindowFrame: the shared nine-slice window mount recipe; the
character window uses it, remaining windows migrate via #164.
- Status-bar buttons toggle inventory/character windows; retail row-major
backpack ordering; WorldSession.SendUseWithTarget + raise/train sends.
GameWindow shrinks 14,214 -> 13,877 lines despite the new features; the
sheet/raise logic is unit-tested in CharacterSheetProviderTests instead
of trapped in the god object. Build green; full suite 3,286 tests pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Rename UiChatInput → UiField (UIElement_Field, RegisterElementClass(3) @ :126190);
update doc to cite retail's CatchDroppedItem/MouseOverTop drag-drop hooks for
future item windows. BackgroundColor default → transparent (controller sets
the translucent 0.35α value explicitly, matching UiText pattern).
- Register Type 3 in DatWidgetFactory.Create: `3 => new UiField()`.
- ChatWindowController.Bind (Variant B): factory now builds 0x10000016 as an
invisible UiText placeholder (Type 12); Bind removes that placeholder via
FindElement(InputId).Parent.RemoveChild and places a UiField at the same rect.
Result: exactly ONE input widget in the input bar, no stray UiText duplicate.
- Input property type changed from UiChatInput to UiField; GameWindow.cs:1861
UiField.Keyboard assignment compiles unchanged (field exists).
- Tests: UiChatInputTests → UiFieldTests (class + all ctor refs renamed);
DatWidgetFactoryTests: new Type3_Field_MakesUiField test; ChatWindowControllerTests:
updated stale "skipped by factory" comments; LayoutConformanceTests: updated
VitalsTree_ChromeCornerHasExpectedSprite — Type-3 chrome-corner elements are
now UiField (sprite rendering for Type-3 dat image elements is a known
limitation, tracked for post-Task-8 UiField.BackgroundSprite follow-up).
- Full suite: 404 passed, 2 skipped, 0 failed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
UiChannelMenu → UiMenu: removed ChatChannelKind, the 14-item array, the
button-text map, and the availability default. Generic surface: MenuItem
(label + object? Payload), Selected (object?), OnSelect, EnabledProvider,
ButtonLabelProvider, RowsPerColumn/RowHeight/ColumnWidth (all settable).
All draw/event mechanics unchanged — same popup geometry, same click
coordinates, same 8-piece bevel, same 3-slice button face.
ChatWindowController gains ChannelItems[], ChannelButtonLabel(), and
ChannelAvailable() (verbatim from old widget), and populates the
factory-built Type-6 UiMenu via find-by-id rather than constructing a
replacement widget. The Menu property type is now UiMenu. OnChannelChanged
wrap replaced with the generic OnSelect wrap for the ReflowInputRow hook.
DatWidgetFactory registers Type 6 → new UiMenu().
Tests: UiChannelMenuTests → UiMenuTests (10 tests, all green); factory
Type6 test added; ChatWindowControllerTests updated to use OnSelect.
Divergence register: AP-42 added (flat item model vs retail nested-submenu
MakePopup @0x46d310 — latent, unreachable through the chat menu).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The live session + its LiveCommandBus are created after the retail-UI block in
OnLoad, so binding the bus by value captured NullCommandBus and silently dropped
outbound chat. Pass a Func<ICommandBus> resolved at submit time (mirrors how the
ImGui ChatPanel re-reads the bus each frame).
AP-41: scrollbar thumb drawn as single stretched tile (0x06004C63) instead of
retail's 3-slice top-cap/middle/bottom-cap — acknowledged in UiChatScrollbar.cs:37,
registered per the divergence-register rule.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implements Task G2: binds the imported chat LayoutDesc (0x21000006) to live
behavior, the acdream analogue of retail ChatInterface + gmMainChatUI::PostInit.
- UiDatElement: add OnClick hook + OnEvent override so Send/max-min buttons
can be wired by a controller without needing a dedicated widget type.
- ChatWindowController.Bind: reads transcript (0x10000011) and input
(0x10000016) rects from the raw ElementInfo tree (factory skips them as
Type-12/no-media), places UiChatView under the transcript panel and
UiChatInput under the input bar; replaces the imported scrollbar track
(0x10000012) with UiChatScrollbar driving UiChatView.Scroll; replaces
the channel menu placeholder (0x10000014) with UiChannelMenu; wires
Send button and max/min toggle via the new OnClick hook.
ChatCommandRouter.Submit routes all input through the existing pipeline.
- 6 smoke tests: Bind returns non-null, Transcript is child of panel,
Input is child of bar, Input.OnSubmit publishes SendChatCmd, channel
change updates submit channel, returns null when panels missing.
Build: 0 errors. Test suite: 392 passed / 1 skipped / 0 failed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>