wyreframe 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/README.md +123 -0
  2. package/dist/index.d.ts +267 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +195 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +63 -0
  7. package/src/parser/Core/Bounds.mjs +61 -0
  8. package/src/parser/Core/Bounds.res +65 -0
  9. package/src/parser/Core/Grid.mjs +268 -0
  10. package/src/parser/Core/Grid.res +265 -0
  11. package/src/parser/Core/Position.mjs +83 -0
  12. package/src/parser/Core/Position.res +54 -0
  13. package/src/parser/Core/Types.mjs +435 -0
  14. package/src/parser/Core/Types.res +331 -0
  15. package/src/parser/Core/__tests__/Bounds_test.mjs +326 -0
  16. package/src/parser/Core/__tests__/Bounds_test.res +412 -0
  17. package/src/parser/Core/__tests__/Grid_test.mjs +322 -0
  18. package/src/parser/Core/__tests__/Grid_test.res +319 -0
  19. package/src/parser/Core/__tests__/Types_test.mjs +614 -0
  20. package/src/parser/Core/__tests__/Types_test.res +650 -0
  21. package/src/parser/Detector/BoxTracer.mjs +302 -0
  22. package/src/parser/Detector/BoxTracer.res +374 -0
  23. package/src/parser/Detector/HierarchyBuilder.mjs +158 -0
  24. package/src/parser/Detector/HierarchyBuilder.res +315 -0
  25. package/src/parser/Detector/ShapeDetector.mjs +134 -0
  26. package/src/parser/Detector/ShapeDetector.res +236 -0
  27. package/src/parser/Detector/__tests__/BoxTracer_test.mjs +70 -0
  28. package/src/parser/Detector/__tests__/BoxTracer_test.res +92 -0
  29. package/src/parser/Detector/__tests__/HierarchyBuilder_test.mjs +489 -0
  30. package/src/parser/Detector/__tests__/HierarchyBuilder_test.res +849 -0
  31. package/src/parser/Detector/__tests__/ShapeDetector_test.mjs +377 -0
  32. package/src/parser/Detector/__tests__/ShapeDetector_test.res +563 -0
  33. package/src/parser/Errors/ErrorContext.mjs +106 -0
  34. package/src/parser/Errors/ErrorContext.res +191 -0
  35. package/src/parser/Errors/ErrorMessages.mjs +289 -0
  36. package/src/parser/Errors/ErrorMessages.res +303 -0
  37. package/src/parser/Errors/ErrorTypes.mjs +105 -0
  38. package/src/parser/Errors/ErrorTypes.res +169 -0
  39. package/src/parser/Interactions/InteractionMerger.mjs +266 -0
  40. package/src/parser/Interactions/InteractionMerger.res +450 -0
  41. package/src/parser/Interactions/InteractionParser.mjs +88 -0
  42. package/src/parser/Interactions/InteractionParser.res +127 -0
  43. package/src/parser/Interactions/SimpleInteractionParser.mjs +278 -0
  44. package/src/parser/Interactions/SimpleInteractionParser.res +262 -0
  45. package/src/parser/Interactions/__tests__/InteractionMerger_test.mjs +576 -0
  46. package/src/parser/Interactions/__tests__/InteractionMerger_test.res +646 -0
  47. package/src/parser/Parser.gen.tsx +96 -0
  48. package/src/parser/Parser.mjs +212 -0
  49. package/src/parser/Parser.res +481 -0
  50. package/src/parser/Scanner/__tests__/Grid_manual.mjs +214 -0
  51. package/src/parser/Scanner/__tests__/Grid_manual.res +141 -0
  52. package/src/parser/Semantic/ASTBuilder.mjs +197 -0
  53. package/src/parser/Semantic/ASTBuilder.res +288 -0
  54. package/src/parser/Semantic/AlignmentCalc.mjs +41 -0
  55. package/src/parser/Semantic/AlignmentCalc.res +104 -0
  56. package/src/parser/Semantic/Elements/ButtonParser.mjs +58 -0
  57. package/src/parser/Semantic/Elements/ButtonParser.res +131 -0
  58. package/src/parser/Semantic/Elements/CheckboxParser.mjs +58 -0
  59. package/src/parser/Semantic/Elements/CheckboxParser.res +79 -0
  60. package/src/parser/Semantic/Elements/CodeTextParser.mjs +50 -0
  61. package/src/parser/Semantic/Elements/CodeTextParser.res +111 -0
  62. package/src/parser/Semantic/Elements/ElementParser.mjs +15 -0
  63. package/src/parser/Semantic/Elements/ElementParser.res +83 -0
  64. package/src/parser/Semantic/Elements/EmphasisParser.mjs +46 -0
  65. package/src/parser/Semantic/Elements/EmphasisParser.res +67 -0
  66. package/src/parser/Semantic/Elements/InputParser.mjs +41 -0
  67. package/src/parser/Semantic/Elements/InputParser.res +97 -0
  68. package/src/parser/Semantic/Elements/LinkParser.mjs +60 -0
  69. package/src/parser/Semantic/Elements/LinkParser.res +156 -0
  70. package/src/parser/Semantic/Elements/TextParser.mjs +19 -0
  71. package/src/parser/Semantic/Elements/TextParser.res +42 -0
  72. package/src/parser/Semantic/Elements/__tests__/ButtonParser_test.mjs +189 -0
  73. package/src/parser/Semantic/Elements/__tests__/ButtonParser_test.res +257 -0
  74. package/src/parser/Semantic/Elements/__tests__/CheckboxParser_test.mjs +202 -0
  75. package/src/parser/Semantic/Elements/__tests__/CheckboxParser_test.res +250 -0
  76. package/src/parser/Semantic/Elements/__tests__/CodeTextParser_manual.mjs +293 -0
  77. package/src/parser/Semantic/Elements/__tests__/CodeTextParser_manual.res +134 -0
  78. package/src/parser/Semantic/Elements/__tests__/InputParser_test.mjs +253 -0
  79. package/src/parser/Semantic/Elements/__tests__/InputParser_test.res +304 -0
  80. package/src/parser/Semantic/Elements/__tests__/LinkParser_test.mjs +289 -0
  81. package/src/parser/Semantic/Elements/__tests__/LinkParser_test.res +402 -0
  82. package/src/parser/Semantic/Elements/__tests__/TextParser_test.mjs +149 -0
  83. package/src/parser/Semantic/Elements/__tests__/TextParser_test.res +167 -0
  84. package/src/parser/Semantic/ParserRegistry.mjs +82 -0
  85. package/src/parser/Semantic/ParserRegistry.res +145 -0
  86. package/src/parser/Semantic/SemanticParser.mjs +850 -0
  87. package/src/parser/Semantic/SemanticParser.res +1368 -0
  88. package/src/parser/Semantic/__tests__/ASTBuilder_test.mjs +187 -0
  89. package/src/parser/Semantic/__tests__/ASTBuilder_test.res +192 -0
  90. package/src/parser/Semantic/__tests__/ParserRegistry_test.mjs +154 -0
  91. package/src/parser/Semantic/__tests__/ParserRegistry_test.res +191 -0
  92. package/src/parser/Semantic/__tests__/SemanticParser_integration_test.mjs +768 -0
  93. package/src/parser/Semantic/__tests__/SemanticParser_integration_test.res +1069 -0
  94. package/src/parser/Semantic/__tests__/SemanticParser_manual.mjs +1329 -0
  95. package/src/parser/Semantic/__tests__/SemanticParser_manual.res +544 -0
  96. package/src/parser/TestMain.mjs +21 -0
  97. package/src/parser/TestMain.res +14 -0
  98. package/src/parser/TextExtractor.mjs +179 -0
  99. package/src/parser/TextExtractor.res +264 -0
  100. package/src/parser/__tests__/GridScanner_integration.test.mjs +632 -0
  101. package/src/parser/__tests__/GridScanner_integration.test.res +816 -0
  102. package/src/parser/__tests__/Performance.test.mjs +244 -0
  103. package/src/parser/__tests__/Performance.test.res +371 -0
  104. package/src/parser/__tests__/PerformanceFixtures.mjs +200 -0
  105. package/src/parser/__tests__/PerformanceFixtures.res +284 -0
  106. package/src/parser/__tests__/WyreframeParser_integration.test.mjs +770 -0
  107. package/src/parser/__tests__/WyreframeParser_integration.test.res +1008 -0
  108. package/src/parser/__tests__/fixtures/alignment-test.txt +9 -0
  109. package/src/parser/__tests__/fixtures/all-elements.txt +16 -0
  110. package/src/parser/__tests__/fixtures/login-scene.txt +17 -0
  111. package/src/parser/__tests__/fixtures/multi-scene.txt +25 -0
  112. package/src/parser/__tests__/fixtures/nested-boxes.txt +15 -0
  113. package/src/parser/__tests__/fixtures/simple-box.txt +5 -0
  114. package/src/parser/__tests__/fixtures/with-dividers.txt +14 -0
  115. package/src/renderer/Renderer.gen.tsx +32 -0
  116. package/src/renderer/Renderer.mjs +391 -0
  117. package/src/renderer/Renderer.res +558 -0
