|
| 1 | +# Grid Cell Expansion — Implementation Plan |
| 2 | + |
| 3 | +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. |
| 4 | +
|
| 5 | +**Goal:** Enable inline expand/collapse for complex table cells in the RepeatingElementsTable grid view, so users can see and edit child nodes directly within the table. |
| 6 | + |
| 7 | +**Architecture:** Add cell-level expand state to `TableRow`, use `FlatRow.flattenElement()` to produce child rows on demand, render them inline within the cell with dynamic row heights, and handle click/edit interactions on the expanded content. |
| 8 | + |
| 9 | +**Tech Stack:** Java 25, JavaFX 24.0.1, Canvas (GraphicsContext), JUnit 5 |
| 10 | + |
| 11 | +**Spec:** `docs/superpowers/specs/2026-03-26-grid-cell-expansion-design.md` |
| 12 | + |
| 13 | +**Status:** Already implemented (commit `19b7eb4a`). This plan documents what was built. |
| 14 | + |
| 15 | +--- |
| 16 | + |
| 17 | +## File Structure |
| 18 | + |
| 19 | +| File | Action | Responsibility | |
| 20 | +|------|--------|----------------| |
| 21 | +| `src/main/java/org/fxt/freexmltoolkit/controls/v2/xmleditor/view/FlatRow.java` | Modify | Add `flattenElement(XmlElement)` convenience method | |
| 22 | +| `src/main/java/org/fxt/freexmltoolkit/controls/v2/xmleditor/view/RepeatingElementsTable.java` | Modify | Add cell expand state to TableRow, variable row heights | |
| 23 | +| `src/main/java/org/fxt/freexmltoolkit/controls/v2/xmleditor/view/XmlCanvasView.java` | Modify | Render expanded cells, handle expand clicks, variable positioning | |
| 24 | + |
| 25 | +--- |
| 26 | + |
| 27 | +### Task 1: Add FlatRow.flattenElement() convenience method |
| 28 | + |
| 29 | +**Files:** |
| 30 | +- Modify: `src/main/java/org/fxt/freexmltoolkit/controls/v2/xmleditor/view/FlatRow.java` |
| 31 | + |
| 32 | +- [x] **Step 1: Add public static flattenElement(XmlElement) method** |
| 33 | + |
| 34 | +```java |
| 35 | +public static List<FlatRow> flattenElement(XmlElement element) { |
| 36 | + List<FlatRow> rows = new ArrayList<>(); |
| 37 | + // Add attributes of the element itself at depth 0 |
| 38 | + int attrIdx = 0; |
| 39 | + for (Map.Entry<String, String> attr : element.getAttributes().entrySet()) { |
| 40 | + FlatRow attrRow = new FlatRow(RowType.ATTRIBUTE, 0, element, null, |
| 41 | + attr.getKey(), attr.getValue(), 0); |
| 42 | + attrRow.setAttributeIndex(attrIdx++); |
| 43 | + rows.add(attrRow); |
| 44 | + } |
| 45 | + // Flatten children recursively |
| 46 | + for (XmlNode child : element.getChildren()) { |
| 47 | + if (child instanceof XmlElement childEl) { |
| 48 | + flattenElement(childEl, 0, null, rows, true); |
| 49 | + } else if (child instanceof XmlText text) { |
| 50 | + if (text.getText() != null && !text.getText().isBlank()) { |
| 51 | + rows.add(new FlatRow(RowType.TEXT, 0, text, null, |
| 52 | + "#text", text.getText().strip(), 0)); |
| 53 | + } |
| 54 | + } else if (child instanceof XmlComment comment) { |
| 55 | + rows.add(new FlatRow(RowType.COMMENT, 0, comment, null, |
| 56 | + null, comment.getText(), 0)); |
| 57 | + } else if (child instanceof XmlCData cdata) { |
| 58 | + rows.add(new FlatRow(RowType.CDATA, 0, cdata, null, |
| 59 | + null, cdata.getText(), 0)); |
| 60 | + } |
| 61 | + } |
| 62 | + return rows; |
| 63 | +} |
| 64 | +``` |
| 65 | + |
| 66 | +- [x] **Step 2: Verify build** |
| 67 | + |
| 68 | +Run: `./gradlew compileJava` |
| 69 | +Expected: BUILD SUCCESSFUL |
| 70 | + |
| 71 | +- [x] **Step 3: Commit** |
| 72 | + |
| 73 | +```bash |
| 74 | +git add src/main/java/org/fxt/freexmltoolkit/controls/v2/xmleditor/view/FlatRow.java |
| 75 | +git commit -m "feat: add FlatRow.flattenElement() for cell expansion" |
| 76 | +``` |
| 77 | + |
| 78 | +--- |
| 79 | + |
| 80 | +### Task 2: Add cell expansion state to RepeatingElementsTable.TableRow |
| 81 | + |
| 82 | +**Files:** |
| 83 | +- Modify: `src/main/java/org/fxt/freexmltoolkit/controls/v2/xmleditor/view/RepeatingElementsTable.java` |
| 84 | + |
| 85 | +- [x] **Step 1: Add expansion fields and methods to TableRow** |
| 86 | + |
| 87 | +```java |
| 88 | +// In TableRow inner class: |
| 89 | +private final Set<String> expandedColumns = new HashSet<>(); |
| 90 | +private final Map<String, List<FlatRow>> expandedCellRows = new LinkedHashMap<>(); |
| 91 | + |
| 92 | +public boolean isColumnExpanded(String columnName) { |
| 93 | + return expandedColumns.contains(columnName); |
| 94 | +} |
| 95 | + |
| 96 | +public void toggleColumnExpanded(String columnName) { |
| 97 | + if (expandedColumns.contains(columnName)) { |
| 98 | + expandedColumns.remove(columnName); |
| 99 | + expandedCellRows.remove(columnName); |
| 100 | + } else { |
| 101 | + expandedColumns.add(columnName); |
| 102 | + XmlElement child = complexChildren.get(columnName); |
| 103 | + if (child != null) { |
| 104 | + expandedCellRows.put(columnName, FlatRow.flattenElement(child)); |
| 105 | + } |
| 106 | + } |
| 107 | +} |
| 108 | + |
| 109 | +public List<FlatRow> getExpandedCellRows(String columnName) { |
| 110 | + return expandedCellRows.getOrDefault(columnName, List.of()); |
| 111 | +} |
| 112 | + |
| 113 | +public Set<String> getExpandedColumns() { |
| 114 | + return expandedColumns; |
| 115 | +} |
| 116 | +``` |
| 117 | + |
| 118 | +- [x] **Step 2: Update calculateRowHeight() to return variable height** |
| 119 | + |
| 120 | +```java |
| 121 | +public double calculateRowHeight(TableRow row) { |
| 122 | + double maxCellHeight = ROW_HEIGHT; |
| 123 | + for (String colName : row.getExpandedColumns()) { |
| 124 | + List<FlatRow> cellRows = row.getExpandedCellRows(colName); |
| 125 | + double cellHeight = ROW_HEIGHT + cellRows.size() * ROW_HEIGHT; |
| 126 | + maxCellHeight = Math.max(maxCellHeight, cellHeight); |
| 127 | + } |
| 128 | + return maxCellHeight; |
| 129 | +} |
| 130 | +``` |
| 131 | + |
| 132 | +- [x] **Step 3: Update getHeight() to use variable row heights** |
| 133 | + |
| 134 | +The `calculateHeight()` method iterates rows and calls `calculateRowHeight(row)` for each, accumulating the total. |
| 135 | + |
| 136 | +- [x] **Step 4: Update getRowIndexAtY() and getRowY() for cumulative heights** |
| 137 | + |
| 138 | +Both methods iterate with `calculateRowHeight()` instead of dividing by fixed `ROW_HEIGHT`. |
| 139 | + |
| 140 | +- [x] **Step 5: Verify build and tests** |
| 141 | + |
| 142 | +Run: `./gradlew compileJava && ./gradlew test` |
| 143 | +Expected: BUILD SUCCESSFUL, all tests pass |
| 144 | + |
| 145 | +- [x] **Step 6: Commit** |
| 146 | + |
| 147 | +```bash |
| 148 | +git add src/main/java/org/fxt/freexmltoolkit/controls/v2/xmleditor/view/RepeatingElementsTable.java |
| 149 | +git commit -m "feat: add cell expansion state and variable row heights to RepeatingElementsTable" |
| 150 | +``` |
| 151 | + |
| 152 | +--- |
| 153 | + |
| 154 | +### Task 3: Render expanded cells and handle interaction in XmlCanvasView |
| 155 | + |
| 156 | +**Files:** |
| 157 | +- Modify: `src/main/java/org/fxt/freexmltoolkit/controls/v2/xmleditor/view/XmlCanvasView.java` |
| 158 | + |
| 159 | +- [x] **Step 1: Update renderInlineTable() for cumulative row positioning** |
| 160 | + |
| 161 | +Replace `dataY + r * ROW_HEIGHT` with cumulative `currentRowY` variable that uses `table.calculateRowHeight(row)`. |
| 162 | + |
| 163 | +- [x] **Step 2: Draw expand arrows for complex cells** |
| 164 | + |
| 165 | +Before drawing cell value, check `row.hasComplexChild(col.getName())`. Draw ▼ if expanded, ► if collapsed, using `gc.fillText()`. |
| 166 | + |
| 167 | +- [x] **Step 3: Render expanded cell sub-rows** |
| 168 | + |
| 169 | +After drawing cell summary, if `row.isColumnExpanded(colName)`: |
| 170 | +- Get `row.getExpandedCellRows(colName)` |
| 171 | +- Draw each FlatRow with: icon (`drawRowIcon()`), label (colored by type), value |
| 172 | +- Position below summary line at `subRowY` increments of `ROW_HEIGHT` |
| 173 | +- Apply depth-based indentation |
| 174 | + |
| 175 | +- [x] **Step 4: Handle click on cell expand arrow** |
| 176 | + |
| 177 | +In `handleTableClick()`, for single-clicks on data rows: |
| 178 | +- Check if click is within arrow zone (first 16px of complex cell) |
| 179 | +- If so, call `row.toggleColumnExpanded(col.getName())` |
| 180 | +- Trigger `recalculateVisibleRows()`, `updateScrollBars()`, `render()` |
| 181 | + |
| 182 | +- [x] **Step 5: Update getTableRowIndexAtScreenY() for variable heights** |
| 183 | + |
| 184 | +Iterate with cumulative `calculateRowHeight()` instead of fixed division. |
| 185 | + |
| 186 | +- [x] **Step 6: Update startEditingTableCell() for variable positioning** |
| 187 | + |
| 188 | +Calculate cell Y position with cumulative row heights for rows before the target. |
| 189 | + |
| 190 | +- [x] **Step 7: Verify build and tests** |
| 191 | + |
| 192 | +Run: `./gradlew compileJava && ./gradlew test` |
| 193 | +Expected: BUILD SUCCESSFUL, all tests pass |
| 194 | + |
| 195 | +- [x] **Step 8: Commit** |
| 196 | + |
| 197 | +```bash |
| 198 | +git add src/main/java/org/fxt/freexmltoolkit/controls/v2/xmleditor/view/XmlCanvasView.java |
| 199 | +git commit -m "feat: render expanded cells and handle expand interaction in grid view" |
| 200 | +``` |
0 commit comments