From 8a717cba5a8e269a3f1cc49d6fb6462cd28654b8 Mon Sep 17 00:00:00 2001 From: webdevred <148627186+webdevred@users.noreply.github.com> Date: Thu, 9 Apr 2026 18:02:55 +0200 Subject: [PATCH 1/3] Fix vertex loss in transformation for all-letter-ending node groups Two bugs caused vertices to be silently dropped when most node names end in letters (like burnside_frame where all structural nodes have suffixes r, l, rr, ll). Bug 1: insertTreeInMap in VertexExtraction used OMap1.snoc which replaces the first element when the key matches. For letter-ending nodes, every group gets SupportKey, so only the last group was kept. Fixed: check if key exists and merge vertices with (<>) on NonEmpty. Bug 2: addVertexTreeToForest used M.insert which replaced the supportForest entry when processing SupportTree vertices. Vertices moved to supportForest by moveSupportVertices (typically digit-ending nodes classified as support via high connectivity) were lost when the remaining SupportTree vertices were processed by addVertexTreeToForest. Fixed: use M.insertWith mergeOMap1Trees to merge OMap1 entries. --- .../JbeamEdit/Transformation.hs | 50 +++++++++++++------ .../Transformation/VertexExtraction.hs | 14 +++--- 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/src-extra/transformation/JbeamEdit/Transformation.hs b/src-extra/transformation/JbeamEdit/Transformation.hs index 98811a81..c5e08944 100644 --- a/src-extra/transformation/JbeamEdit/Transformation.hs +++ b/src-extra/transformation/JbeamEdit/Transformation.hs @@ -130,9 +130,30 @@ addVertexTreeToForest newNames tf grouped forest forestAcc t = . addPrefixComments t . fmap (sortVertices t newNames tf) $ groupByPrefix origTree groupsForT - in Right (M.insert t tree forestAcc) + in Right (M.insertWith mergeOMap1Trees t tree forestAcc) Nothing -> Right forestAcc +mergeOMap1Trees + :: OMap1 VertexTreeKey VertexTree + -> OMap1 VertexTreeKey VertexTree + -> OMap1 VertexTreeKey VertexTree +mergeOMap1Trees new existing = + foldl' go existing (OMap1.assocs new) + where + go acc (k, VertexTree newCmts newVerts) = + case OMap1.lookup k acc of + Nothing -> + fromList (OMap1.assocs acc <> [(k, VertexTree newCmts newVerts)]) + Just (VertexTree existCmts existVerts) -> + fromList $ + map + ( \(k', v') -> + if k' == k + then (k', VertexTree existCmts (existVerts <> newVerts)) + else (k', v') + ) + (OMap1.assocs acc) + groupAnnotatedVertices :: XGroupBreakpoints -> AnnotatedVertex @@ -218,22 +239,19 @@ moveVerticesInVertexForest topNode newNames tfCfg vertexTrees = vertexTrees brks = xGroupBreakpoints tfCfg in case mapM (groupAnnotatedVertices brks) allVertices of - Right movableVertices' -> do + Right movableVertices' -> let groupedVertices = M.fromListWith (++) movableVertices' - (badBeamNodes, conns) <- - vertexConns (maxSupportCoordinates tfCfg) topNode groupedVertices - let (supportForest, nonSupportVertices) = - moveSupportVertices - newNames - tfCfg - conns - groupedVertices - newForest <- - foldM - (addVertexTreeToForest newNames tfCfg nonSupportVertices vertexTrees) - supportForest - treesOrder - Right (badBeamNodes, newForest) + in do + (badBeamNodes, conns) <- + vertexConns (maxSupportCoordinates tfCfg) topNode groupedVertices + let (supportForest, nonSupportVertices) = + moveSupportVertices newNames tfCfg conns groupedVertices + newForest <- + foldM + (addVertexTreeToForest newNames tfCfg nonSupportVertices vertexTrees) + supportForest + treesOrder + Right (badBeamNodes, newForest) Left err -> Left err getVertexNamesInForest diff --git a/src-extra/transformation/JbeamEdit/Transformation/VertexExtraction.hs b/src-extra/transformation/JbeamEdit/Transformation/VertexExtraction.hs index a3298741..48bbbbd3 100644 --- a/src-extra/transformation/JbeamEdit/Transformation/VertexExtraction.hs +++ b/src-extra/transformation/JbeamEdit/Transformation/VertexExtraction.hs @@ -146,14 +146,14 @@ getVertexTreePrefix vt = maybe SupportKey PrefixKey (getVertexPrefix . anVertexN insertTreeInMap :: VertexTree -> OMap1 VertexTreeKey VertexTree -> OMap1 VertexTreeKey VertexTree -insertTreeInMap (VertexTree newComments newVertexGroups) = +insertTreeInMap (VertexTree newComments newVertexGroups) omap = let vType = getVertexTreePrefix newVertexGroups - vertexTree = - VertexTree - { tComments = newComments - , tAnnotatedVertices = newVertexGroups - } - in OMap1.snoc vType vertexTree + in case OMap1.lookup vType omap of + Nothing -> OMap1.snoc vType (VertexTree newComments newVertexGroups) omap + Just (VertexTree existingComments existingVertices) -> + let merged = VertexTree existingComments (existingVertices <> newVertexGroups) + in fromList $ + map (\(k, v) -> if k == vType then (k, merged) else (k, v)) (OMap1.assocs omap) isSupportVertex :: Vertex -> Bool isSupportVertex v = From 34432454edf310254aad1ad2d545d6078a20a206 Mon Sep 17 00:00:00 2001 From: webdevred <148627186+webdevred@users.noreply.github.com> Date: Thu, 9 Apr 2026 18:12:26 +0200 Subject: [PATCH 2/3] Add OMap1.insertWith and use it to eliminate duplicated merge logic --- .../transformation/JbeamEdit/Transformation.hs | 15 ++------------- .../JbeamEdit/Transformation/OMap1.hs | 11 +++++++++++ .../JbeamEdit/Transformation/VertexExtraction.hs | 8 ++------ 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src-extra/transformation/JbeamEdit/Transformation.hs b/src-extra/transformation/JbeamEdit/Transformation.hs index c5e08944..9aadf5ab 100644 --- a/src-extra/transformation/JbeamEdit/Transformation.hs +++ b/src-extra/transformation/JbeamEdit/Transformation.hs @@ -140,19 +140,8 @@ mergeOMap1Trees mergeOMap1Trees new existing = foldl' go existing (OMap1.assocs new) where - go acc (k, VertexTree newCmts newVerts) = - case OMap1.lookup k acc of - Nothing -> - fromList (OMap1.assocs acc <> [(k, VertexTree newCmts newVerts)]) - Just (VertexTree existCmts existVerts) -> - fromList $ - map - ( \(k', v') -> - if k' == k - then (k', VertexTree existCmts (existVerts <> newVerts)) - else (k', v') - ) - (OMap1.assocs acc) + merge (VertexTree _ newVerts) (VertexTree ec ev) = VertexTree ec (ev <> newVerts) + go acc (k, v) = OMap1.insertWith merge k v acc groupAnnotatedVertices :: XGroupBreakpoints diff --git a/src-extra/transformation/JbeamEdit/Transformation/OMap1.hs b/src-extra/transformation/JbeamEdit/Transformation/OMap1.hs index 14a2e98f..aa4aa587 100644 --- a/src-extra/transformation/JbeamEdit/Transformation/OMap1.hs +++ b/src-extra/transformation/JbeamEdit/Transformation/OMap1.hs @@ -7,6 +7,7 @@ module JbeamEdit.Transformation.OMap1 ( lookup, assocs, consOMap, + insertWith, singleton, head, snoc, @@ -74,6 +75,16 @@ uncons (OMap1 (firstK, firstV) rest) = Just newFirst@(newFirstK, _) -> (firstK, firstV, newFirst OMap.<| OMap.delete newFirstK rest) Nothing -> (firstK, firstV, OMap.empty) +insertWith :: Ord k => (v -> v -> v) -> k -> v -> OMap1 k v -> OMap1 k v +insertWith f k v omap = + case lookup k omap of + Nothing -> fromList (assocs omap <> [(k, v)]) + Just existing -> + fromList $ + map + (\(k', v') -> if k' == k then (k', f v existing) else (k', v')) + (assocs omap) + snoc :: Ord k => k -> v -> OMap1 k v -> OMap1 k v snoc newLastK newLastV (OMap1 oldFirst rest) | fst oldFirst == newLastK = OMap1 (newLastK, newLastV) rest diff --git a/src-extra/transformation/JbeamEdit/Transformation/VertexExtraction.hs b/src-extra/transformation/JbeamEdit/Transformation/VertexExtraction.hs index 48bbbbd3..38ae1dae 100644 --- a/src-extra/transformation/JbeamEdit/Transformation/VertexExtraction.hs +++ b/src-extra/transformation/JbeamEdit/Transformation/VertexExtraction.hs @@ -148,12 +148,8 @@ insertTreeInMap :: VertexTree -> OMap1 VertexTreeKey VertexTree -> OMap1 VertexTreeKey VertexTree insertTreeInMap (VertexTree newComments newVertexGroups) omap = let vType = getVertexTreePrefix newVertexGroups - in case OMap1.lookup vType omap of - Nothing -> OMap1.snoc vType (VertexTree newComments newVertexGroups) omap - Just (VertexTree existingComments existingVertices) -> - let merged = VertexTree existingComments (existingVertices <> newVertexGroups) - in fromList $ - map (\(k, v) -> if k == vType then (k, merged) else (k, v)) (OMap1.assocs omap) + merge _ (VertexTree ec ev) = VertexTree ec (ev <> newVertexGroups) + in OMap1.insertWith merge vType (VertexTree newComments newVertexGroups) omap isSupportVertex :: Vertex -> Bool isSupportVertex v = From cd6f358e387c57e767fc71600c0a1240e9cf85cd Mon Sep 17 00:00:00 2001 From: webdevred <148627186+webdevred@users.noreply.github.com> Date: Fri, 10 Apr 2026 18:43:30 +0200 Subject: [PATCH 3/3] Rename z-sorting-threshold to y-sorting-threshold The threshold controls when Y-coordinate differences (front/back) are significant enough to sort by, falling back to Z (height) within a band. The old name was a holdover from when the code sorted on Z instead of Y. Breaking change: existing .jbeam-edit.yaml configs using z-sorting-threshold must rename the key to y-sorting-threshold. Also renames the Haskell field zSortingThreshold to ySortingThreshold and the local variable zDiff to yDiff in compareAV. --- examples/jbeam-edit.yaml | 2 +- src-extra/transformation/JbeamEdit/Transformation.hs | 8 ++++---- .../transformation/JbeamEdit/Transformation/Config.hs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/jbeam-edit.yaml b/examples/jbeam-edit.yaml index e8c5a01d..906ae8ea 100644 --- a/examples/jbeam-edit.yaml +++ b/examples/jbeam-edit.yaml @@ -1,4 +1,4 @@ -z-sorting-threshold: 0.05 +y-sorting-threshold: 0.05 support-threshold: 96 max-support-coordinates: 3 diff --git a/src-extra/transformation/JbeamEdit/Transformation.hs b/src-extra/transformation/JbeamEdit/Transformation.hs index 9aadf5ab..34681f0e 100644 --- a/src-extra/transformation/JbeamEdit/Transformation.hs +++ b/src-extra/transformation/JbeamEdit/Transformation.hs @@ -179,7 +179,7 @@ moveSupportVertices newNames tfCfg connMap vsPerType = ] brks = xGroupBreakpoints tfCfg - thr = zSortingThreshold tfCfg + thr = ySortingThreshold tfCfg assignSupportNames = assignNames newNames brks SupportTree @@ -342,8 +342,8 @@ compareAV thr treeType vertex1 vertex2 = y2 = vY . aVertex $ vertex2 compareZ = comparing (vZ . aVertex) vertex1 vertex2 compareY = - let zDiff = abs $ y1 - y2 - in bool EQ (compare y1 y2) (zDiff > thr) + let yDiff = abs $ y1 - y2 + in bool EQ (compare y1 y2) (yDiff > thr) compareX = on compare (vX . aVertex) vertex1 vertex2 in mconcat [ supportNameCompare @@ -404,7 +404,7 @@ sortVertices -> VertexTree -> VertexTree sortVertices treeType newNames tfCfg (VertexTree comments vertices) = - let thr = zSortingThreshold tfCfg + let thr = ySortingThreshold tfCfg brks = xGroupBreakpoints tfCfg sortedGroups = NE.sortBy (compareAV thr treeType) vertices diff --git a/src-extra/transformation/JbeamEdit/Transformation/Config.hs b/src-extra/transformation/JbeamEdit/Transformation/Config.hs index db68c94f..1070f9b7 100644 --- a/src-extra/transformation/JbeamEdit/Transformation/Config.hs +++ b/src-extra/transformation/JbeamEdit/Transformation/Config.hs @@ -64,7 +64,7 @@ defaultBreakpoints = ] data TransformationConfig = TransformationConfig - { zSortingThreshold :: Scientific + { ySortingThreshold :: Scientific , xGroupBreakpoints :: XGroupBreakpoints , supportThreshold :: Double , maxSupportCoordinates :: Natural @@ -137,7 +137,7 @@ parseSupportThreshold o = do instance FromJSON TransformationConfig where parseJSON = withObject "TransformationConfig" $ \o -> TransformationConfig - <$> o .:? "z-sorting-threshold" .!= defaultSortingThreshold + <$> o .:? "y-sorting-threshold" .!= defaultSortingThreshold <*> o .:? "x-group-breakpoints" .!= defaultBreakpoints <*> parseSupportThreshold o <*> o .:? "max-support-coordinates" .!= defaultMaxSupportCoordinates