@coldtea/pr-lens-renderer 0.2.6 → 0.3.1

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 (59) hide show
  1. package/README.md +2 -1
  2. package/dist/bounds.d.ts +6 -1
  3. package/dist/bounds.d.ts.map +1 -1
  4. package/dist/bounds.js +21 -0
  5. package/dist/bounds.js.map +1 -1
  6. package/dist/design.d.ts +5 -0
  7. package/dist/design.d.ts.map +1 -1
  8. package/dist/design.js +5 -0
  9. package/dist/design.js.map +1 -1
  10. package/dist/index.d.ts +2 -2
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +1 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/layout/architecture.d.ts +2 -0
  15. package/dist/layout/architecture.d.ts.map +1 -1
  16. package/dist/layout/architecture.js +5 -0
  17. package/dist/layout/architecture.js.map +1 -1
  18. package/dist/layout/congestion.d.ts +5 -0
  19. package/dist/layout/congestion.d.ts.map +1 -1
  20. package/dist/layout/congestion.js +50 -12
  21. package/dist/layout/congestion.js.map +1 -1
  22. package/dist/layout/edges.d.ts +18 -3
  23. package/dist/layout/edges.d.ts.map +1 -1
  24. package/dist/layout/edges.js +93 -23
  25. package/dist/layout/edges.js.map +1 -1
  26. package/dist/layout/labels.d.ts +15 -4
  27. package/dist/layout/labels.d.ts.map +1 -1
  28. package/dist/layout/labels.js +404 -119
  29. package/dist/layout/labels.js.map +1 -1
  30. package/dist/manifest.d.ts +1 -1
  31. package/dist/manifest.d.ts.map +1 -1
  32. package/dist/manifest.js +3 -1
  33. package/dist/manifest.js.map +1 -1
  34. package/dist/render.d.ts +12 -2
  35. package/dist/render.d.ts.map +1 -1
  36. package/dist/render.js +2 -2
  37. package/dist/render.js.map +1 -1
  38. package/dist/svg/architecture.d.ts.map +1 -1
  39. package/dist/svg/architecture.js +3 -8
  40. package/dist/svg/architecture.js.map +1 -1
  41. package/dist/theme.d.ts +9 -8
  42. package/dist/theme.d.ts.map +1 -1
  43. package/dist/theme.js +61 -8
  44. package/dist/theme.js.map +1 -1
  45. package/dist/version.d.ts +1 -1
  46. package/dist/version.js +1 -1
  47. package/package.json +2 -2
  48. package/src/bounds.ts +29 -1
  49. package/src/design.ts +5 -0
  50. package/src/index.ts +2 -1
  51. package/src/layout/architecture.ts +7 -0
  52. package/src/layout/congestion.ts +71 -12
  53. package/src/layout/edges.ts +126 -21
  54. package/src/layout/labels.ts +495 -136
  55. package/src/manifest.ts +3 -2
  56. package/src/render.ts +19 -4
  57. package/src/svg/architecture.ts +3 -7
  58. package/src/theme.ts +62 -8
  59. package/src/version.ts +1 -1
package/src/manifest.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import type { GraphDoc, Lens, RenderAsset, RenderManifest } from "@coldtea/pr-lens-schema";
3
3
  import { SCHEMA_VERSION } from "@coldtea/pr-lens-schema";
4
- import type { Theme } from "./theme.js";
4
+ import { THEMES, type Theme } from "./theme.js";
5
5
  import { RENDERER_NAME, RENDERER_VERSION } from "./version.js";
6
6
 
7
7
  /**
@@ -62,7 +62,8 @@ const escapeForFileName = (id: string): string =>
62
62
 
63
63
  /** The contract's ceiling on an `Id`, which an asset id has to stay under. */
64
64
  const ASSET_ID_MAX = 128;
65
- const THEME_SUFFIX_MAX = "-light".length;
65
+ /** The longest theme name, so a stem can never push an asset id over the cap. */
66
+ const THEME_SUFFIX_MAX = Math.max(...THEMES.map((theme) => theme.length)) + "-".length;
66
67
  const STEM_MAX = ASSET_ID_MAX - THEME_SUFFIX_MAX;
67
68
  const DIGEST_LENGTH = 12;
68
69
 
package/src/render.ts CHANGED
@@ -5,7 +5,7 @@ import { applyCorrections } from "./corrections.js";
5
5
  import { PrLensRenderError } from "./errors.js";
