jsx_rosetta 0.4.0 → 0.6.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +342 -11
  3. data/CLAUDE.md +70 -0
  4. data/README.md +50 -0
  5. data/ROADMAP.md +92 -0
  6. data/agents/jsx-rosetta-resolve-todo-file.md +90 -0
  7. data/lib/jsx_rosetta/ast/inflector.rb +32 -0
  8. data/lib/jsx_rosetta/backend/phlex.rb +1421 -158
  9. data/lib/jsx_rosetta/backend/rails_view.rb +1 -1
  10. data/lib/jsx_rosetta/backend/view_component/expression_translator.rb +357 -33
  11. data/lib/jsx_rosetta/backend/view_component.rb +261 -31
  12. data/lib/jsx_rosetta/cli.rb +175 -37
  13. data/lib/jsx_rosetta/icons/lucide.json +37 -0
  14. data/lib/jsx_rosetta/icons.rb +44 -0
  15. data/lib/jsx_rosetta/ir/lowering.rb +1164 -70
  16. data/lib/jsx_rosetta/ir/module_shape_classifier.rb +20 -1
  17. data/lib/jsx_rosetta/ir/radix_registry.rb +84 -0
  18. data/lib/jsx_rosetta/ir/types.rb +264 -19
  19. data/lib/jsx_rosetta/ir.rb +5 -4
  20. data/lib/jsx_rosetta/pages_routing.rb +640 -0
  21. data/lib/jsx_rosetta/version.rb +1 -1
  22. data/lib/jsx_rosetta.rb +8 -6
  23. data/plans/nextjs_pages_to_rails.md +200 -0
  24. data/plans/nextjs_pages_to_rails_slice_2.md +118 -0
  25. data/plans/nextjs_pages_to_rails_slice_3.md +121 -0
  26. data/plans/nextjs_pages_to_rails_slice_4.md +301 -0
  27. data/plans/translator_widening_and_pages_followups.md +120 -0
  28. data/plans/translator_widening_slice_a.md +208 -0
  29. data/skills/jsx-rosetta-resolve-todos/SKILL.md +206 -0
  30. data/skills/jsx-rosetta-resolve-todos/data/design_tokens.template.yml +71 -0
  31. data/skills/jsx-rosetta-resolve-todos/data/target_app_conventions.template.yml +107 -0
  32. data/skills/jsx-rosetta-resolve-todos/examples/design_tokens.ant_design_v5.yml +190 -0
  33. data/skills/jsx-rosetta-resolve-todos/recipes/01_design_tokens.md +74 -0
  34. data/skills/jsx-rosetta-resolve-todos/recipes/02_promoted_ivar.md +49 -0
  35. data/skills/jsx-rosetta-resolve-todos/recipes/03_react_hooks.md +34 -0
  36. data/skills/jsx-rosetta-resolve-todos/recipes/04_apollo_hooks.md +34 -0
  37. data/skills/jsx-rosetta-resolve-todos/recipes/05_event_handlers.md +45 -0
  38. data/skills/jsx-rosetta-resolve-todos/recipes/06_module_constants.md +29 -0
  39. data/skills/jsx-rosetta-resolve-todos/recipes/07_nextjs_navigation.md +44 -0
  40. data/skills/jsx-rosetta-resolve-todos/recipes/08_generic_js_bailouts.md +55 -0
  41. data/skills/jsx-rosetta-resolve-todos/tools/apply_promoted_ivar.rb +189 -0
  42. data/skills/jsx-rosetta-resolve-todos/tools/apply_substitutions.rb +292 -0
  43. data/skills/jsx-rosetta-resolve-todos/tools/diff_corpus.rb +161 -0
  44. data/skills/jsx-rosetta-resolve-todos/tools/discover_bailouts.rb +211 -0
  45. metadata +30 -1