@@ -0,0 +1,236 @@
1
+ // ShapeDetector.res
2
+ // Main Shape Detector Module - Stage 2 of the parser pipeline
3
+ //
4
+ // Integrates BoxTracer, DividerDetector, and HierarchyBuilder to:
5
+ // 1. Find all boxes in the grid
6
+ // 2. Detect dividers within boxes
7
+ // 3. Build nesting hierarchy
8
+ // 4. Collect all errors without early stopping
9
+ //
10
+ // Requirements:
11
+ // - REQ-28: Reliability - Error Recovery (collect all errors)
12
+
13
+ open Types
14
+
15
+ // ============================================================================
16
+ // Type Definitions
17
+ // ============================================================================
18
+
19
+ /**
20
+ * Result type for shape detection.
21
+ * Returns either:
22
+ * - Ok(boxes): Array of root-level boxes with nested hierarchy
23
+ * - Error(errors): Array of all errors encountered during detection
24
+ */
25
+ type detectResult = result<array<BoxTracer.box>, array<ErrorTypes.t>>
26
+
27
+ // ============================================================================
28
+ // Helper Functions
29
+ // ============================================================================
30
+
31
+ /**
32
+ * Convert HierarchyBuilder.hierarchyError to ErrorTypes.t
33
+ */
34
+ let hierarchyErrorToParseError = (error: HierarchyBuilder.hierarchyError): ErrorTypes.t => {
35
+ switch error {
36
+ | OverlappingBoxes({box1, box2}) =>
37
+ ErrorTypes.make(
38
+ InvalidElement({
39
+ content: `Overlapping boxes detected at (${Int.toString(box1.bounds.top)},${Int.toString(
40
+ box1.bounds.left,
41
+ )}) and (${Int.toString(box2.bounds.top)},${Int.toString(box2.bounds.left)})`,
42
+ position: {row: box1.bounds.top, col: box1.bounds.left},
43
+ }),
44
+ None,
45
+ )
46
+ | CircularNesting =>
47
+ ErrorTypes.make(
48
+ InvalidElement({
49
+ content: "Circular nesting detected in box hierarchy",
50
+ position: {row: 0, col: 0},
51
+ }),
52
+ None,
53
+ )
54
+ }
55
+ }
56
+
57
+ // ============================================================================
58
+ // Helper Functions (must be defined before detect)
59
+ // ============================================================================
60
+
61
+ /**
62
+ * Remove duplicate boxes that have the same bounds.
63
+ *
64
+ * This handles the case where multiple corners might trace the same box.
65
+ * We keep only the first occurrence of each unique box.
66
+ *
67
+ * @param boxes - Array of traced boxes
68
+ * @return Array of unique boxes (deduplicated by bounds)
69
+ */
70
+ let deduplicateBoxes = (boxes: array<BoxTracer.box>): array<BoxTracer.box> => {
71
+ let seen = []
72
+ let unique = []
73
+
74
+ boxes->Array.forEach(box => {
75
+ // Check if we've seen a box with these bounds before
76
+ let isDuplicate = seen->Array.some(seenBounds => {
77
+ Bounds.equals(seenBounds, box.bounds)
78
+ })
79
+
80
+ if !isDuplicate {
81
+ // New unique box
82
+ unique->Array.push(box)
83
+ seen->Array.push(box.bounds)
84
+ }
85
+ })
86
+
87
+ unique
88
+ }
89
+
90
+ // ============================================================================
91
+ // Main Detection Function
92
+ // ============================================================================
93
+
94
+ /**
95
+ * Detect all shapes (boxes, dividers) in the grid and build hierarchy.
96
+ *
97
+ * Algorithm:
98
+ * 1. Find all '+' corner positions using grid.cornerIndex
99
+ * 2. For each corner, attempt to trace a box using BoxTracer
100
+ * 3. Collect all successfully traced boxes
101
+ * 4. For each box, detect dividers within its bounds
102
+ * 5. Build parent-child hierarchy using HierarchyBuilder
103
+ * 6. Return Result with boxes or errors
104
+ *
105
+ * IMPORTANT: Not all '+' corners are top-left corners of boxes.
106
+ * A '+' can be a top-right, bottom-left, bottom-right corner, or
107
+ * part of nested box structure. traceBox failures for non-top-left
108
+ * corners are EXPECTED and should NOT be treated as errors.
109
+ *
110
+ * @param grid - The 2D character grid from GridScanner
111
+ * @return detectResult - Ok(root boxes) or Error(all errors)
112
+ *
113
+ * Requirements: REQ-28 (Error Recovery - no early stopping)
114
+ */
115
+ let detect = (grid: Grid.t): detectResult => {
116
+ // Step 1: Find all '+' corner positions using pre-built index
117
+ let corners = grid.cornerIndex
118
+
119
+ // Step 2: Trace boxes from each corner
120
+ // Note: Most corners will NOT be valid top-left corners, which is expected.
121
+ // Only corners that are actually top-left of a box will trace successfully.
122
+ let boxes = []
123
+ let traceFailures = [] // Keep track of failures
124
+
125
+ corners->Array.forEach(corner => {
126
+ switch BoxTracer.traceBox(grid, corner) {
127
+ | Ok(box) => {
128
+ // Successfully traced a box - add to collection
129
+ boxes->Array.push(box)
130
+ }
131
+ | Error(traceError) => {
132
+ // This corner failed to trace as a top-left corner.
133
+ // Could be a non-top-left corner (expected) or a malformed box (error).
134
+ traceFailures->Array.push(traceError)
135
+ }
136
+ }
137
+ })
138
+
139
+ // Step 3: Check if we found any boxes
140
+ if Array.length(boxes) === 0 {
141
+ // No boxes traced successfully
142
+ if Array.length(corners) === 0 {
143
+ // No corners at all - truly empty wireframe
144
+ Ok([])
145
+ } else if Array.length(traceFailures) > 0 {
146
+ // Had corners but all traces failed - this indicates malformed boxes
147
+ // Return all trace failures as errors
148
+ Error(traceFailures)
149
+ } else {
150
+ // No corners and no failures - empty result
151
+ Ok([])
152
+ }
153
+ } else {
154
+ // Step 4: Detect dividers for each successfully traced box
155
+ // TODO: Implement when DividerDetector module is available
156
+
157
+ // Step 5: Remove duplicate boxes (same bounds traced multiple times)
158
+ let uniqueBoxes = deduplicateBoxes(boxes)
159
+
160
+ // Step 6: Build hierarchy using HierarchyBuilder
161
+ switch HierarchyBuilder.buildHierarchy(uniqueBoxes) {
162
+ | Ok(rootBoxes) => {
163
+ // Hierarchy built successfully - return the boxes
164
+ Ok(rootBoxes)
165
+ }
166
+ | Error(hierarchyError) => {
167
+ // Hierarchy building failed (e.g., overlapping boxes)
168
+ // This IS a real error that should be reported
169
+ let parseError = hierarchyErrorToParseError(hierarchyError)
170
+ Error([parseError])
171
+ }
172
+ }
173
+ }
174
+ }
175
+
176
+ // ============================================================================
177
+ // Additional Helper Functions
178
+ // ============================================================================
179
+
180
+ /**
181
+ * Count total number of boxes including nested children.
182
+ *
183
+ * @param boxes - Array of boxes (can include nested children)
184
+ * @return Total count of all boxes
185
+ */
186
+ let rec countBoxes = (boxes: array<BoxTracer.box>): int => {
187
+ boxes->Array.reduce(0, (acc, box) => {
188
+ 1 + acc + countBoxes(box.children)
189
+ })
190
+ }
191
+
192
+ /**
193
+ * Get all boxes as a flat array (including nested children).
194
+ *
195
+ * @param boxes - Array of root boxes
196
+ * @return Flat array of all boxes
197
+ */
198
+ let rec flattenBoxes = (boxes: array<BoxTracer.box>): array<BoxTracer.box> => {
199
+ boxes->Array.reduce([], (acc, box) => {
200
+ let childBoxes = flattenBoxes(box.children)
201
+ Array.concat(Array.concat(acc, [box]), childBoxes)
202
+ })
203
+ }
204
+
205
+ /**
206
+ * Get statistics about detected shapes for debugging.
207
+ *
208
+ * @param result - Detection result
209
+ * @return String with statistics
210
+ */
211
+ let getStats = (result: detectResult): string => {
212
+ switch result {
213
+ | Ok(boxes) => {
214
+ let rootCount = Array.length(boxes)
215
+ let totalCount = countBoxes(boxes)
216
+ `Shape Detection Success:
217
+ Root boxes: ${Int.toString(rootCount)}
218
+ Total boxes (including nested): ${Int.toString(totalCount)}`
219
+ }
220
+ | Error(errors) => {
221
+ let errorCount = Array.length(errors)
222
+ let warningCount = errors->Array.reduce(0, (acc, err) => {
223
+ if ErrorTypes.isWarning(err) {
224
+ acc + 1
225
+ } else {
226
+ acc
227
+ }
228
+ })
229
+ let realErrorCount = errorCount - warningCount
230
+
231
+ `Shape Detection Failed:
232
+ Errors: ${Int.toString(realErrorCount)}
233
+ Warnings: ${Int.toString(warningCount)}`
234
+ }
235
+ }
236
+ }
@@ -0,0 +1,70 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Grid from "../../Core/Grid.mjs";
4
+ import * as Types from "../../Core/Types.mjs";
5
+ import * as Vitest from "rescript-vitest/src/Vitest.mjs";
6
+ import * as BoxTracer from "../BoxTracer.mjs";
7
+
8
+ Vitest.describe("BoxTracer - Width Validation", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, () => {
9
+ Vitest.test("should detect mismatched width between top and bottom edges", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => {
10
+ let input = [
11
+ "+-----+",
12
+ "| |",
13
+ "+-------+"
14
+ ];
15
+ let grid = Grid.fromLines(input);
16
+ let topLeft = Types.Position.make(0, 0);
17
+ let result = BoxTracer.traceBox(grid, topLeft);
18
+ if (result.TAG === "Ok") {
19
+ return t.expect(true).toBe(false);
20
+ }
21
+ let match = result._0.code;
22
+ if (match.TAG !== "MismatchedWidth") {
23
+ return t.expect(true).toBe(false);
24
+ }
25
+ let pos = match.topLeft;
26
+ t.expect(match.topWidth).toBe(6);
27
+ t.expect(match.bottomWidth).toBe(8);
28
+ t.expect(pos.row).toBe(0);
29
+ t.expect(pos.col).toBe(0);
30
+ });
31
+ Vitest.test("should successfully trace box with matching widths", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => {
32
+ let input = [
33
+ "+-----+",
34
+ "| |",
35
+ "+-----+"
36
+ ];
37
+ let grid = Grid.fromLines(input);
38
+ let topLeft = Types.Position.make(0, 0);
39
+ let result = BoxTracer.traceBox(grid, topLeft);
40
+ if (result.TAG === "Ok") {
41
+ let box = result._0;
42
+ t.expect(box.bounds.top).toBe(0);
43
+ t.expect(box.bounds.left).toBe(0);
44
+ t.expect(box.bounds.bottom).toBe(2);
45
+ t.expect(box.bounds.right).toBe(6);
46
+ return t.expect(box.name).toBe(undefined);
47
+ }
48
+ console.log(result._0);
49
+ t.expect(true).toBe(false);
50
+ });
51
+ Vitest.test("should extract box name and validate width", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => {
52
+ let input = [
53
+ "+--Login--+",
54
+ "| |",
55
+ "+--Login--+"
56
+ ];
57
+ let grid = Grid.fromLines(input);
58
+ let topLeft = Types.Position.make(0, 0);
59
+ let result = BoxTracer.traceBox(grid, topLeft);
60
+ if (result.TAG !== "Ok") {
61
+ return t.expect(true).toBe(false);
62
+ }
63
+ let box = result._0;
64
+ t.expect(box.name).toBe("Login");
65
+ t.expect(Types.Bounds.width(box.bounds)).toBe(10);
66
+ t.expect(Types.Bounds.height(box.bounds)).toBe(2);
67
+ });
68
+ });
69
+
70
+ /* Not a pure module */
@@ -0,0 +1,92 @@
1
+ // BoxTracer_test.res
2
+ // Unit tests for BoxTracer width validation and box tracing
3
+
4
+ open Vitest
5
+
6
+ describe("BoxTracer - Width Validation", () => {
7
+ test("should detect mismatched width between top and bottom edges", t => {
8
+ // Create a malformed box with different top and bottom widths
9
+ let input = [
10
+ "+-----+", // Top: 7 chars wide (5 dashes + 2 corners)
11
+ "| |",
12
+ "+-------+", // Bottom: 9 chars wide (7 dashes + 2 corners) - MISMATCH!
13
+ ]
14
+
15
+ let grid = Grid.fromLines(input)
16
+ let topLeft = Types.Position.make(0, 0)
17
+
18
+ let result = BoxTracer.traceBox(grid, topLeft)
19
+
20
+ switch result {
21
+ | Error(err) => {
22
+ // Verify we get a MismatchedWidth error
23
+ switch err.code {
24
+ | ErrorTypes.MismatchedWidth({topWidth, bottomWidth, topLeft: pos}) => {
25
+ t->expect(topWidth)->Expect.toBe(6) // 0 to 6 = 6 chars
26
+ t->expect(bottomWidth)->Expect.toBe(8) // 0 to 8 = 8 chars
27
+ t->expect(pos.row)->Expect.toBe(0)
28
+ t->expect(pos.col)->Expect.toBe(0)
29
+ }
30
+ | _ => t->expect(true)->Expect.toBe(false) // fail: Expected MismatchedWidth error
31
+ }
32
+ }
33
+ | Ok(_) => t->expect(true)->Expect.toBe(false) // fail: Expected error for mismatched width, but got Ok
34
+ }
35
+ })
36
+
37
+ test("should successfully trace box with matching widths", t => {
38
+ // Create a well-formed box with matching top and bottom widths
39
+ let input = [
40
+ "+-----+",
41
+ "| |",
42
+ "+-----+", // Same width as top
43
+ ]
44
+
45
+ let grid = Grid.fromLines(input)
46
+ let topLeft = Types.Position.make(0, 0)
47
+
48
+ let result = BoxTracer.traceBox(grid, topLeft)
49
+
50
+ switch result {
51
+ | Ok(box) => {
52
+ // Verify box bounds are correct
53
+ t->expect(box.bounds.top)->Expect.toBe(0)
54
+ t->expect(box.bounds.left)->Expect.toBe(0)
55
+ t->expect(box.bounds.bottom)->Expect.toBe(2)
56
+ t->expect(box.bounds.right)->Expect.toBe(6)
57
+
58
+ // Verify no name (unnamed box)
59
+ t->expect(box.name)->Expect.toBe(None)
60
+ }
61
+ | Error(err) => {
62
+ Console.log(err)
63
+ t->expect(true)->Expect.toBe(false) // fail: Expected successful box trace for matching widths
64
+ }
65
+ }
66
+ })
67
+
68
+ test("should extract box name and validate width", t => {
69
+ let input = [
70
+ "+--Login--+",
71
+ "| |",
72
+ "+--Login--+",
73
+ ]
74
+
75
+ let grid = Grid.fromLines(input)
76
+ let topLeft = Types.Position.make(0, 0)
77
+
78
+ let result = BoxTracer.traceBox(grid, topLeft)
79
+
80
+ switch result {
81
+ | Ok(box) => {
82
+ // Verify box name extraction
83
+ t->expect(box.name)->Expect.toBe(Some("Login"))
84
+
85
+ // Verify bounds
86
+ t->expect(Types.Bounds.width(box.bounds))->Expect.toBe(10)
87
+ t->expect(Types.Bounds.height(box.bounds))->Expect.toBe(2)
88
+ }
89
+ | Error(_) => t->expect(true)->Expect.toBe(false) // fail: Expected successful trace for named box
90
+ }
91
+ })
92
+ })