6
6
  import { buildManifest, contentHash, renderAssetFileName, renderAssetId } from "./manifest.js";
7
7
  import { findView, flattenViews, resolveScope, type ScopedGraph } from "./scope.js";
8
- import { paletteFor, THEMES, type Theme } from "./theme.js";
8
+ import { paletteFor, THEME_PAIR, type Theme } from "./theme.js";
9
9
  import { paintArchitecture } from "./svg/architecture.js";
10
10
  import { paintDataFlow } from "./svg/dataflow.js";
11
11
  import { svgDocument } from "./svg/document.js";
@@ -122,10 +122,25 @@ const requireView = (views: readonly View[], id: string): View => {
122
122
  return view;
123
123
  };
124
124
 
125
+ /**
126
+ * What a render may be asked for: the `<picture>` pair, one half of it, or
127
+ * the single self-contained render.
128
+ *
129
+ * Not `readonly Theme[]`, which would admit all three at once — three assets
130
+ * per view against a view cap derived from two, so a document that validates
131
+ * could produce a manifest that does not. The contract's promise is that if
132
+ * it parses, it renders; this is the boundary that keeps it true.
133
+ */
134
+ export type RenderThemes =
135
+ | readonly ["light", "dark"]
136
+ | readonly ["light"]
137
+ | readonly ["dark"]
138
+ | readonly ["neutral"];
139
+
125
140
  export type RenderAllOptions = {
126
141
  config?: Config;
127
- /** Which halves of the theme pair to produce. Both, by default. */
128
- themes?: readonly Theme[];
142
+ /** The `<picture>` pair by default; `['neutral']` for a single-image surface. */
143
+ themes?: RenderThemes;
129
144
  };
130
145
 
131
146
  export type RenderedAsset = RenderedSvg & { asset: RenderAsset };