@@ -0,0 +1,37 @@
1
+ {
2
+ "AlertCircle": "<circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"12\" x2=\"12\" y1=\"8\" y2=\"12\"/><line x1=\"12\" x2=\"12.01\" y1=\"16\" y2=\"16\"/>",
3
+ "AlertTriangle": "<path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/>",
4
+ "ArrowDown": "<path d=\"M12 5v14\"/><path d=\"m19 12-7 7-7-7\"/>",
5
+ "ArrowLeft": "<path d=\"m12 19-7-7 7-7\"/><path d=\"M19 12H5\"/>",
6
+ "ArrowRight": "<path d=\"M5 12h14\"/><path d=\"m12 5 7 7-7 7\"/>",
7
+ "ArrowUp": "<path d=\"m5 12 7-7 7 7\"/><path d=\"M12 19V5\"/>",
8
+ "Bell": "<path d=\"M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9\"/><path d=\"M10.3 21a1.94 1.94 0 0 0 3.4 0\"/>",
9
+ "Calendar": "<rect width=\"18\" height=\"18\" x=\"3\" y=\"4\" rx=\"2\" ry=\"2\"/><line x1=\"16\" x2=\"16\" y1=\"2\" y2=\"6\"/><line x1=\"8\" x2=\"8\" y1=\"2\" y2=\"6\"/><line x1=\"3\" x2=\"21\" y1=\"10\" y2=\"10\"/>",
10
+ "Check": "<path d=\"M20 6 9 17l-5-5\"/>",
11
+ "ChevronDown": "<path d=\"m6 9 6 6 6-6\"/>",
12
+ "ChevronLeft": "<path d=\"m15 18-6-6 6-6\"/>",
13
+ "ChevronRight": "<path d=\"m9 18 6-6-6-6\"/>",
14
+ "ChevronUp": "<path d=\"m18 15-6-6-6 6\"/>",
15
+ "ChevronsUpDown": "<path d=\"m7 15 5 5 5-5\"/><path d=\"m7 9 5-5 5 5\"/>",
16
+ "Circle": "<circle cx=\"12\" cy=\"12\" r=\"10\"/>",
17
+ "Copy": "<rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/>",
18
+ "Dot": "<circle cx=\"12.1\" cy=\"12.1\" r=\"1\"/>",
19
+ "Eye": "<path d=\"M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/>",
20
+ "EyeOff": "<path d=\"M9.88 9.88a3 3 0 1 0 4.24 4.24\"/><path d=\"M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68\"/><path d=\"M6.61 6.61A13.526 13.526 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61\"/><line x1=\"2\" x2=\"22\" y1=\"2\" y2=\"22\"/>",
21
+ "GripVertical": "<circle cx=\"9\" cy=\"12\" r=\"1\"/><circle cx=\"9\" cy=\"5\" r=\"1\"/><circle cx=\"9\" cy=\"19\" r=\"1\"/><circle cx=\"15\" cy=\"12\" r=\"1\"/><circle cx=\"15\" cy=\"5\" r=\"1\"/><circle cx=\"15\" cy=\"19\" r=\"1\"/>",
22
+ "Info": "<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 16v-4\"/><path d=\"M12 8h.01\"/>",
23
+ "Loader2": "<path d=\"M21 12a9 9 0 1 1-6.219-8.56\"/>",
24
+ "Mail": "<rect width=\"20\" height=\"16\" x=\"2\" y=\"4\" rx=\"2\"/><path d=\"m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7\"/>",
25
+ "Menu": "<line x1=\"4\" x2=\"20\" y1=\"12\" y2=\"12\"/><line x1=\"4\" x2=\"20\" y1=\"6\" y2=\"6\"/><line x1=\"4\" x2=\"20\" y1=\"18\" y2=\"18\"/>",
26
+ "Minus": "<path d=\"M5 12h14\"/>",
27
+ "MoreHorizontal": "<circle cx=\"12\" cy=\"12\" r=\"1\"/><circle cx=\"19\" cy=\"12\" r=\"1\"/><circle cx=\"5\" cy=\"12\" r=\"1\"/>",
28
+ "MoreVertical": "<circle cx=\"12\" cy=\"12\" r=\"1\"/><circle cx=\"12\" cy=\"5\" r=\"1\"/><circle cx=\"12\" cy=\"19\" r=\"1\"/>",
29
+ "PanelLeft": "<rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M9 3v18\"/>",
30
+ "Plus": "<path d=\"M5 12h14\"/><path d=\"M12 5v14\"/>",
31
+ "Search": "<circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3\"/>",
32
+ "Settings": "<path d=\"M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/>",
33
+ "Star": "<polygon points=\"12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2\"/>",
34
+ "Trash2": "<path d=\"M3 6h18\"/><path d=\"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6\"/><path d=\"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2\"/><line x1=\"10\" x2=\"10\" y1=\"11\" y2=\"17\"/><line x1=\"14\" x2=\"14\" y1=\"11\" y2=\"17\"/>",
35
+ "User": "<path d=\"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2\"/><circle cx=\"12\" cy=\"7\" r=\"4\"/>",
36
+ "X": "<path d=\"M18 6 6 18\"/><path d=\"m6 6 12 12\"/>"
37
+ }
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module JsxRosetta
6
+ # Vendored SVG path data for the most common icons referenced by shadcn-shaped
7
+ # JSX. Backends look up icons here to emit standalone Phlex classes alongside
8
+ # the translated component .rb file, so consumers don't have to write icon
9
+ # shims by hand.
10
+ #
11
+ # Refresh `lib/jsx_rosetta/icons/lucide.json` from the upstream Lucide package
12
+ # if you need names that aren't here yet — but verify the path data, since
13
+ # Lucide occasionally tweaks icon shapes between releases.
14
+ module Icons
15
+ LUCIDE_DATA_PATH = File.expand_path("icons/lucide.json", __dir__)
16
+
17
+ # Source specifiers that import from a Lucide-shaped icon package.
18
+ # Includes both the React-flavored `lucide-react` and the bare `lucide`
19
+ # package (some shadcn forks use it directly).
20
+ LUCIDE_SOURCE_PATTERN = /\Alucide(-react)?\z/
21
+
22
+ def self.lucide_data
23
+ @lucide_data ||= JSON.parse(File.read(LUCIDE_DATA_PATH))
24
+ end
25
+
26
+ # Look up the inner-SVG for a Lucide icon. Tolerates both canonical
27
+ # (`ChevronRight`) and legacy `*Icon` (`ChevronRightIcon`) names, since
28
+ # shadcn varies which it imports across components. Returns nil for
29
+ # the degenerate name `"Icon"` (and any empty result after stripping)
30
+ # so callers don't get a misleading data[""] miss.
31
+ def self.lucide_for(name)
32
+ name = name.to_s
33
+ return nil if name.empty? || name == "Icon"
34
+
35
+ data = lucide_data
36
+ data[name] || data[name.sub(/Icon\z/, "")]
37
+ end
38
+
39
+ # True iff the given `IR::ModuleImport#source` is a known Lucide package.
40
+ def self.lucide_source?(source)
41
+ LUCIDE_SOURCE_PATTERN.match?(source.to_s)
42
+ end
43
+ end
44
+ end