From 49176424b1e6a3e0cb82e8c2a091699d79a063f0 Mon Sep 17 00:00:00 2001 From: zojize Date: Sun, 19 Apr 2026 19:54:07 -0500 Subject: [PATCH 01/29] style(viz): drop redundant border-l-transparent on memory cards The default border color already matches the background when no accent is applied; the explicit transparent left border caused a subtle color mismatch with the rest of the 1px border. --- src/components/MemoryCell.vue | 2 +- src/components/MemoryMap/AllocationMap.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/MemoryCell.vue b/src/components/MemoryCell.vue index 5868c86..4d59790 100644 --- a/src/components/MemoryCell.vue +++ b/src/components/MemoryCell.vue @@ -41,7 +41,7 @@ const structName = computed(() => { From 9d0dfbf2a9d2cc9ecb94d7f13e7300c3cd97447f Mon Sep 17 00:00:00 2001 From: zojize Date: Tue, 21 Apr 2026 17:33:57 -0500 Subject: [PATCH 28/29] feat(viz): expand array fields per element in allocation cards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Struct allocations on the heap (and in stack cards that flowed through MemoryCell) used to render array fields as a single summary row like "keys [3]" — useful to know the shape, useless for seeing actual contents. `getFieldValues` now flattens every array field into one row per element (`keys[0]`, `keys[1]`, `children[0]`, etc.) so the card mirrors the memory layout byte-for-byte. Nested arrays recurse with subscript chaining. Each flattened row carries its own address, which lets statement LHS / RHS tinting propagate down to the specific element being written — a step on `root->keys[1] = 20` now paints the `keys[1]` row blue instead of the whole card. MemoryCell gains the corresponding statementLhs/RhsAddresses props and applies the same blue/green tint rules DSValue already used for the DS view and stack rendering paths. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/components/MemoryCell.vue | 8 ++++- src/components/MemoryMap/AllocationMap.vue | 35 ++++++++++++++++++++-- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/components/MemoryCell.vue b/src/components/MemoryCell.vue index 4d59790..9024c8f 100644 --- a/src/components/MemoryCell.vue +++ b/src/components/MemoryCell.vue @@ -21,6 +21,8 @@ const props = defineProps<{ fieldValues?: Map changed?: boolean highlightedFieldAddress?: number | null + statementLhsAddresses?: ReadonlySet + statementRhsAddresses?: ReadonlySet }>() const emit = defineEmits<{ @@ -63,7 +65,11 @@ const structName = computed(() => { :key="name" :data-field-address="field.address" class="flex items-baseline justify-between gap-4 border-b border-gray-200 py-1 font-mono transition-colors last:border-b-0 dark:border-gray-200/10" - :class="{ 'bg-blue-500/15! rounded px-1 -mx-1': highlightedFieldAddress === field.address }" + :class="{ + 'bg-blue-500/15! rounded px-1 -mx-1': highlightedFieldAddress === field.address, + 'bg-blue-500/10! rounded px-1 -mx-1': highlightedFieldAddress !== field.address && statementLhsAddresses?.has(field.address), + 'bg-green-500/10! rounded px-1 -mx-1': highlightedFieldAddress !== field.address && !statementLhsAddresses?.has(field.address) && statementRhsAddresses?.has(field.address), + }" > {{ name }} () + + // Expand array fields into one row per element (e.g. `keys[0]`, `keys[1]`, + // `children[0]`…) so the user sees every byte accounted for and the + // current-statement highlight can land on the exact element being + // written. Nested arrays recurse with subscript prefixes. + function flattenArray(prefix: string, elemNode: LayoutNode, elemAddr: number, length: number, stride: number) { + for (let i = 0; i < length; i++) { + const addr = elemAddr + i * stride + const subscript = `${prefix}[${i}]` + if (elemNode.kind === 'array') { + flattenArray(subscript, elemNode.element, addr, elemNode.length, elemNode.stride) + continue + } + map.set(subscript, { + type: fieldNodeToType(elemNode), + value: readByLayout(addr, elemNode), + address: addr, + }) + } + } + for (const field of layout.fields) { const fieldAddr = address + field.offset - const fieldType = fieldNodeToType(field.node) + if (field.node.kind === 'array') { + flattenArray(field.name, field.node.element, fieldAddr, field.node.length, field.node.stride) + continue + } // Read based on the FIELD'S own layout node — findAllocation would return // the enclosing struct, which would mis-type primitive/pointer fields as structs. - const fieldValue = readByLayout(fieldAddr, field.node) - map.set(field.name, { type: fieldType, value: fieldValue, address: fieldAddr }) + map.set(field.name, { + type: fieldNodeToType(field.node), + value: readByLayout(fieldAddr, field.node), + address: fieldAddr, + }) } return map } @@ -500,6 +527,8 @@ watch(hover.fieldAddress, (addr) => { :field-values="entry.fields" :changed="changedAddresses.has(entry.cell.address)" :highlighted-field-address="hover.fieldAddress.value" + :statement-lhs-addresses="statementLhsAddresses" + :statement-rhs-addresses="statementRhsAddresses" :class="{ 'outline outline-2 outline-blue-400': selectedAddress === entry.cell.address, 'border-l-blue-500!': isStatementLhs(entry.cell.address), From 85fb5cd735f093c4f1b5cc87dfc2f4de4d720f6a Mon Sep 17 00:00:00 2001 From: zojize Date: Tue, 21 Apr 2026 17:44:18 -0500 Subject: [PATCH 29/29] fix(viz): ByteMap skips same-column scroll on pointer hover MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pointer-target auto-scroll was yanking the same column the user was hovering in when a pointer and its target lived in the same region — scrolling stack while the cursor was anchored to a stack pointer, or heap-to-heap. That lost the hover context and usually the arrow along with it. Now mirrors the AllocationMap pattern: if hover.source is 'byte-stack' and the target is also on the stack, skip the scroll; same for heap. Cross-region targets (the common case — stack pointer to heap alloc) still auto-scroll as before. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/components/MemoryMap/ByteMap.vue | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/components/MemoryMap/ByteMap.vue b/src/components/MemoryMap/ByteMap.vue index 95d31fa..1ca239c 100644 --- a/src/components/MemoryMap/ByteMap.vue +++ b/src/components/MemoryMap/ByteMap.vue @@ -236,16 +236,25 @@ onActivated(() => { // interesting allocations sit near the bottom). Mirrors the AllocationMap // behaviour: bring the target row into view so the arrow has something to // point at without the user manually scrolling. +// +// Skip scrolling if the target lives in the same column the user is already +// hovering — the cursor is anchored to a specific row there, and yanking +// that column away loses the hover context and the arrow alike. watch(() => hover.pointerArrow.value?.target, (target) => { if (target == null || target <= 0) return const half = MEMORY_SIZE / 2 + const src = hover.source.value if (target < half) { + if (src === 'byte-stack') + return const bpr = stackBytesPerRow.value if (bpr > 0) stackScrollToIdx(Math.floor(target / bpr)) } else { + if (src === 'byte-heap') + return const bpr = heapBytesPerRow.value if (bpr > 0) heapScrollToIdx(Math.floor((target - half) / bpr))