turbometro 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 (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +80 -0
  3. package/bin/turbometro.js +2 -0
  4. package/dist/cli.d.ts +1 -0
  5. package/dist/cli.js +107 -0
  6. package/dist/find-root.d.ts +2 -0
  7. package/dist/find-root.js +14 -0
  8. package/dist/graph.d.ts +14 -0
  9. package/dist/graph.js +74 -0
  10. package/dist/layout.d.ts +19 -0
  11. package/dist/layout.js +15 -0
  12. package/dist/parse-turbo.d.ts +5 -0
  13. package/dist/parse-turbo.js +29 -0
  14. package/dist/parse-workspace.d.ts +4 -0
  15. package/dist/parse-workspace.js +100 -0
  16. package/dist/render-html.d.ts +8 -0
  17. package/dist/render-html.js +699 -0
  18. package/dist/render-map.d.ts +29 -0
  19. package/dist/render-map.js +287 -0
  20. package/dist/replay.d.ts +9 -0
  21. package/dist/replay.js +74 -0
  22. package/dist/scale.d.ts +7 -0
  23. package/dist/scale.js +19 -0
  24. package/dist/scene-data.d.ts +40 -0
  25. package/dist/scene-data.js +146 -0
  26. package/dist/theme.d.ts +15 -0
  27. package/dist/theme.js +15 -0
  28. package/dist/types.d.ts +30 -0
  29. package/dist/types.js +1 -0
  30. package/fixtures/mini-mono/apps/web/package.json +11 -0
  31. package/fixtures/mini-mono/package.json +5 -0
  32. package/fixtures/mini-mono/packages/ui/package.json +11 -0
  33. package/fixtures/mini-mono/packages/utils/package.json +8 -0
  34. package/fixtures/mini-mono/pnpm-workspace.yaml +3 -0
  35. package/fixtures/mini-mono/turbo.json +10 -0
  36. package/package.json +44 -0
  37. package/vendor/dag-map/LICENSE +201 -0
  38. package/vendor/dag-map/NOTICE +6 -0
  39. package/vendor/dag-map/src/color-scales.js +61 -0
  40. package/vendor/dag-map/src/dag-map.css +63 -0
  41. package/vendor/dag-map/src/events.js +108 -0
  42. package/vendor/dag-map/src/graph-utils.js +185 -0
  43. package/vendor/dag-map/src/hasse.css +61 -0
  44. package/vendor/dag-map/src/index.js +55 -0
  45. package/vendor/dag-map/src/layout-flow.js +1066 -0
  46. package/vendor/dag-map/src/layout-hasse.js +485 -0
  47. package/vendor/dag-map/src/layout-metro.js +542 -0
  48. package/vendor/dag-map/src/occupancy.js +138 -0
  49. package/vendor/dag-map/src/render-flow-station.js +132 -0
  50. package/vendor/dag-map/src/render.js +360 -0
  51. package/vendor/dag-map/src/route-angular.js +137 -0
  52. package/vendor/dag-map/src/route-bezier.js +51 -0
  53. package/vendor/dag-map/src/route-metro.js +122 -0
  54. package/vendor/dag-map/src/themes.js +49 -0
@@ -0,0 +1,122 @@
1
+ // ================================================================
2
+ // route-metro.js — Metro-style right-angle routing with rounded elbows
3
+ // ================================================================
4
+ // Produces clean H-V-H or V-H-V paths with quadratic bezier corners.
5
+ // Designed for parallel-line layouts where visual clarity is paramount.
6
+ //
7
+ // For LTR layouts: horizontal → rounded elbow → vertical → rounded elbow → horizontal
8
+ // For TTB layouts: vertical → rounded elbow → horizontal → rounded elbow → vertical
9
+
10
+ /**
11
+ * Rounded elbow path between two points using right-angle bends.
12
+ *
13
+ * @param {number} px - source x
14
+ * @param {number} py - source y
15
+ * @param {number} qx - destination x
16
+ * @param {number} qy - destination y
17
+ * @param {number} routeIdx - route index (for deterministic midpoint variation)
18
+ * @param {number} segIdx - segment index within route
19
+ * @param {number} refY - reference Y (not used, kept for API compat)
20
+ * @param {object} [options]
21
+ * @param {number} [options.cornerRadius=8] - corner radius in px (before scale)
22
+ * @param {'h-first'|'v-first'|'auto'} [options.bendStyle='auto'] - which axis to run first
23
+ * @returns {string} SVG path data (without leading M)
24
+ */
25
+ export function metroPath(px, py, qx, qy, routeIdx, segIdx, refY, options = {}) {
26
+ const cornerRadius = options.cornerRadius ?? 8;
27
+ const bendStyle = options.bendStyle ?? 'auto';
28
+ const dx = qx - px, dy = qy - py;
29
+
30
+ // Straight horizontal or vertical — no bend needed
31
+ if (Math.abs(dy) < 1) return `L ${qx} ${qy}`;
32
+ if (Math.abs(dx) < 1) return `L ${qx} ${qy}`;
33
+
34
+ // Clamp radius so it doesn't exceed available space
35
+ const r = Math.min(cornerRadius, Math.abs(dx) / 2, Math.abs(dy) / 2);
36
+
37
+ if (r < 1) return `L ${qx} ${qy}`; // too small for rounded corners
38
+
39
+ // Determine bend direction
40
+ const useHFirst = bendStyle === 'h-first' ||
41
+ (bendStyle === 'auto' && Math.abs(dx) >= Math.abs(dy));
42
+
43
+ if (useHFirst) {
44
+ // H-V path: horizontal run → rounded corner → vertical run
45
+ const midFrac = options.midFrac ?? (0.45 + (((routeIdx * 7 + segIdx * 13) % 17) / 17) * 0.10);
46
+ const midX = px + dx * midFrac;
47
+
48
+ return hvPath(px, py, midX, qx, qy, r);
49
+ } else {
50
+ // V-H path: vertical run → rounded corner → horizontal run
51
+ const midFrac = options.midFrac ?? (0.45 + (((routeIdx * 7 + segIdx * 13) % 17) / 17) * 0.10);
52
+ const midY = py + dy * midFrac;
53
+
54
+ return vhPath(px, py, midY, qx, qy, r);
55
+ }
56
+ }
57
+
58
+ /**
59
+ * H-V-H path: horizontal → corner → vertical → corner → horizontal
60
+ */
61
+ function hvPath(px, py, midX, qx, qy, r) {
62
+ const dy = qy - py;
63
+ const sy = Math.sign(dy); // +1 down, -1 up
64
+
65
+ // First elbow: at (midX, py) turning toward qy
66
+ const e1x = midX - r; // approach point
67
+ const e1cx = midX; // corner control point
68
+ const e1cy = py;
69
+ const e1ex = midX; // exit point
70
+ const e1ey = py + sy * r;
71
+
72
+ // Second elbow: at (midX, qy) turning toward qx
73
+ const e2x = midX; // approach point
74
+ const e2y = qy - sy * r;
75
+ const e2cx = midX; // corner control point
76
+ const e2cy = qy;
77
+ const e2ex = midX + r * Math.sign(qx - midX); // exit toward qx
78
+ const e2ey = qy;
79
+
80
+ let d = '';
81
+ d += `L ${e1x.toFixed(1)} ${py} `; // horizontal to first elbow approach
82
+ d += `Q ${e1cx.toFixed(1)} ${e1cy} ${e1ex.toFixed(1)} ${e1ey.toFixed(1)} `; // rounded corner
83
+ d += `L ${e2x.toFixed(1)} ${e2y.toFixed(1)} `; // vertical run
84
+ d += `Q ${e2cx.toFixed(1)} ${e2cy} ${e2ex.toFixed(1)} ${e2ey.toFixed(1)} `; // rounded corner
85
+ d += `L ${qx} ${qy}`; // horizontal to destination
86
+
87
+ return d;
88
+ }
89
+
90
+ /**
91
+ * V-H-V path: vertical → corner → horizontal → corner → vertical
92
+ */
93
+ function vhPath(px, py, midY, qx, qy, r) {
94
+ const dx = qx - px;
95
+ const sx = Math.sign(dx); // +1 right, -1 left
96
+ const dy = qy - py;
97
+ const sy = Math.sign(dy);
98
+
99
+ // First elbow: at (px, midY) turning toward qx
100
+ const e1y = midY - sy * r;
101
+ const e1cx = px;
102
+ const e1cy = midY;
103
+ const e1ex = px + sx * r;
104
+ const e1ey = midY;
105
+
106
+ // Second elbow: at (qx, midY) turning toward qy
107
+ const e2x = qx - sx * r;
108
+ const e2y = midY;
109
+ const e2cx = qx;
110
+ const e2cy = midY;
111
+ const e2ex = qx;
112
+ const e2ey = midY + sy * r;
113
+
114
+ let d = '';
115
+ d += `L ${px} ${e1y.toFixed(1)} `; // vertical to first elbow
116
+ d += `Q ${e1cx.toFixed(1)} ${e1cy.toFixed(1)} ${e1ex.toFixed(1)} ${e1ey.toFixed(1)} `; // rounded corner
117
+ d += `L ${e2x.toFixed(1)} ${e2y.toFixed(1)} `; // horizontal run
118
+ d += `Q ${e2cx.toFixed(1)} ${e2cy.toFixed(1)} ${e2ex.toFixed(1)} ${e2ey.toFixed(1)} `; // rounded corner
119
+ d += `L ${qx} ${qy}`; // vertical to destination
120
+
121
+ return d;
122
+ }
@@ -0,0 +1,49 @@
1
+ // ================================================================
2
+ // themes.js — Theme definitions for dag-map
3
+ // ================================================================
4
+ // Five built-in themes plus custom theme support via resolveTheme().
5
+ //
6
+ // The class names (pure, recordable, side_effecting, gate) are defaults.
7
+ // Users can use any string keys they want — just pass matching keys in
8
+ // the node `cls` field and in a custom theme's `classes` object.
9
+
10
+ export const THEMES = {
11
+ cream: {
12
+ paper: '#F5F0E8', ink: '#2C2C2C', muted: '#8C8680', border: '#D4CFC7',
13
+ classes: { pure: '#2B8A8E', recordable: '#E8846B', side_effecting: '#D4944C', gate: '#C45B4A', pending: '#B0AAA0' }
14
+ },
15
+ light: {
16
+ paper: '#FAFAFA', ink: '#333333', muted: '#999999', border: '#E0E0E0',
17
+ classes: { pure: '#0077B6', recordable: '#E36414', side_effecting: '#6C757D', gate: '#DC3545', pending: '#BDBDBD' }
18
+ },
19
+ dark: {
20
+ paper: '#1E1E2E', ink: '#CDD6F4', muted: '#6C7086', border: '#313244',
21
+ classes: { pure: '#94E2D5', recordable: '#F38BA8', side_effecting: '#F9E2AF', gate: '#EBA0AC', pending: '#45475A' }
22
+ },
23
+ blueprint: {
24
+ paper: '#1B2838', ink: '#E0E8F0', muted: '#5A7A9A', border: '#2A4060',
25
+ classes: { pure: '#4FC3F7', recordable: '#FF8A65', side_effecting: '#FFD54F', gate: '#EF5350', pending: '#3D5A7A' }
26
+ },
27
+ mono: {
28
+ paper: '#FFFFFF', ink: '#1A1A1A', muted: '#888888', border: '#CCCCCC',
29
+ classes: { pure: '#444444', recordable: '#777777', side_effecting: '#999999', gate: '#333333', pending: '#AAAAAA' }
30
+ },
31
+ metro: {
32
+ paper: '#FFFFFF', ink: '#1A1A1A', muted: '#9E9E9E', border: '#D4EAF7',
33
+ classes: { pure: '#0078C8', recordable: '#E3242B', side_effecting: '#F5A623', gate: '#6A2D8E', pending: '#BDBDBD' },
34
+ lineOpacity: 1.4
35
+ }
36
+ };
37
+
38
+ /**
39
+ * Resolve a theme option to a full theme object.
40
+ *
41
+ * @param {string|object} [themeOption] - theme name string, custom theme object, or undefined
42
+ * @returns {object} resolved theme with paper, ink, muted, border, and classes
43
+ */
44
+ export function resolveTheme(themeOption) {
45
+ if (!themeOption || themeOption === 'cream') return THEMES.cream;
46
+ if (typeof themeOption === 'string') return THEMES[themeOption] || THEMES.cream;
47
+ // Custom theme object — merge with cream defaults
48
+ return { ...THEMES.cream, ...themeOption, classes: { ...THEMES.cream.classes, ...(themeOption.classes || {}) } };
49
+ }