Repository navigation
refactor(viz): byte-memory model + hover highlight + ByteMap overlay - #5
Merged
Merged
Conversation
- Replace manual click-outside/clipboard/timeout handlers with VueUse composables in index.vue - Drop unused ghost-fade logic in DataStructureView, unused SvgArrow component - Use formatAddr helper in AddressLink/FieldTable for consistent hex formatting - Replace queue.shift() with index walk in usePointerGraph BFS - Add avl-rotations and doubly-reverse templates Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ocation registry Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lization Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a struct definition is registered in the interpreter, immediately compute and cache its layout via computeStructLayout, store it in context.structLayouts, and call mem.registerStructLayout to wire the memory manager. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Translate all old-API call sites (mem.read/write/readField, structFieldOffset, space.cells) to the new byte-addressed MemoryManager API. Also fixes memory.ts to correctly handle pointer alloc init, inline arrays/structs within allocations, multi-dimensional arrays, and stride-aware pointer arithmetic. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add two reactive Vue composables as part of the byte-addressed memory refactor (Task 12): useAllocationQuery wraps MemoryManager allocation lookups with version-reactive computed properties, and useStructLayout resolves struct names to typed field rows and byte sizes via InterpreterContext. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Change InterpreterContext.memory from AddressSpace to MemoryManager so components can call findAllocation(), readScalar(), space.version, etc. - Update interpreter/index.ts to store markRaw(mem) and drop the spread-copy reactivity trick (version bump on mem.space is sufficient) - Port usePointerGraph to use space.allocations + layout tree for edges - Port useStatementAddresses to use findAllocation/readScalar/elementAddress - Rewrite DSValue.vue: prop changed from cell:MemoryCell to address+type - Rewrite FieldTable.vue: same prop change; uses useMemoryDecoder for display - Rewrite DataStructureView.vue: builds standaloneItems from allocations - Stub MemoryMap.vue (Task 16 replacement) and useMemoryDiff.ts (Task 14) - Fix MemoryCell.vue: local MemoryCell interface replaces deleted type - Update test fixtures: memory: mem (not mem.space), use space.allocations Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the no-op useMemoryDiff stub with a real implementation that snapshots AddressSpace before each step and computes byte-range diffs and allocation deltas (newlyAllocated/newlyFreed) reactively via a computed ref. index.vue wires in snapshotSpace + previousSnapshot and derives the changedAddresses set consumed by MemoryMap and FieldTable. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace Task 13 stub with three-component MemoryMap decomposition: MemoryMapRow (per-row byte grid with address label), MemoryMapTooltip (hover/pin detail pane), and MemoryMap outer grid (gap-aware row layout, pinned/hover address tracking). Update index.vue call sites to pass `mem: MemoryManager` + `changedBytes` instead of the old address-set props. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Restore the original MemoryMap as AllocationMap.vue (ported to the new byte-addressed API) and make it the default. The byte-level view from the refactor lives at ByteMap.vue and is opt-in via a toolbar toggle persisted in localStorage. MemoryMap.vue is now a thin dispatcher. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… dark-mode aware Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ring-inset on changed-byte amber ring so it no longer bleeds past the cell boundary at the top scroll row - useElementSize on each column body to compute bytesPerRow reactively; rows reshuffle cleanly on resize (minimum 4 cells) - ByteMap emits select-cell upward through MemoryMap (selectByteCell) and into index.vue, which resolves the allocation base and drives the existing FieldTable detail pane; free-byte clicks clear the selection - FieldTable gains optional byteDetail prop rendered as a banner above the allocation fields (address, offset, path, type, raw byte) - Delete ByteMapDetail.vue (superseded by the unified panel) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…l bytes Add `memoryVersion` counter to InterpreterContext and bump it in init()/step() so Vue components no longer depend on markRaw'd mem.space.version. Rewrite ByteMap to render the full 16KB buffer via useVirtualList (28px row height), splitting at MEMORY_SIZE/2 with heap pre-scrolled to the bottom. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…onMap borders Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Restore missing `flex` utility so entries get proper gaps (gap-1.5) - Give entry cards an elevated look: white/gray-900 bg, subtle border, shadow-sm - Distinguish card header strip from body with a darker tint and divider - Center "No stack variables" / "No heap allocations" / "No data to display" in their respective panels Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ing allocation
Previously getFieldValues called findAllocation(fieldAddr) which returns the
ENCLOSING struct allocation — so every field of a struct was treated as a struct
itself, and primitive/pointer fields rendered as "ListNode {...}" instead of
their actual values. Now each field is read using its own LayoutNode from the
struct layout tree, producing int scalars, AddressLinks for pointers, etc.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Byte cells now colour by leafType (from describeByte): int=blue, float=amber, char=purple, bool=rose, pointer=green, struct=cyan, array=orange — matching DataStructureView's kindColors. Continuous bytes of the same allocation are no longer an indistinguishable blue/green blob; each field inside a struct gets the colour of its own type. - Byte-detail banner in FieldTable now shows whenever a byte was clicked in the byte map (previously hidden at offset 0 / allocation base). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MemoryMapByteCell reserved 2px of transparent border on the left of every cell so that a boundary-marker border wouldn't shift layout. In dark mode the transparent slice showed the column background through each cell as a visible vertical separator, breaking the continuous colored blocks within an allocation; in light mode the stripes blended in, so the two modes looked different. Because every cell is w-8 (box-sizing: border-box), removing the reserved border doesn't change layout — boundary cells still fit their 2px colored border within the same 32px width. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…p empty-on-resize - Wrap stack & heap columns in Splitpanes so the user can drag the divider to rebalance bytes-per-row between them. - Re-scroll heap to its last row on every bytes-per-row change (not just the first measurement). Previously the scrollTop stayed stuck beyond the new total-content height after a splitpane drag or editor/viz resize, leaving the heap column rendering zero rows. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a LE/BE toggle button in the MemoryMap toolbar (visible only in Bytes mode).
Clicking it:
- byte-swaps every scalar in every allocation (live, dead, and the NULL
sentinel) using each allocation's layout tree, so padding and char bytes
are naturally skipped
- flips the MemoryManager's internal \`littleEndian\` flag so subsequent
reads/writes use the new order
- bumps memoryVersion to re-render all memory-bound views
Values remain semantically consistent across the toggle — \`int x = 42\`
still evaluates \`x == 42\` truthy, but its bytes are now laid out differently
in the byte view. The change is exposed to the UI via a new useSetEndianness()
injection, since the context itself is readonly()-wrapped.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The AllocationMap's TransitionGroup leave rule uses \`position: absolute; width: 100%\` to pull the exiting element out of flow. Without a positioned ancestor on the column's scroll body, the absolute element fell back to the Splitpanes-wrapped viz panel — so leaving items briefly stretched across both stack and heap columns before fading out. Adding \`relative\` to the scroll body scopes the absolute position to the column, so leaving items stay within their column's bounds. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…olumn When a user hovers an AddressLink inside AllocationMap, the handler emits hoverPointer → index.vue updates hoveredNodeAddress → AllocationMap receives it back as the highlightedAddress prop. Both watches (the local hoveredTarget one and the prop-driven one) end up firing. The hoveredTarget watch had a same-column guard since the pre-refactor days, but the prop-driven watch didn't — so the cell would scroll under the cursor and the link would run away from the pointer. Regression source: Task 13's port split the original single-function scroll logic in MemoryMap.vue into two watches, preserving the guard on one of them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…posable Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… CSS cleanup - ADDRESS_LABEL_WIDTH 62→56 px so overlay rects align with byte cells - ByteMapOverlay uses context.memoryVersion for reactivity (mem is markRaw, so props.mem.space.version didn't track; overlay stayed empty on initial Bytes mount until a view toggle re-ran the computed) - refThrottled on column widths (60ms) so splitpane drag doesn't cascade into per-frame row/overlay re-renders - Replace mm-* CSS and scoped style blocks with UnoCSS atomics; add scrollbar-hidden shortcut to uno.config.ts Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t clipped SVG strokes paint centered on the rect edge. A rect at y=0 with a 2px stroke paints from y=-1 to y=1; the scroll container clips y<0 and the top half of the stroke disappears for allocations on the topmost row. Insetting the rect by strokeWidth/2 on all sides keeps the full stroke inside the drawable region without changing the visual size of the highlight. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When an allocation spans multiple rows, rendering each rect individually produces internal vertical/horizontal borders at the row boundaries that look disconnected. Replace the per-rect rendering with a single SVG <path> that traces the allocation's perimeter, so a multi-row highlight reads as one continuous shape. - allocationPath() in overlayGeometry.ts computes the perimeter d-string. Handles three cases: single-row rect, head+tail (connected staircase when tail_right > head_left, disjoint sub-paths otherwise), and head+middle+tail (always connected staircase). - Inset is applied during path construction so the centered stroke fits inside the scroll container's clip region. - Boost halo is now a wider stroke on the same path (drawn under the base stroke) instead of a separate ring of rects. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When bytesPerRow changes (splitpane drag), row addresses shift — row 1 moves from addr 10 to addr 9 if bpr goes 10→9. Keying MemoryMapByteRow by item.data (the row-start address) meant every visible row got a new key on every bpr tick, forcing Vue to unmount+re-mount ~30 rows and their ~300 child cells per tick. Keying by item.index keeps the same row components alive and just updates their row-start prop, so each drag tick is a prop diff instead of a mount cycle. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Listen for Splitpanes @resize / @Resized to freeze column widths during active drag. Mid-drag, splitpanes still resize the pane boundaries via CSS, but bytesPerRow stays constant so the virtual list, rows, and overlay don't recompute on every pointer move. On release we snap to the final widths and recompute once. Trade-off: the byte grid doesn't reflow live during drag. In practice it only snaps once at the end, which reads as a decisive layout change rather than a stuttery live update. Window-resize and other non-drag size changes still flow through via the 60ms throttled watcher. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
✅ Deploy Preview for viz-list ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
The beforeEach previously slept 500ms after btn-step became visible, hoping that was enough for tree-sitter WASM to finish loading. On CI's cold start it sometimes wasn't, so the first test would click Step before handleStep() could init (tree was still undefined), memoryVersion stayed at 0, and the toPass loop timed out. Expose `data-ready="!!tree"` on the Step button and wait on it in beforeEach. Replaces an arbitrary timeout with a deterministic signal tied to the actual readiness condition. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous slider topped out at 500ms which still felt too fast for users who wanted to follow a stepping simulation. Widen the range so the slow end is actually slow: 50ms (fastest) to 1500ms (slowest), step 50, 30 discrete positions. Label tiers updated to fastest / fast / med / slow / slowest. Default stays at 200ms so existing preferences aren't disrupted. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The content extractor was scanning plain code tokens and greedily matching `i--` in `for (…; i--)` loops against the `i-` icon prefix, tripping the presetIcons loader for every occurrence. Blocklist any all-dash suffix so decrement operators stop emitting "[unocss] failed to load icon -" warnings on every dev boot without disabling warnings globally. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Callers that need to identify the scalar containing a given byte (arrow anchors, byte-level pointer inspection) had to walk the layout themselves. Return the walk's accumulated offset + size so describeByte is a sufficient one-stop query. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduce a draggable/bezier pointer-arrow overlay above the memory map, usable in both Allocations and Bytes views. Three layered sources: 1. Hover a pointer (byte cell or AddressLink) → primary arrow to target. 2. Clicking a pointer pins its arrow via selectedAddress / selectedByteAddress; the arrow dismisses on view toggle without closing the detail panel (pinnedGateOpen). 3. A "arrows" toolbar toggle enumerates every live pointer in the arena as dimmer ambient arrows. Key pieces: - ArrowOverlay.vue: single SVG overlay sibling to the KeepAlive'd view. getBoundingClientRect isn't a Vue reactive dep, so a MutationObserver on the host plus bubbling transition/animation event listeners (+ rAF loop while any is active) keep endpoints tracking KeepAlive swaps and CSS-driven card transitions. Endpoints use shortest-segment geometry otherwise; anchors can opt into bottom-edge (AddressLink underline) or left-edge (allocation cards) via data-anchor-edge. - useHoverHighlight: extended with pointerArrow, pinnedArrows, ambientArrows, and a computed activeArrowSources Set for AddressLink's underline state. Arrow reads are guarded with try/catch + dead-alloc checks so a freed selection can't throw out of a computed and leave the store latched. - Color plumbing (highlightColors): new pointer-source (violet) and pointer-target (amber) highlight kinds for the two ends of an active arrow; ARROW_STYLES mirrors those so arrow line color matches the anchor's rectangle tint. Source/target tinting also applies to Allocations view cards. - Supporting UX polish bundled in: Allocations tab renamed (plural); toolbar buttons now share a transparent-border box so the LE/BE toggle doesn't shift toolbar height; AllocationMap uses Splitpanes so its gutter matches ByteMap; MemoryMap wraps its view swap in KeepAlive so ByteMap's measured widths and scroll state survive a toggle; ByteMap v-show-gates its rows on a non-zero measured width to avoid an initial 4-bpr flash; heap column re-snaps to bottom on Bytes activation; scroll- to-pointer-target in ByteMap mirrors AllocationMap's hover scroll. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The test deterministically reproduces a specific KeepAlive-swap bug where ArrowOverlay's computed held stale getBoundingClientRect coords across a view toggle. Now fixed via a MutationObserver. Keeping the spec as a searchable record of what the MutationObserver guards, but skipped so the e2e suite doesn't grow on every debugging session. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The perpendicular displacement used signed components, so arrow direction dictated which side of the segment the control point landed on — a rightward arrow bowed up while its return trip bowed down, which read as visual noise in the ambient show-all-arrows view. Force the control into the upper-left quadrant of the midpoint so every curve arches up and left, regardless of source/target geometry. Anti-parallel pairs now stay separated while sharing a single consistent arch direction. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Browser overscroll (the macOS/touchpad rubber-band bounce) transiently offsets a scroll container's content during the rebound, but the arrow overlay reads endpoints straight from getBoundingClientRect without any bounce-aware correction — the rendered paths drift by a few pixels until the scroll settles. Setting overscroll-behavior: none on the scrollable containers (and the root) suppresses the bounce outright. No visual drift during overscroll attempts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
Summary
Two large refactors consolidated on one branch:
87436f2..8441222): interpreter storage moves from cell-indexed slots to aUint8Array+DataViewbacked arena with natural alignment, struct padding, and a sparse allocation registry. Adds anallocationvsbytesview toggle, live endianness switching, and a byte-level CompArch visualization.cee0192..8934887): replaces prop-drilling of hover state with auseHoverHighlight()provide/inject composable. ByteMap gains an SVG overlay that renders execution highlights as contiguous staircase paths per allocation, with byte ↔ DS view hover propagation. Splitpane drag is deferred to drag-end for perf.See
docs/superpowers/specs/for design docs if relevant. Pre-merge checklist green: typecheck, lint, knip, 177/177 tests, build.Test plan
singly-insertBack,doubly-insertFront,btree,tree-skewedfrom start through breakpoint in both Allocation and Bytes views🤖 Generated with Claude Code