mirror of
https://github.com/waaraawa/ByteGrid.git
synced 2026-07-22 06:41:47 +00:00
feat(core): improve legendColumns with row-major order
- Change legend layout from column-major to row-major order - Fields now fill left-to-right, then top-to-bottom - More intuitive for users (e.g., 4 fields + 3 cols = 3-1 layout) - Fix bottom position width calculation for multi-column legends - totalWidth now accounts for legend width (200px per column) - Add tests for 3-column layout with 6 fields
This commit is contained in:
parent
4d5cd4e289
commit
cbba30bb72
7 changed files with 553 additions and 20 deletions
|
|
@ -118,5 +118,3 @@ fields:
|
|||
**layoutUnit field:**
|
||||
- Can be omitted (auto-inferred from suffix)
|
||||
- Can be explicit (use `layoutUnit: bit` or `layoutUnit: byte` if desired)
|
||||
|
||||
For more details, see [CLAUDE.md](../CLAUDE.md).
|
||||
|
|
|
|||
|
|
@ -247,6 +247,11 @@ export function parse(source: string): ByteGridConfig {
|
|||
? (obj.legendPosition as 'right' | 'left' | 'bottom' | 'none')
|
||||
: undefined;
|
||||
|
||||
// Parse legendColumns (optional)
|
||||
const legendColumns = obj.legendColumns && typeof obj.legendColumns === 'number'
|
||||
? obj.legendColumns
|
||||
: undefined;
|
||||
|
||||
// Parse showFooter (optional)
|
||||
const showFooter = obj.showFooter !== undefined && typeof obj.showFooter === 'boolean'
|
||||
? obj.showFooter
|
||||
|
|
@ -262,6 +267,7 @@ export function parse(source: string): ByteGridConfig {
|
|||
layoutUnit: finalLayoutUnit,
|
||||
colorScheme,
|
||||
legendPosition,
|
||||
legendColumns,
|
||||
showFooter,
|
||||
fields,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ const DEFAULT_OPTIONS: Required<RenderOptions> = {
|
|||
showHexDump: false,
|
||||
showLegend: true,
|
||||
legendPosition: 'right',
|
||||
legendColumns: 1,
|
||||
showFooter: true,
|
||||
showGrid: true,
|
||||
cellWidth: 40, // Increased from 30 to make bit numbers readable
|
||||
|
|
@ -78,6 +79,14 @@ export function renderSVG(
|
|||
? config.showFooter
|
||||
: opts.showFooter;
|
||||
|
||||
// Determine legendColumns
|
||||
// Priority: options.legendColumns > config.legendColumns > default (1)
|
||||
const effectiveLegendColumns = options?.legendColumns !== undefined
|
||||
? Math.max(1, Math.floor(options.legendColumns))
|
||||
: config.legendColumns !== undefined
|
||||
? Math.max(1, Math.floor(config.legendColumns))
|
||||
: opts.legendColumns;
|
||||
|
||||
// Detect rows with bitfields
|
||||
const rowsWithBitfields = new Set<number>();
|
||||
for (const block of blocks) {
|
||||
|
|
@ -94,7 +103,10 @@ export function renderSVG(
|
|||
// Calculate dimensions
|
||||
const rows = Math.ceil(config.size / layout);
|
||||
const gridWidth = layout * opts.cellWidth;
|
||||
const legendWidth = effectiveLegendPosition !== 'none' && effectiveLegendPosition !== 'bottom' ? 200 : 0;
|
||||
const legendColumnWidth = 200; // Width of each legend column
|
||||
const legendWidth = effectiveLegendPosition !== 'none' && effectiveLegendPosition !== 'bottom'
|
||||
? legendColumnWidth * effectiveLegendColumns
|
||||
: 0;
|
||||
const margin = 20;
|
||||
|
||||
// Count unique fields for legend height calculation
|
||||
|
|
@ -104,7 +116,7 @@ export function renderSVG(
|
|||
// Calculate legend height (for 'bottom' position or side positions)
|
||||
let legendHeight = 0;
|
||||
if (effectiveLegendPosition !== 'none' && fieldCount > 0) {
|
||||
// Calculate actual height needed based on bitfields
|
||||
// Calculate actual height needed based on bitfields (row-major order)
|
||||
let totalLegendHeight = 40; // Header space
|
||||
const uniqueFields = new Map<string, LayoutBlock>();
|
||||
for (const block of blocks) {
|
||||
|
|
@ -112,13 +124,25 @@ export function renderSVG(
|
|||
uniqueFields.set(block.fieldName, block);
|
||||
}
|
||||
}
|
||||
|
||||
// For multi-column row-major, we need to find the tallest entry in each row
|
||||
const rowHeights: number[] = [];
|
||||
let fieldIndex = 0;
|
||||
for (const [, block] of uniqueFields) {
|
||||
const rowIndex = Math.floor(fieldIndex / effectiveLegendColumns);
|
||||
let entryHeight = 50;
|
||||
if (block.bitfields && block.bitfields.length > 0) {
|
||||
entryHeight = 50 + block.bitfields.length * 12;
|
||||
}
|
||||
totalLegendHeight += entryHeight;
|
||||
rowHeights[rowIndex] = Math.max(rowHeights[rowIndex] || 0, entryHeight);
|
||||
fieldIndex++;
|
||||
}
|
||||
|
||||
// Sum up all row heights
|
||||
for (const h of rowHeights) {
|
||||
totalLegendHeight += h;
|
||||
}
|
||||
|
||||
legendHeight = totalLegendHeight;
|
||||
}
|
||||
|
||||
|
|
@ -153,7 +177,9 @@ export function renderSVG(
|
|||
gridStartY = margin + 60;
|
||||
} else if (effectiveLegendPosition === 'bottom') {
|
||||
// Legend at bottom (with extra 30px spacing for title)
|
||||
totalWidth = margin + gridWidth + margin;
|
||||
// Need to account for multi-column legend width
|
||||
const bottomLegendWidth = legendColumnWidth * effectiveLegendColumns;
|
||||
totalWidth = margin + Math.max(gridWidth, bottomLegendWidth) + margin;
|
||||
totalHeight = margin + 60 + gridHeight + margin + 30 + legendHeight + footerHeight;
|
||||
gridStartX = margin;
|
||||
gridStartY = margin + 60;
|
||||
|
|
@ -306,45 +332,75 @@ export function renderSVG(
|
|||
}
|
||||
}
|
||||
|
||||
let index = 0;
|
||||
for (const [fieldName, block] of uniqueFields) {
|
||||
let entryHeight = 50; // Default height per field
|
||||
// Calculate number of rows (row-major order: fill columns left-to-right)
|
||||
const totalFields = uniqueFields.size;
|
||||
const totalRows = Math.ceil(totalFields / effectiveLegendColumns);
|
||||
|
||||
// Calculate height needed if bitfields exist
|
||||
if (block.bitfields && block.bitfields.length > 0) {
|
||||
entryHeight = 50 + block.bitfields.length * 12; // Extra 12px per bitfield
|
||||
// Calculate cumulative Y positions for each row (to handle variable heights)
|
||||
const rowYPositions: number[] = [legendStartY];
|
||||
const fieldsArray = Array.from(uniqueFields.entries());
|
||||
|
||||
for (let row = 0; row < totalRows; row++) {
|
||||
let maxHeightInRow = 50; // Default height
|
||||
|
||||
// Check all fields in this row across all columns (row-major)
|
||||
for (let col = 0; col < effectiveLegendColumns; col++) {
|
||||
const fieldIndex = row * effectiveLegendColumns + col;
|
||||
if (fieldIndex < totalFields) {
|
||||
const [, block] = fieldsArray[fieldIndex];
|
||||
let entryHeight = 50;
|
||||
if (block.bitfields && block.bitfields.length > 0) {
|
||||
entryHeight = 50 + block.bitfields.length * 12;
|
||||
}
|
||||
maxHeightInRow = Math.max(maxHeightInRow, entryHeight);
|
||||
}
|
||||
}
|
||||
|
||||
const y = legendStartY + index * entryHeight;
|
||||
if (row < totalRows - 1) {
|
||||
rowYPositions.push(rowYPositions[row] + maxHeightInRow);
|
||||
}
|
||||
}
|
||||
|
||||
// Render fields in row-major order (left-to-right, then top-to-bottom)
|
||||
let fieldIndex = 0;
|
||||
for (const [fieldName, block] of uniqueFields) {
|
||||
// Calculate row and column for this field (row-major)
|
||||
const row = Math.floor(fieldIndex / effectiveLegendColumns);
|
||||
const col = fieldIndex % effectiveLegendColumns;
|
||||
|
||||
// Calculate position
|
||||
const x = legendX + col * legendColumnWidth;
|
||||
const y = rowYPositions[row];
|
||||
|
||||
const color = COLORS[block.color] || COLORS.gray;
|
||||
|
||||
// Color box
|
||||
svg += `<rect x="${legendX}" y="${y}" width="20" height="20" fill="${color}" stroke="#333" stroke-width="1"/>`;
|
||||
svg += `<rect x="${x}" y="${y}" width="20" height="20" fill="${color}" stroke="#333" stroke-width="1"/>`;
|
||||
|
||||
// Field name
|
||||
svg += `<text x="${legendX + 30}" y="${y + 12}" font-size="11" font-weight="bold" fill="#333">${escapeXml(fieldName)}</text>`;
|
||||
svg += `<text x="${x + 30}" y="${y + 12}" font-size="11" font-weight="bold" fill="#333">${escapeXml(fieldName)}</text>`;
|
||||
|
||||
// Type
|
||||
svg += `<text x="${legendX + 30}" y="${y + 24}" font-size="9" fill="#666">${escapeXml(block.fieldType)}</text>`;
|
||||
svg += `<text x="${x + 30}" y="${y + 24}" font-size="9" fill="#666">${escapeXml(block.fieldType)}</text>`;
|
||||
|
||||
// Offset info
|
||||
const unit = layoutUnit === 'bit' ? 'bits' : 'bytes';
|
||||
const size = block.offsetEnd - block.offsetStart + 1;
|
||||
svg += `<text x="${legendX + 30}" y="${y + 36}" font-size="9" fill="#999">offset: ${block.offsetStart}-${block.offsetEnd} (${size} ${unit})</text>`;
|
||||
svg += `<text x="${x + 30}" y="${y + 36}" font-size="9" fill="#999">offset: ${block.offsetStart}-${block.offsetEnd} (${size} ${unit})</text>`;
|
||||
|
||||
// Bitfields (if any)
|
||||
if (block.bitfields && block.bitfields.length > 0) {
|
||||
svg += `<text x="${legendX + 30}" y="${y + 48}" font-size="9" font-weight="bold" fill="#666">Bits:</text>`;
|
||||
svg += `<text x="${x + 30}" y="${y + 48}" font-size="9" font-weight="bold" fill="#666">Bits:</text>`;
|
||||
|
||||
block.bitfields.forEach((bf, bfIndex) => {
|
||||
const bfY = y + 60 + bfIndex * 12;
|
||||
svg += `<text x="${legendX + 35}" y="${bfY}" font-size="8" fill="#888">`;
|
||||
svg += `<text x="${x + 35}" y="${bfY}" font-size="8" fill="#888">`;
|
||||
svg += `bit ${escapeXml(bf.bits)}: ${escapeXml(bf.name)}`;
|
||||
svg += `</text>`;
|
||||
});
|
||||
}
|
||||
|
||||
index++;
|
||||
fieldIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ export interface ByteGridConfig {
|
|||
layoutUnit?: LayoutUnit; // 'byte' (default) or 'bit'
|
||||
colorScheme?: ColorScheme;
|
||||
legendPosition?: LegendPosition; // 'right' (default), 'left', 'bottom', or 'none'
|
||||
legendColumns?: number; // Number of columns in legend (default: 1)
|
||||
showFooter?: boolean; // Show footer with total size and layout info (default: true)
|
||||
fields: Field[];
|
||||
}
|
||||
|
|
@ -131,6 +132,7 @@ export interface RenderOptions {
|
|||
showHexDump?: boolean;
|
||||
showLegend?: boolean; // Deprecated: use legendPosition instead
|
||||
legendPosition?: LegendPosition; // 'right' (default), 'left', 'bottom', or 'none'
|
||||
legendColumns?: number; // Number of columns in legend (default: 1)
|
||||
showFooter?: boolean; // Show footer with total size and layout info (default: true)
|
||||
showGrid?: boolean;
|
||||
cellWidth?: number;
|
||||
|
|
|
|||
|
|
@ -624,5 +624,55 @@ fields:
|
|||
expect(result.showFooter).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
// legendColumns tests
|
||||
describe('legendColumns', () => {
|
||||
it('should parse legendColumns: 1', () => {
|
||||
const yaml = `
|
||||
name: Test
|
||||
size: 16
|
||||
legendColumns: 1
|
||||
fields:
|
||||
- offset: 0-3
|
||||
name: Field1
|
||||
type: uint32_t
|
||||
`;
|
||||
|
||||
const result = parse(yaml);
|
||||
|
||||
expect(result.legendColumns).toBe(1);
|
||||
});
|
||||
|
||||
it('should parse legendColumns: 2', () => {
|
||||
const yaml = `
|
||||
name: Test
|
||||
size: 16
|
||||
legendColumns: 2
|
||||
fields:
|
||||
- offset: 0-3
|
||||
name: Field1
|
||||
type: uint32_t
|
||||
`;
|
||||
|
||||
const result = parse(yaml);
|
||||
|
||||
expect(result.legendColumns).toBe(2);
|
||||
});
|
||||
|
||||
it('should return undefined when legendColumns is not specified', () => {
|
||||
const yaml = `
|
||||
name: Test
|
||||
size: 16
|
||||
fields:
|
||||
- offset: 0-3
|
||||
name: Field1
|
||||
type: uint32_t
|
||||
`;
|
||||
|
||||
const result = parse(yaml);
|
||||
|
||||
expect(result.legendColumns).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -483,5 +483,138 @@ describe('SVGRenderer', () => {
|
|||
expect(svg).not.toContain('Total size:');
|
||||
});
|
||||
});
|
||||
|
||||
// legendColumns tests
|
||||
describe('legendColumns option', () => {
|
||||
const multiFieldConfig: ByteGridConfig = {
|
||||
name: 'Multi Field Test',
|
||||
size: 16,
|
||||
layout: 16,
|
||||
fields: [
|
||||
{ offset: '0-3', name: 'Field1', type: 'uint32_t', color: 'blue' },
|
||||
{ offset: '4-7', name: 'Field2', type: 'uint32_t', color: 'cyan' },
|
||||
{ offset: '8-11', name: 'Field3', type: 'uint32_t', color: 'yellow' },
|
||||
{ offset: '12-15', name: 'Field4', type: 'uint32_t', color: 'green' },
|
||||
],
|
||||
};
|
||||
|
||||
const multiFieldBlocks: LayoutBlock[] = [
|
||||
{
|
||||
row: 0, col: 0, span: 4,
|
||||
fieldName: 'Field1', fieldType: 'uint32_t', color: 'blue',
|
||||
offsetStart: 0, offsetEnd: 3, isPadding: false,
|
||||
},
|
||||
{
|
||||
row: 0, col: 4, span: 4,
|
||||
fieldName: 'Field2', fieldType: 'uint32_t', color: 'cyan',
|
||||
offsetStart: 4, offsetEnd: 7, isPadding: false,
|
||||
},
|
||||
{
|
||||
row: 0, col: 8, span: 4,
|
||||
fieldName: 'Field3', fieldType: 'uint32_t', color: 'yellow',
|
||||
offsetStart: 8, offsetEnd: 11, isPadding: false,
|
||||
},
|
||||
{
|
||||
row: 0, col: 12, span: 4,
|
||||
fieldName: 'Field4', fieldType: 'uint32_t', color: 'green',
|
||||
offsetStart: 12, offsetEnd: 15, isPadding: false,
|
||||
},
|
||||
];
|
||||
|
||||
it('should render legend in 1 column by default', () => {
|
||||
const svg = renderSVG(multiFieldConfig, multiFieldBlocks);
|
||||
|
||||
expect(svg).toContain('Field1');
|
||||
expect(svg).toContain('Field2');
|
||||
expect(svg).toContain('Field3');
|
||||
expect(svg).toContain('Field4');
|
||||
});
|
||||
|
||||
it('should render legend in 2 columns when legendColumns is 2', () => {
|
||||
const svg = renderSVG(multiFieldConfig, multiFieldBlocks, {
|
||||
legendColumns: 2,
|
||||
});
|
||||
|
||||
expect(svg).toContain('Field1');
|
||||
expect(svg).toContain('Field2');
|
||||
expect(svg).toContain('Field3');
|
||||
expect(svg).toContain('Field4');
|
||||
});
|
||||
|
||||
it('should use config.legendColumns when options.legendColumns is not provided', () => {
|
||||
const configWithColumns: ByteGridConfig = {
|
||||
...multiFieldConfig,
|
||||
legendColumns: 2,
|
||||
};
|
||||
|
||||
const svg = renderSVG(configWithColumns, multiFieldBlocks);
|
||||
|
||||
expect(svg).toContain('Field1');
|
||||
expect(svg).toContain('Field2');
|
||||
});
|
||||
|
||||
it('should prioritize options.legendColumns over config.legendColumns', () => {
|
||||
const configWithColumns: ByteGridConfig = {
|
||||
...multiFieldConfig,
|
||||
legendColumns: 1,
|
||||
};
|
||||
|
||||
const svg = renderSVG(configWithColumns, multiFieldBlocks, {
|
||||
legendColumns: 2,
|
||||
});
|
||||
|
||||
expect(svg).toContain('Field1');
|
||||
});
|
||||
|
||||
it('should handle legendColumns with minimum value of 1', () => {
|
||||
const svg = renderSVG(multiFieldConfig, multiFieldBlocks, {
|
||||
legendColumns: 0, // Should be treated as 1
|
||||
});
|
||||
|
||||
expect(svg).toContain('Field1');
|
||||
});
|
||||
|
||||
it('should render legend in 3 columns when legendColumns is 3', () => {
|
||||
// 6 fields for testing 3 columns
|
||||
const sixFieldConfig: ByteGridConfig = {
|
||||
name: 'Six Field Test',
|
||||
size: 24,
|
||||
layout: 16,
|
||||
fields: [
|
||||
{ offset: '0-3', name: 'Field1', type: 'uint32_t', color: 'blue' },
|
||||
{ offset: '4-7', name: 'Field2', type: 'uint32_t', color: 'cyan' },
|
||||
{ offset: '8-11', name: 'Field3', type: 'uint32_t', color: 'yellow' },
|
||||
{ offset: '12-15', name: 'Field4', type: 'uint32_t', color: 'green' },
|
||||
{ offset: '16-19', name: 'Field5', type: 'uint32_t', color: 'orange' },
|
||||
{ offset: '20-23', name: 'Field6', type: 'uint32_t', color: 'purple' },
|
||||
],
|
||||
};
|
||||
|
||||
const sixFieldBlocks: LayoutBlock[] = [
|
||||
{ row: 0, col: 0, span: 4, fieldName: 'Field1', fieldType: 'uint32_t', color: 'blue', offsetStart: 0, offsetEnd: 3, isPadding: false },
|
||||
{ row: 0, col: 4, span: 4, fieldName: 'Field2', fieldType: 'uint32_t', color: 'cyan', offsetStart: 4, offsetEnd: 7, isPadding: false },
|
||||
{ row: 0, col: 8, span: 4, fieldName: 'Field3', fieldType: 'uint32_t', color: 'yellow', offsetStart: 8, offsetEnd: 11, isPadding: false },
|
||||
{ row: 0, col: 12, span: 4, fieldName: 'Field4', fieldType: 'uint32_t', color: 'green', offsetStart: 12, offsetEnd: 15, isPadding: false },
|
||||
{ row: 1, col: 0, span: 4, fieldName: 'Field5', fieldType: 'uint32_t', color: 'orange', offsetStart: 16, offsetEnd: 19, isPadding: false },
|
||||
{ row: 1, col: 4, span: 4, fieldName: 'Field6', fieldType: 'uint32_t', color: 'purple', offsetStart: 20, offsetEnd: 23, isPadding: false },
|
||||
];
|
||||
|
||||
const svg = renderSVG(sixFieldConfig, sixFieldBlocks, {
|
||||
legendColumns: 3,
|
||||
});
|
||||
|
||||
// All 6 fields should be present
|
||||
expect(svg).toContain('Field1');
|
||||
expect(svg).toContain('Field2');
|
||||
expect(svg).toContain('Field3');
|
||||
expect(svg).toContain('Field4');
|
||||
expect(svg).toContain('Field5');
|
||||
expect(svg).toContain('Field6');
|
||||
|
||||
// Check for proper width (should be wider for 3 columns)
|
||||
// Each column is 200px, so 3 columns = 600px
|
||||
expect(svg).toMatch(/width="\d+"/);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
288
roadmap.md
Normal file
288
roadmap.md
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
# ByteGrid Development Roadmap
|
||||
|
||||
**Last Updated:** 2025-10-14
|
||||
**Current Phase:** Phase 2 (Core Features)
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: MVP ✅ COMPLETED
|
||||
|
||||
**Goal:** Basic structure visualization with manual field definition
|
||||
|
||||
### Completed Features
|
||||
- [x] Project structure setup (monorepo with core + plugin)
|
||||
- [x] Basic type definitions (ByteGridConfig, Field, LayoutBlock)
|
||||
- [x] YAML parser with js-yaml
|
||||
- [x] Field validation (overlap detection, range checking)
|
||||
- [x] Simple layout engine (single row)
|
||||
- [x] Basic SVG renderer
|
||||
- [x] Obsidian plugin integration
|
||||
- [x] Test infrastructure (Jest)
|
||||
|
||||
**Status:** ✅ All core functionality implemented
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Core Features 🚧 IN PROGRESS
|
||||
|
||||
**Goal:** Robust visualization with advanced layout options
|
||||
|
||||
### Completed Features ✅
|
||||
- [x] Multi-row layout support
|
||||
- [x] Grid lines rendering
|
||||
- [x] Color system (9 predefined colors)
|
||||
- [x] Bitfield visualization (sub-byte fields)
|
||||
- [x] Bit-unit layout mode (`layoutUnit: bit`)
|
||||
- [x] Suffix notation (`32b`, `4B`)
|
||||
- [x] Legend position control (`legendPosition: right/left/bottom/none`)
|
||||
- [x] Footer visibility control (`showFooter: true/false`)
|
||||
- [x] Comprehensive test coverage (114 tests)
|
||||
|
||||
### In Progress 🔄
|
||||
- [ ] Color schemes (dark, light modes)
|
||||
- [ ] Row/column labels customization
|
||||
- [ ] Cell padding/spacing options
|
||||
|
||||
### Planned Features 📋
|
||||
- [ ] Hex dump display (`showHexDump: true`)
|
||||
- [ ] Field description tooltips
|
||||
- [ ] Value display formatting (hex, decimal, binary)
|
||||
- [ ] Endianness indicators
|
||||
|
||||
**Target Completion:** Q4 2025
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Interactive Features 📅 PLANNED
|
||||
|
||||
**Goal:** Rich user interaction and real-time visualization
|
||||
|
||||
### Features
|
||||
- [ ] Interactive tooltips on hover
|
||||
- Field name, type, offset
|
||||
- Description and value
|
||||
- Bit range for bitfields
|
||||
- [ ] Field highlighting
|
||||
- Click to highlight field
|
||||
- Show all related blocks
|
||||
- Copy field info to clipboard
|
||||
- [ ] Zoom controls
|
||||
- Zoom in/out
|
||||
- Pan/scroll for large structures
|
||||
- [ ] Search/filter fields
|
||||
- Search by name or type
|
||||
- Filter by color or category
|
||||
- [ ] Export options
|
||||
- Export as SVG file
|
||||
- Export as PNG image
|
||||
- Export as C struct definition
|
||||
- Copy as Markdown table
|
||||
|
||||
**Target Start:** Q1 2026
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Advanced Features 📅 FUTURE
|
||||
|
||||
**Goal:** Binary file integration and advanced analysis
|
||||
|
||||
### Binary File Support
|
||||
- [ ] Binary file parser (`binaryParser.ts`)
|
||||
- Read binary files
|
||||
- Auto-populate field values
|
||||
- Endianness handling
|
||||
- Generate hex dump
|
||||
- [ ] File format templates
|
||||
- WAV_HEADER (audio files)
|
||||
- ELF_HEADER (executables)
|
||||
- TCP_HEADER (network packets)
|
||||
- PNG_HEADER (image files)
|
||||
- Custom template system
|
||||
|
||||
### Structure Analysis
|
||||
- [ ] Structure comparison (`comparison.ts`)
|
||||
- Side-by-side view
|
||||
- Overlay view
|
||||
- Diff highlighting
|
||||
- [ ] Padding analysis
|
||||
- Auto-detect padding bytes
|
||||
- Show memory waste
|
||||
- Suggest optimizations
|
||||
- [ ] Alignment visualization
|
||||
- Show alignment boundaries
|
||||
- Detect misaligned fields
|
||||
- Platform-specific rules
|
||||
|
||||
**Target Start:** Q2 2026
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Performance & Scale 📅 FUTURE
|
||||
|
||||
**Goal:** Handle large structures and optimize rendering
|
||||
|
||||
### Performance Optimization
|
||||
- [ ] Rendering cache (`cache.ts`)
|
||||
- Hash-based cache keys
|
||||
- TTL-based expiration
|
||||
- localStorage persistence
|
||||
- [ ] Virtual scrolling
|
||||
- Lazy render large structures
|
||||
- Only render visible rows
|
||||
- Smooth scrolling
|
||||
- [ ] Pagination
|
||||
- Break large structures (>1000 bytes)
|
||||
- Page navigation controls
|
||||
- Jump to offset
|
||||
|
||||
### Large Structure Support
|
||||
- [ ] Performance benchmarks
|
||||
- 1KB: < 100ms
|
||||
- 10KB: < 500ms
|
||||
- 100KB: < 2s (with pagination)
|
||||
- [ ] Memory optimization
|
||||
- Efficient data structures
|
||||
- DOM node recycling
|
||||
- Progressive rendering
|
||||
|
||||
**Target Start:** Q3 2026
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Extensibility 📅 FUTURE
|
||||
|
||||
**Goal:** Plugin system and customization
|
||||
|
||||
### Custom Extensions
|
||||
- [ ] Type system extensions
|
||||
- Custom type definitions
|
||||
- Type aliases
|
||||
- Nested structures
|
||||
- Union types
|
||||
- [ ] Custom color schemes
|
||||
- User-defined palettes
|
||||
- Theme import/export
|
||||
- Dark/light mode auto-switch
|
||||
- [ ] Render hooks
|
||||
- Pre-render processing
|
||||
- Post-render modifications
|
||||
- Custom SVG elements
|
||||
- [ ] Plugin API
|
||||
- Register custom parsers
|
||||
- Add custom validators
|
||||
- Extend layout engine
|
||||
|
||||
### Developer Tools
|
||||
- [ ] Debug mode
|
||||
- Show internal state
|
||||
- Performance metrics
|
||||
- Error details
|
||||
- [ ] Documentation generator
|
||||
- Auto-generate type docs
|
||||
- Example templates
|
||||
- Interactive playground
|
||||
|
||||
**Target Start:** Q4 2026
|
||||
|
||||
---
|
||||
|
||||
## Current Sprint (October 2024)
|
||||
|
||||
### Completed This Sprint ✅
|
||||
- [x] Bit-unit layout implementation
|
||||
- [x] Legend position options (right, left, bottom, none)
|
||||
- [x] Footer visibility control
|
||||
- [x] Fix legend overlapping issues
|
||||
- [x] Add comprehensive tests for new features
|
||||
- [x] Documentation updates
|
||||
|
||||
### Next Sprint Goals (November 2024)
|
||||
1. **Color Schemes** (Priority: High)
|
||||
- Implement dark and light color schemes
|
||||
- Add `colorScheme` option to config
|
||||
- Update color constants
|
||||
- Add tests
|
||||
|
||||
2. **Hex Dump Display** (Priority: Medium)
|
||||
- Add `showHexDump` option
|
||||
- Render hex values below grid
|
||||
- Format with proper spacing
|
||||
- Handle large structures
|
||||
|
||||
3. **Documentation** (Priority: High)
|
||||
- Update CLAUDE.md with new options
|
||||
- Create user guide in docs/
|
||||
- Add more examples
|
||||
- API reference cleanup
|
||||
|
||||
---
|
||||
|
||||
## Known Limitations & Future Improvements
|
||||
|
||||
### Current Limitations
|
||||
1. **YAML Line Numbers**
|
||||
- Error messages show field index, not line number
|
||||
- **Improvement:** Custom YAML parser with position tracking
|
||||
|
||||
2. **Cache Persistence**
|
||||
- Cache cleared on session end
|
||||
- **Improvement:** localStorage persistence (Phase 5)
|
||||
|
||||
3. **Type System**
|
||||
- No custom type definitions
|
||||
- No nested structures
|
||||
- **Improvement:** Type alias system (Phase 6)
|
||||
|
||||
4. **Bit Ordering**
|
||||
- Assumes MSB-first
|
||||
- **Improvement:** Add `bitOrder: msb/lsb` option
|
||||
|
||||
5. **Layout Constraints**
|
||||
- Fixed cell sizes per visualization
|
||||
- **Improvement:** Responsive cell sizing
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
See [CONTRIBUTING.md](../CONTRIBUTING.md) for guidelines on:
|
||||
- Proposing new features
|
||||
- Submitting pull requests
|
||||
- Testing requirements
|
||||
- Code style guidelines
|
||||
|
||||
---
|
||||
|
||||
## Versioning Strategy
|
||||
|
||||
Following **Semantic Versioning** (MAJOR.MINOR.PATCH):
|
||||
|
||||
- **MAJOR:** Breaking changes to public API
|
||||
- **MINOR:** New features, backward compatible
|
||||
- **PATCH:** Bug fixes, performance improvements
|
||||
|
||||
### Planned Releases
|
||||
- **v0.2.0** - Phase 2 completion (Color schemes, hex dump)
|
||||
- **v0.3.0** - Phase 3 start (Interactive features)
|
||||
- **v1.0.0** - Production ready (All core features stable)
|
||||
- **v2.0.0** - Phase 4-5 (Binary files, performance)
|
||||
|
||||
---
|
||||
|
||||
## Feedback & Priorities
|
||||
|
||||
Feature priorities may change based on:
|
||||
- User feedback and requests
|
||||
- Community contributions
|
||||
- Technical dependencies
|
||||
- Performance considerations
|
||||
|
||||
To suggest features or vote on priorities:
|
||||
- Open an issue on GitHub
|
||||
- Join discussions in the community
|
||||
- Contribute code or documentation
|
||||
|
||||
---
|
||||
|
||||
**Document Version:** 1.0
|
||||
**Next Review:** December 2024
|
||||
Loading…
Reference in a new issue