@@ -144,7 +159,7 @@ export type RenderAllResult = {
144
159
  */
145
160
  export const renderAll = (doc: GraphDoc, options: RenderAllOptions = {}): RenderAllResult => {
146
161
  const prepared = prepare(doc, options.config);
147
- const themes = options.themes ?? THEMES;
162
+ const themes = options.themes ?? THEME_PAIR;
148
163
 
149
164
  const targets: { lens: Lens; view: View | undefined }[] =
150
165
  prepared.views.length > 0
@@ -8,7 +8,6 @@ import { travellingPulses } from "./pulse.js";
8
8
  import type { ScopedGraph } from "../scope.js";
9
9
  import { canvasFor, union } from "../bounds.js";
10
10
  import { coord, type Box } from "../geometry.js";
11
- import { placeLabelPills } from "../layout/labels.js";
12
11
  import { relieveCongestion } from "../layout/congestion.js";
13
12
  import { lines, tag, textNode, wrap } from "./primitives.js";
14
13
  import { curveBounds, type RoutedEdge } from "../layout/edges.js";
@@ -28,6 +27,7 @@ import {
28
27
  cardTextWidth,
29
28
  deltaBadgeText,
30
29
  laneHeaderText,
30
+ occupiedBoxes,
31
31
  type PlacedNode,
32
32
  } from "../layout/architecture.js";
33
33
  import {
@@ -267,14 +267,10 @@ export const paintArchitecture = (
267
267
  hints: LayoutHints | undefined,
268
268
  palette: Palette,
269
269
  ): ArchitecturePainting => {
270
- const { layout, routed } = relieveCongestion(graph, hints);
270
+ const { layout, routed, pills } = relieveCongestion(graph, hints);
271
271
  const styles = stylesFor(palette);
272
- const pills = placeLabelPills(routed);
272
+ const drawn: Box[] = occupiedBoxes(layout.nodes);
273
273
 
274
- const drawn: Box[] = layout.nodes.flatMap((node) => {
275
- const row = badgeRow(node);
276
- return row === undefined ? [node.box] : [node.box, row.box];
277
- });
278
274
  const edgeMarkup: string[] = [];
279
275
  const pillMarkup: string[] = [];
280
276
  for (const edge of routed) {
package/src/theme.ts CHANGED
@@ -1,15 +1,16 @@
1
- import { assertNever, THEMES, type Theme } from "@coldtea/pr-lens-schema";
1
+ import { assertNever, THEMES, THEME_PAIR, type Theme } from "@coldtea/pr-lens-schema";
2
2
 
3
3
  /**
4
- * The two halves of a GitHub `<picture>` pair. A comment ships both and the
5
- * client picks; neither half may depend on the page it lands in, because an
6
- * SVG served as an image cannot see it.
4
+ * Every theme a render can target, and the two that make a GitHub
5
+ * `<picture>` pair. A paired comment ships both halves and the client picks;
6
+ * no render may depend on the page it lands in, because an SVG served as an
7
+ * image cannot see it.
7
8
  *
8
- * The contract owns the pair, because the number of themes is one half of the
9
- * arithmetic that bounds a document's view tree. Re-exported here so a caller
10
- * rendering a diagram does not have to reach into the schema for it.
9
+ * The contract owns the pair, because its size is one half of the arithmetic
10
+ * that bounds a document's view tree. Re-exported here so a caller rendering
11
+ * a diagram does not have to reach into the schema for it.
11
12
  */
12
- export { THEMES, type Theme };
13
+ export { THEMES, THEME_PAIR, type Theme };
13
14
 
14
15
  /**
15
16
  * Every colour the renderer can paint, resolved to a literal before it
@@ -106,12 +107,65 @@ const DARK: Palette = {
106
107
  shadow: "rgba(0,0,0,.28)",
107
108
  };
108
109
 
110
+ /**
111
+ * The one render for a surface that shows a single image and cannot swap on
112
+ * the reader's theme — GitLab and Bitbucket both.
113
+ *
114
+ * Handing such a surface the light asset is what it used to do, and it put a
115
+ * glaring white rectangle in front of every dark-mode reader, with strokes
116
+ * tuned for a light ground washing out against a dark page.
117
+ *
118
+ * A transparent render does not solve it either, and the arithmetic says why:
119
+ * for any flat colour, contrast against white is `1.05 / (L + 0.05)` and
120
+ * against black is `(L + 0.05) / 0.05`, which are equal only at L ≈ 0.179,
121
+ * where both are 4.58:1. That is the ceiling for text meant to sit directly
122
+ * on either page — barely over AA in the best case, and under it against a
123
+ * near-black like #0d1117.
124
+ *
125
+ * So this render is opaque and self-contained: it brings its own ground, and
126
+ * every contrast ratio inside it is fixed regardless of where it lands. The
127
+ * ground is dimmed rather than light — on a dark page it sits naturally, and
128
+ * on a light page it reads as a deliberately dark figure, the way a
129
+ * screenshot or a code block does. Its border is visible against both, so it
130
+ * never bleeds into the page.
131
+ */
132
+ const NEUTRAL: Palette = {
133
+ background: "#22272e",
134
+ dot: "rgba(144,157,171,.2)",
135
+ lane: "rgba(144,157,171,.06)",
136
+ card: "#2d333b",
137
+ cardBorder: "#444c56",
138
+ foreground: "#cdd9e5",
139
+ muted: "#909dab",
140
+ edge: "#768390",
141
+ chip: "rgba(144,157,171,.16)",
142
+ pill: "#22272e",
143
+ pillBorder: "#373e47",
144
+ lifeline: "#444c56",
145
+ added: "#57ab5a",
146
+ addedText: "#6bc46d",
147
+ addedFill: "rgba(70,149,74,.18)",
148
+ addedBorder: "rgba(87,171,90,.45)",
149
+ modified: "#c69026",
150
+ modifiedText: "#daaa3f",
151
+ modifiedFill: "rgba(174,124,20,.18)",
152
+ modifiedBorder: "rgba(198,144,38,.45)",
153
+ removed: "#e5534b",
154
+ removedText: "#ff938a",
155
+ removedFill: "rgba(229,83,75,.14)",
156
+ removedBorder: "rgba(229,83,75,.45)",
157
+ neutralFill: "rgba(144,157,171,.16)",
158
+ shadow: "rgba(0,0,0,.3)",
159
+ };
160
+
109
161
  export const paletteFor = (theme: Theme): Palette => {
110
162
  switch (theme) {
111
163
  case "light":
112
164
  return LIGHT;
113
165
  case "dark":
114
166
  return DARK;
167
+ case "neutral":
168
+ return NEUTRAL;
115
169
  default:
116
170
  return assertNever(theme, "Unhandled theme");
117
171
  }
package/src/version.ts CHANGED
@@ -6,4 +6,4 @@
6
6
  */
7
7
  export const RENDERER_NAME = "@coldtea/pr-lens-renderer";
8
8
 
9
- export const RENDERER_VERSION = "0.2.6";
9
+ export const RENDERER_VERSION = "0.3.1";