Every phase below ends at a logically atomic commit point and closes
with a review gate: run the phase verification (unit tests and/or
netdebug + trace logs + visual inspection), then a thorough code review
by the owner with possible amendments or clarifications, then commit.
The next phase does not start before the gate passes. Tooling: unit
tests (cargo test in bin/app), netdebug zeromq CLI (CallMethod,
GetPropertyValue, signal subscriptions), trace logs (ui::chatview2*),
and visual inspection on the schema-chatview dev screen. See
design.md → Development Protocol.
src/util/fenwick.rs (new, get, add, set, push,
prefix, range, lower_bound, rebuild) with randomized unit tests
comparing every operation against brute-force sums over a reference
Vec<f32>; verify cargo test in bin/app passessrc/ui/chatview2/buffer.rs record store: slotmap
arena, order index sorted by (timestamp, msg_id), dedup set, removal
by id; verify unit tests pass: insert at any position, duplicate
insert ignored, same-millisecond coexistence, removal, ordered
iterationtotal_height,
visible_range(scroll, view_h), pos_of(msg_id), set_height
point-updates, insert_batch single-rebuild, plus the
below-viewport compensation math helper; verify randomized unit tests
pass comparing geometry against a linear scan and compensation
below/inside/above viewport and at scroll==0[u8 tag][type bytes],
privmsg payload = nick, text encoding + confirmed flag): a fixed
#[repr(u8)] MsgType enum whose discriminants are the wire tags
(encode via as u8, decode via a from_u8 match, no factories);
verify unit tests pass for encode/decode round-trip and that an
unknown tag or undecodable payload panics with an identifying
message (corrupt data is never silently skipped)src/ui/chatview2/scroll.rs: internal
pixels-from-bottom scroll (no scene property), Idle/Drag/Glide/Anim
state machine with intents fed from the gesture subsystem's drag
lifecycle (drag start/move/end; flick = threshold on the session's
DragEnd velocity — no local sampling; page tick,
scroll_to_bottom), clamping, is_at_bottom indication,
height-change compensation application, anchor snapshot/resolve with
bottom shortcut and clamped fallback; verify unit tests pass for
state transitions (grab cancels motion, wheel coalescing extends the
target, flick fed a DragEnd-style velocity decays to stop, clamps
at 0 and top, scroll_to_bottom, anchor round-trip with inserts above
and below)src/ui/chatview2/ module skeleton and the
ChatView2 UIObject: properties (rect, shared styling, is_at_bottom
bool), view-wide method stubs (set_channel, set_filter,
copy_select, unselect, scroll_to_bottom, get_line_ids,
delete_line), the gesture contract (gesture_set chatview-shaped:
tap + long-press + vertical drag; exact-rect gesture_hit_test;
handle_gesture stub), empty-buffer draw running the visible-window
loop, and the node factory; verify make compile-dev succeeds and
the dev screen renders an empty viewsrc/app/schema/test_chatview.rs dev schema
(modeled on schema/test.rs) hosting the chatview2 node for
development, gated behind a new schema-test-chatview cargo feature
following the existing schema-test-* convention (mutually exclusive
with schema-app via the one-schema compile guard); verify by
running the app with the feature enabled: screen shows, and a
netdebug scene dump lists the chatview2 node with its properties and
methodssrc/ui/chatview2/loader.rs: single kvdb-owning
bg task, coverage invariant (live bottom + viewport + preload
margin), wake-ups (set_channel, near-top scroll, insert, filter, rect
change), filter application at load, plus working get_line_ids and
delete_line methods on the chatview; verify via netdebug on a
fixture tree: set_channel then get_line_ids returns the expected
(ts, id) list in display order, delete_line removes a record and it
stays gone after re-set_channel; trace logs show load batches and
Fenwick rebuilds as designedsrc/ui/chatview2/msg/mod.rs: MessageType
trait (materialize/release/regen/height/draw/hit_test/copy_text) and
the hardcoded MsgType enum dispatch (no factories, no placeholder —
unknown type ids panic at decode), height reporting into the buffer;
verify unit tests pass for materialize→release→materialize state
rebuild, height report, and cache lifecycle (state cached across
draws, invalidated by width/styling/data changes); visually confirm
records render on the dev screen and trace logs show
materialize/release as the window movesmsg/privmsg.rs part 1: the type's
insert_line/insert_unconf_line/confirm methods (persist via the
loader, dedup, buffer insert, confirm rewrites the payload in place
and regens, materialize if visible) and basic rendering (nick colors,
timestamp, plain text layout at the wrapped width); verify unit tests
pass for layout cache behavior (layout reused across repeated
measures, re-wrapped on width change, invalidated on styling/data
change) and the unconfirmed→confirm in-place update; verify via
netdebug CallMethod insert_line feeding batches of lines and
confirm on one: trace logs show dedup, fenwick pushes, layout cache
hit/miss behavior; visual inspection against the old chatview for
basic lines and unconfirmed→confirmed restylingnick_clicked signal carrying msg id + nick, CTCP ACTION rendering,
NOTICE styling, unconfirmed gray; verify netdebug-driven inserts of
urls/actions/notices render correctly (visual), the nick_clicked
signal is observable on the netdebug pub socket, and toast behavior
matches the old chatviewTap toggle, long-press entering selection mode with
subsequent DragMove extending the selection instead of scrolling,
drag sweep, selection-mode taps, per-type copy_text joined in
display order, unselect, select_changed transitions; verify unit
test for mixed-type copy ordering passes, netdebug
copy_select/unselect behave per spec, and manual
drag/toggle/long-press selection works visually on the dev screenmsg/datemsg.rs and derived records: synthetic
(midnight, [0;32]) keys, sync_separators on insert/load/delete,
orphan cleanup, selectable with date-label copy text; verify unit
tests pass for separator sync and orphan removal, netdebug
delete_line of a day's only message removes its separator, and
visual day-boundary rendering matches the old chatviewhandle_gesture
mapping to the scroll controller per design's gesture integration
table (Down pauses inertia and resets long-press mode, DragStart
grabs (kills motion), DragMove 1:1 on the chat axis (scroll =
scroll0 + dy), DragEnd flick on the session velocity, Up clears
touch-active state), plus handle_mouse_wheel and PageUp/PageDown
keys feeding page_tick (animated half-page with coalescing),
clamps, a visible scroll-to-bottom arrow layer (priority +1 above
the view) driven by is_at_bottom calling scroll_to_bottom, and
the animator deadline-cadence task; verify the slop dead-zone, 20ms
move cadence, and single-fire long-press come from the session (no
local recognition), trace logs show the intended state transitions
per gesture, netdebug GetPropertyValue is_at_bottom flips as
expected, the overlaid arrow receives its taps (arbitration), and
visual inspection confirms pixel-exact drag, smooth wheel animation,
correct stops at both clamps, and the arrow toggling with positionSetPropertyValue on font_size and by resizing the window:
trace logs show visible-first regen order and one rebuild, visual
inspection confirms the anchored message stays put and bottom stays
pinnedmsg/filemsg.rs: set_file_status method, status
lifecycle rendering, fud URL derivation, download_request/
fileurl_detected/status_changed signals, downloaded image display
with fit bounds, and the content-scoped key → Task map surviving
eviction; wire i18n_fish into type constructors and translate file
status strings; verify a fud round-trip with the plugin enabled
(visual image + statuses), trace logs show task dedup/attach on
re-materialization, eviction mid-download continues the task, and a
language switch translates the status textsrc/app/schema/chat.rs to a single chat screen
with one chatview2: set_channel on channel selection, channel
label binding, scroll-to-bottom arrow driven by is_at_bottom +
scroll_to_bottom; remove the per-channel screen loop in
schema/mod.rs; retarget relay paths in main.rs and plugins
(darkirc insert/confirm to the privmsg node, fud status fan-out to
the filemsg node); set explicit node priorities for every interactive
layer floating over the chatview (scroll-to-bottom arrow, cmd-hint
popup) above it, per the gesture session's priority-ordered
targeting; verify on desktop with darkirc: receiving messages
updates the active channel, switching channels clears/reloads and
restores each channel's position, and unread indication works via
signalssrc/ui/chatview/ and all old-chatview references
(ui/mod.rs re-exports, test schemas using create_chatview); verify
make compile-dev and make compile-apk both succeedspecs/chatview/spec.md
(URLs, selection/copy incl. separators, file messages, actions/notices,
unconfirmed, date separators, keys, touch incl. slop dead-zone,
grab-to-stop, long-press select mode, wheel, restore, reflow) on
desktop; performance pass scrolling a large history (no slowdown,
bounded memory via renderer debug stats); android device smoke test
(drag, flick, grab-stop, long-press select/copy, channel switching)