@shipload/item-renderer 0.2.2 → 0.2.3

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 (67) hide show
  1. package/.claude/settings.local.json +6 -0
  2. package/bun.lock +2 -2
  3. package/package.json +2 -2
  4. package/scripts/check-bundle-size.ts +21 -21
  5. package/scripts/copy-fonts.ts +19 -19
  6. package/scripts/preview.ts +13 -15
  7. package/src/assets/stardust-base64.ts +1 -1
  8. package/src/errors.ts +8 -8
  9. package/src/fonts/index.ts +25 -26
  10. package/src/fonts/load-bun.ts +9 -9
  11. package/src/index.ts +21 -21
  12. package/src/links.ts +11 -11
  13. package/src/meta.ts +16 -16
  14. package/src/payload/base64url.ts +16 -16
  15. package/src/payload/codec.ts +13 -13
  16. package/src/primitives/category-icon.ts +69 -48
  17. package/src/primitives/compact-row.ts +13 -13
  18. package/src/primitives/divider.ts +9 -9
  19. package/src/primitives/icon-hex.ts +18 -16
  20. package/src/primitives/module-slot.ts +73 -73
  21. package/src/primitives/panel.ts +10 -10
  22. package/src/primitives/quantity-badge.ts +13 -13
  23. package/src/primitives/span-paragraph.ts +48 -50
  24. package/src/primitives/stat-bar.ts +24 -24
  25. package/src/primitives/svg.ts +13 -13
  26. package/src/primitives/text.ts +25 -25
  27. package/src/primitives/wrap.ts +12 -12
  28. package/src/render.ts +15 -19
  29. package/src/templates/_shared.ts +5 -6
  30. package/src/templates/component.ts +67 -65
  31. package/src/templates/index.ts +17 -17
  32. package/src/templates/item-cell.ts +48 -45
  33. package/src/templates/module.ts +83 -81
  34. package/src/templates/packed-entity.ts +12 -14
  35. package/src/templates/resource.ts +63 -65
  36. package/src/templates/ship-panel.ts +66 -71
  37. package/src/templates/social-card.ts +27 -25
  38. package/src/tokens/colors.ts +29 -29
  39. package/src/tokens/index.ts +6 -6
  40. package/src/tokens/spacing.ts +1 -1
  41. package/src/tokens/typography.ts +1 -1
  42. package/test/__image_snapshots__/module-storage-t1.diff.png +0 -0
  43. package/test/__image_snapshots__/packed-entity-ship-t1-two-modules.diff.png +0 -0
  44. package/test/base64url.test.ts +22 -22
  45. package/test/codec.test.ts +26 -35
  46. package/test/errors.test.ts +21 -21
  47. package/test/fixtures/cargo-items.ts +43 -43
  48. package/test/fonts.test.ts +23 -23
  49. package/test/links-meta.test.ts +37 -37
  50. package/test/pixel.test.ts +44 -41
  51. package/test/primitives-category-icon.test.ts +74 -67
  52. package/test/primitives-compact-row.test.ts +29 -29
  53. package/test/primitives-domain.test.ts +61 -50
  54. package/test/primitives-layout.test.ts +47 -47
  55. package/test/primitives-module-slot.test.ts +58 -58
  56. package/test/render.test.ts +38 -35
  57. package/test/sanity.test.ts +5 -5
  58. package/test/sdk-link.test.ts +13 -13
  59. package/test/svg.test.ts +24 -22
  60. package/test/templates-component.test.ts +32 -32
  61. package/test/templates-dispatch.test.ts +29 -29
  62. package/test/templates-item-cell.test.ts +79 -79
  63. package/test/templates-module.test.ts +52 -52
  64. package/test/templates-packed-entity.test.ts +42 -42
  65. package/test/templates-resource.test.ts +61 -61
  66. package/test/templates-ship-panel.test.ts +65 -61
  67. package/test/tokens.test.ts +28 -26
@@ -1,13 +1,13 @@
1
- import type { ResolvedItem } from '@shipload/sdk'
2
- import type { CargoItem } from '../payload/codec.ts'
3
- import { RenderError } from '../errors.ts'
4
- import { renderResource } from './resource.ts'
5
- import { renderPackedEntity } from './packed-entity.ts'
6
- import { renderComponent } from './component.ts'
7
- import { renderModule } from './module.ts'
1
+ import type { ResolvedItem } from "@shipload/sdk";
2
+ import type { CargoItem } from "../payload/codec.ts";
3
+ import { RenderError } from "../errors.ts";
4
+ import { renderResource } from "./resource.ts";
5
+ import { renderPackedEntity } from "./packed-entity.ts";
6
+ import { renderComponent } from "./component.ts";
7
+ import { renderModule } from "./module.ts";
8
8
 
9
9
  export interface RenderByTypeOpts {
10
- mode?: 'values' | 'ranges'
10
+ mode?: "values" | "ranges";
11
11
  }
12
12
 
13
13
  export function renderByType(
@@ -16,15 +16,15 @@ export function renderByType(
16
16
  opts?: RenderByTypeOpts,
17
17
  ): string {
18
18
  switch (resolved.itemType) {
19
- case 'resource':
20
- return renderResource(item, resolved, opts)
21
- case 'entity':
22
- return renderPackedEntity(item, resolved)
23
- case 'component':
24
- return renderComponent(item, resolved, opts)
25
- case 'module':
26
- return renderModule(item, resolved, opts)
19
+ case "resource":
20
+ return renderResource(item, resolved, opts);
21
+ case "entity":
22
+ return renderPackedEntity(item, resolved);
23
+ case "component":
24
+ return renderComponent(item, resolved, opts);
25
+ case "module":
26
+ return renderModule(item, resolved, opts);
27
27
  default:
28
- throw new RenderError(`unknown itemType '${String(resolved.itemType)}'`)
28
+ throw new RenderError(`unknown itemType '${String(resolved.itemType)}'`);
29
29
  }
30
30
  }
@@ -1,32 +1,28 @@
1
- import type { ResolvedItem, ResourceTier } from '@shipload/sdk'
2
- import { tierColors, categoryColors, categoryIconShapes } from '@shipload/sdk'
3
- import { el } from '../primitives/svg.ts'
4
- import { text } from '../primitives/text.ts'
5
- import { categoryIconPath } from '../primitives/category-icon.ts'
6
- import { tokens } from '../tokens/index.ts'
7
-
8
- function tierKey(tier: number): ResourceTier {
9
- return `t${tier}` as ResourceTier
10
- }
1
+ import type { ResolvedItem } from "@shipload/sdk";
2
+ import { tierColors, categoryColors, categoryIconShapes } from "@shipload/sdk";
3
+ import { el } from "../primitives/svg.ts";
4
+ import { text } from "../primitives/text.ts";
5
+ import { categoryIconPath } from "../primitives/category-icon.ts";
6
+ import { tokens } from "../tokens/index.ts";
11
7
 
12
8
  export interface ItemCellProps {
13
- resolved: ResolvedItem
14
- quantity?: number
15
- size?: number
9
+ resolved: ResolvedItem;
10
+ quantity?: number;
11
+ size?: number;
16
12
  }
17
13
 
18
14
  export interface ItemCellGroupProps extends ItemCellProps {
19
- x: number
20
- y: number
15
+ x: number;
16
+ y: number;
21
17
  }
22
18
 
23
19
  function cellInner(props: ItemCellProps): string {
24
- const size = props.size ?? 48
25
- const height = Math.round(size * 1.25)
26
- const r = Math.max(4, Math.round(size * 0.12))
27
- const cx = size / 2
20
+ const size = props.size ?? 48;
21
+ const height = Math.round(size * 1.25);
22
+ const r = Math.max(4, Math.round(size * 0.12));
23
+ const cx = size / 2;
28
24
 
29
- const border = el('rect', {
25
+ const border = el("rect", {
30
26
  x: 0.5,
31
27
  y: 0.5,
32
28
  width: size - 1,
@@ -34,28 +30,35 @@ function cellInner(props: ItemCellProps): string {
34
30
  rx: r,
35
31
  ry: r,
36
32
  fill: tokens.colors.surface.panel,
37
- stroke: tierColors[tierKey(props.resolved.tier)] ?? tokens.colors.surface.panelBorder,
38
- 'stroke-width': 1.5,
39
- })
33
+ stroke: tierColors[props.resolved.tier] ?? tokens.colors.surface.panelBorder,
34
+ "stroke-width": 1.5,
35
+ });
40
36
 
41
- let content = ''
37
+ let content = "";
42
38
  if (props.resolved.abbreviation) {
43
- const iconCy = size * 0.45
39
+ const iconCy = size * 0.45;
44
40
  content = text({
45
41
  x: cx,
46
42
  y: iconCy,
47
43
  value: props.resolved.abbreviation,
48
44
  size: Math.round(size * 0.28),
49
45
  weight: 700,
50
- anchor: 'middle',
46
+ anchor: "middle",
51
47
  color: tokens.colors.text.primary,
52
48
  family: tokens.typography.display,
53
- })
49
+ });
54
50
  } else if (props.resolved.category) {
55
- const shape = categoryIconShapes[props.resolved.category]
56
- const color = categoryColors[props.resolved.category]
57
- const iconCy = size * 0.4
58
- content = categoryIconPath({ shape, cx, cy: iconCy, size: size * 0.32, color, strokeWidth: 1.5 })
51
+ const shape = categoryIconShapes[props.resolved.category];
52
+ const color = categoryColors[props.resolved.category];
53
+ const iconCy = size * 0.4;
54
+ content = categoryIconPath({
55
+ shape,
56
+ cx,
57
+ cy: iconCy,
58
+ size: size * 0.32,
59
+ color,
60
+ strokeWidth: 1.5,
61
+ });
59
62
  } else if (props.resolved.icon) {
60
63
  content = text({
61
64
  x: cx,
@@ -63,38 +66,38 @@ function cellInner(props: ItemCellProps): string {
63
66
  value: props.resolved.icon,
64
67
  size: Math.round(size * 0.44),
65
68
  weight: 400,
66
- anchor: 'middle',
67
- dominantBaseline: 'central',
69
+ anchor: "middle",
70
+ dominantBaseline: "central",
68
71
  color: tokens.colors.text.primary,
69
- })
72
+ });
70
73
  }
71
74
 
72
- const qty = props.quantity ?? 0
73
- let quantityText = ''
75
+ const qty = props.quantity ?? 0;
76
+ let quantityText = "";
74
77
  if (qty > 1) {
75
- const qtyFontSize = Math.max(9, Math.round(size * 0.22))
76
- const qtyPad = Math.max(3, Math.round(size * 0.12))
78
+ const qtyFontSize = Math.max(9, Math.round(size * 0.22));
79
+ const qtyPad = Math.max(3, Math.round(size * 0.12));
77
80
  quantityText = text({
78
81
  x: size - qtyPad,
79
82
  y: height - qtyPad,
80
83
  value: String(qty),
81
84
  size: qtyFontSize,
82
85
  weight: 700,
83
- anchor: 'end',
86
+ anchor: "end",
84
87
  color: tokens.colors.text.primary,
85
88
  family: tokens.typography.display,
86
- })
89
+ });
87
90
  }
88
91
 
89
- return border + content + quantityText
92
+ return border + content + quantityText;
90
93
  }
91
94
 
92
95
  export function itemCellGroup(props: ItemCellGroupProps): string {
93
- return `<g transform="translate(${props.x}, ${props.y})">${cellInner(props)}</g>`
96
+ return `<g transform="translate(${props.x}, ${props.y})">${cellInner(props)}</g>`;
94
97
  }
95
98
 
96
99
  export function renderItemCell(props: ItemCellProps): string {
97
- const size = props.size ?? 48
98
- const height = Math.round(size * 1.25)
99
- return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${height}" viewBox="0 0 ${size} ${height}">${cellInner(props)}</svg>`
100
+ const size = props.size ?? 48;
101
+ const height = Math.round(size * 1.25);
102
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${height}" viewBox="0 0 ${size} ${height}">${cellInner(props)}</svg>`;
100
103
  }
@@ -1,81 +1,83 @@
1
- import type { ResolvedItem } from '@shipload/sdk'
2
- import { describeModuleForItem, formatTier, renderDescription } from '@shipload/sdk'
3
- import type { CargoItem } from '../payload/codec.ts'
4
- import { panel } from '../primitives/panel.ts'
5
- import { iconHex } from '../primitives/icon-hex.ts'
6
- import { text } from '../primitives/text.ts'
7
- import { divider } from '../primitives/divider.ts'
8
- import { compactRow } from '../primitives/compact-row.ts'
9
- import { quantityBadge } from '../primitives/quantity-badge.ts'
10
- import { spanParagraph } from '../primitives/span-paragraph.ts'
11
- import { tokens } from '../tokens/index.ts'
12
- import { shortCode, formatMass, tierBorder } from './_shared.ts'
1
+ import type { ResolvedItem } from "@shipload/sdk";
2
+ import { describeModuleForItem, formatTier, renderDescription } from "@shipload/sdk";
3
+ import type { CargoItem } from "../payload/codec.ts";
4
+ import { panel } from "../primitives/panel.ts";
5
+ import { iconHex } from "../primitives/icon-hex.ts";
6
+ import { text } from "../primitives/text.ts";
7
+ import { divider } from "../primitives/divider.ts";
8
+ import { compactRow } from "../primitives/compact-row.ts";
9
+ import { quantityBadge } from "../primitives/quantity-badge.ts";
10
+ import { spanParagraph } from "../primitives/span-paragraph.ts";
11
+ import { tokens } from "../tokens/index.ts";
12
+ import { shortCode, formatMass, tierBorder } from "./_shared.ts";
13
13
 
14
14
  function capabilityColor(name: string): string {
15
- const key = name.toLowerCase().replace(/\s+/g, '') as keyof typeof tokens.colors.capability
16
- return tokens.colors.capability[key] ?? tokens.colors.accent.component
15
+ const key = name.toLowerCase().replace(/\s+/g, "") as keyof typeof tokens.colors.capability;
16
+ return tokens.colors.capability[key] ?? tokens.colors.accent.component;
17
17
  }
18
18
 
19
19
  export interface RenderModuleOpts {
20
- mode?: 'values' | 'ranges'
20
+ mode?: "values" | "ranges";
21
21
  }
22
22
 
23
- export function renderModule(item: CargoItem, resolved: ResolvedItem, opts?: RenderModuleOpts): string {
24
- const mode = opts?.mode ?? 'values'
25
- const w = tokens.spacing.panelWidth
26
- const pad = tokens.spacing.panelPadding
27
- const innerW = w - pad * 2
28
-
29
- const group = resolved.attributes?.[0]
30
- const attrs = group?.attributes ?? []
31
- const desc = mode === 'values' ? describeModuleForItem(resolved) : undefined
32
-
33
- const capabilityName =
34
- group?.capability ??
35
- resolved.name.replace(/\s+T\d+$/i, '')
36
-
37
- const headerH = 48
38
- const metaRowH = 28
39
- const sepY = pad + headerH + metaRowH + 6
40
-
41
- let bodyHeight = 0
42
- if (mode === 'ranges') {
43
- bodyHeight = 20 + 8
23
+ export function renderModule(
24
+ item: CargoItem,
25
+ resolved: ResolvedItem,
26
+ opts?: RenderModuleOpts,
27
+ ): string {
28
+ const mode = opts?.mode ?? "values";
29
+ const w = tokens.spacing.panelWidth;
30
+ const pad = tokens.spacing.panelPadding;
31
+ const innerW = w - pad * 2;
32
+
33
+ const group = resolved.attributes?.[0];
34
+ const attrs = group?.attributes ?? [];
35
+ const desc = mode === "values" ? describeModuleForItem(resolved) : undefined;
36
+
37
+ const capabilityName = group?.capability ?? resolved.name.replace(/\s+T\d+$/i, "");
38
+
39
+ const headerH = 48;
40
+ const metaRowH = 28;
41
+ const sepY = pad + headerH + metaRowH + 6;
42
+
43
+ let bodyHeight = 0;
44
+ if (mode === "ranges") {
45
+ bodyHeight = 20 + 8;
44
46
  } else if (desc && group) {
45
47
  const plain = renderDescription(desc)
46
48
  .map((s) => s.text)
47
- .join('')
49
+ .join("");
48
50
  const lines = plain.split(/\s+/).reduce(
49
51
  (acc, word) => {
50
- const last = acc[acc.length - 1] ?? ''
51
- if (last.length === 0) return [...acc.slice(0, -1), word]
52
- if (last.length + 1 + word.length <= 36) return [...acc.slice(0, -1), `${last} ${word}`]
53
- return [...acc, word]
52
+ const last = acc[acc.length - 1] ?? "";
53
+ if (last.length === 0) return [...acc.slice(0, -1), word];
54
+ if (last.length + 1 + word.length <= 36) return [...acc.slice(0, -1), `${last} ${word}`];
55
+ return [...acc, word];
54
56
  },
55
- [''],
56
- )
57
- const lineCount = lines.filter((l) => l.length > 0).length
58
- bodyHeight = 20 + lineCount * 14 + 8
57
+ [""],
58
+ );
59
+ const lineCount = lines.filter((l) => l.length > 0).length;
60
+ bodyHeight = 20 + lineCount * 14 + 8;
59
61
  } else if (group && attrs.length > 0) {
60
- const capHeaderH = 22
61
- const attrsH = attrs.length * 18
62
- bodyHeight = capHeaderH + attrsH + 8
62
+ const capHeaderH = 22;
63
+ const attrsH = attrs.length * 18;
64
+ bodyHeight = capHeaderH + attrsH + 8;
63
65
  }
64
66
 
65
- const height = headerH + metaRowH + 14 + bodyHeight + pad
67
+ const height = headerH + metaRowH + 14 + bodyHeight + pad;
66
68
 
67
- const chrome = panel({ width: w, height, borderColor: tierBorder(resolved.tier) })
69
+ const chrome = panel({ width: w, height, borderColor: tierBorder(resolved.tier) });
68
70
 
69
- const quantity = Number(BigInt(item.quantity.toString()))
70
- const badge = quantityBadge({ x: w - pad, y: pad, quantity })
71
+ const quantity = Number(BigInt(item.quantity.toString()));
72
+ const badge = quantityBadge({ x: w - pad, y: pad, quantity });
71
73
 
72
- const iconColor = group ? capabilityColor(group.capability) : capabilityColor(capabilityName)
74
+ const iconColor = group ? capabilityColor(group.capability) : capabilityColor(capabilityName);
73
75
  const icon = iconHex({
74
76
  x: pad,
75
77
  y: pad + 4,
76
78
  color: iconColor,
77
79
  code: shortCode(resolved.itemId),
78
- })
80
+ });
79
81
 
80
82
  const name = text({
81
83
  x: pad + 34,
@@ -84,45 +86,45 @@ export function renderModule(item: CargoItem, resolved: ResolvedItem, opts?: Ren
84
86
  size: tokens.typography.sizes.title,
85
87
  weight: 700,
86
88
  family: tokens.typography.display,
87
- })
89
+ });
88
90
 
89
91
  const subtitleLabel = text({
90
92
  x: pad,
91
93
  y: pad + headerH + 4,
92
- value: 'Type',
94
+ value: "Type",
93
95
  size: tokens.typography.sizes.body,
94
96
  color: tokens.colors.text.secondary,
95
- })
97
+ });
96
98
  const subtitleValue = text({
97
99
  x: w - pad,
98
100
  y: pad + headerH + 4,
99
101
  value: `MODULE · ${formatTier(resolved.tier)}`,
100
102
  size: tokens.typography.sizes.body,
101
103
  weight: 600,
102
- anchor: 'end',
103
- })
104
+ anchor: "end",
105
+ });
104
106
 
105
107
  const massLabel = text({
106
108
  x: pad,
107
109
  y: pad + headerH + metaRowH - 8,
108
- value: 'Mass',
110
+ value: "Mass",
109
111
  size: tokens.typography.sizes.body,
110
112
  color: tokens.colors.text.secondary,
111
- })
113
+ });
112
114
  const massValue = text({
113
115
  x: w - pad,
114
116
  y: pad + headerH + metaRowH - 8,
115
117
  value: formatMass(resolved.mass),
116
118
  size: tokens.typography.sizes.body,
117
119
  weight: 600,
118
- anchor: 'end',
119
- })
120
+ anchor: "end",
121
+ });
120
122
 
121
- const sep = divider({ x: pad, y: sepY, width: innerW })
123
+ const sep = divider({ x: pad, y: sepY, width: innerW });
122
124
 
123
- let capSection = ''
124
- if (mode === 'ranges') {
125
- const accentColor = capabilityColor(capabilityName)
125
+ let capSection = "";
126
+ if (mode === "ranges") {
127
+ const accentColor = capabilityColor(capabilityName);
126
128
  capSection = text({
127
129
  x: pad,
128
130
  y: sepY + 16,
@@ -132,9 +134,9 @@ export function renderModule(item: CargoItem, resolved: ResolvedItem, opts?: Ren
132
134
  family: tokens.typography.sans,
133
135
  color: accentColor,
134
136
  letterSpacing: 1,
135
- })
137
+ });
136
138
  } else if (desc && group) {
137
- const accentColor = capabilityColor(group.capability)
139
+ const accentColor = capabilityColor(group.capability);
138
140
  const capHeader = text({
139
141
  x: pad,
140
142
  y: sepY + 16,
@@ -144,18 +146,18 @@ export function renderModule(item: CargoItem, resolved: ResolvedItem, opts?: Ren
144
146
  family: tokens.typography.sans,
145
147
  color: accentColor,
146
148
  letterSpacing: 1,
147
- })
148
- const spans = renderDescription(desc)
149
+ });
150
+ const spans = renderDescription(desc);
149
151
  const { svg: paraSvg } = spanParagraph({
150
152
  x: pad,
151
153
  y: sepY + 36,
152
154
  spans,
153
155
  charsPerLine: 36,
154
156
  lineHeight: 14,
155
- })
156
- capSection = capHeader + paraSvg
157
+ });
158
+ capSection = capHeader + paraSvg;
157
159
  } else if (group && attrs.length > 0) {
158
- const capY = sepY + 22
160
+ const capY = sepY + 22;
159
161
  const capHeader = text({
160
162
  x: pad,
161
163
  y: capY,
@@ -165,25 +167,25 @@ export function renderModule(item: CargoItem, resolved: ResolvedItem, opts?: Ren
165
167
  family: tokens.typography.sans,
166
168
  color: capabilityColor(group.capability),
167
169
  letterSpacing: 0.8,
168
- })
170
+ });
169
171
 
170
172
  const attrRows = attrs
171
173
  .map((attr, i) => {
172
- const displayValue = String(attr.value)
174
+ const displayValue = String(attr.value);
173
175
  return compactRow({
174
176
  x: pad,
175
177
  y: capY + 14 + i * 18,
176
178
  width: innerW,
177
179
  label: attr.label,
178
180
  value: displayValue,
179
- })
181
+ });
180
182
  })
181
- .join('')
183
+ .join("");
182
184
 
183
- capSection = capHeader + attrRows
185
+ capSection = capHeader + attrRows;
184
186
  }
185
187
 
186
- const inner = `${chrome}${icon}${name}${badge}${subtitleLabel}${subtitleValue}${massLabel}${massValue}${sep}${capSection}`
188
+ const inner = `${chrome}${icon}${name}${badge}${subtitleLabel}${subtitleValue}${massLabel}${massValue}${sep}${capSection}`;
187
189
 
188
- return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${height}" viewBox="0 0 ${w} ${height}">${inner}</svg>`
190
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${height}" viewBox="0 0 ${w} ${height}">${inner}</svg>`;
189
191
  }
@@ -1,30 +1,28 @@
1
- import type { ResolvedItem, ResolvedModuleSlot } from '@shipload/sdk'
2
- import { describeModuleForSlot, renderDescription } from '@shipload/sdk'
3
- import type { CargoItem } from '../payload/codec.ts'
4
- import { renderShipPanel, type ShipPanelSlot } from './ship-panel.ts'
1
+ import type { ResolvedItem, ResolvedModuleSlot } from "@shipload/sdk";
2
+ import { describeModuleForSlot, renderDescription } from "@shipload/sdk";
3
+ import type { CargoItem } from "../payload/codec.ts";
4
+ import { renderShipPanel, type ShipPanelSlot } from "./ship-panel.ts";
5
5
 
6
6
  function slotToPanelSlot(slot: ResolvedModuleSlot): ShipPanelSlot {
7
7
  if (!slot.installed || !slot.attributes || !slot.name) {
8
- return { installed: false }
8
+ return { installed: false };
9
9
  }
10
- const desc = describeModuleForSlot(slot)
10
+ const desc = describeModuleForSlot(slot);
11
11
  if (desc) {
12
- return { name: slot.name, installed: true, description: renderDescription(desc) }
12
+ return { name: slot.name, installed: true, description: renderDescription(desc) };
13
13
  }
14
- const shorthand = slot.attributes
15
- .map((a) => `${a.value} ${a.label.toLowerCase()}`)
16
- .join(' · ')
17
- return { name: slot.name, installed: true, description: shorthand }
14
+ const shorthand = slot.attributes.map((a) => `${a.value} ${a.label.toLowerCase()}`).join(" · ");
15
+ return { name: slot.name, installed: true, description: shorthand };
18
16
  }
19
17
 
20
18
  export function renderPackedEntity(item: CargoItem, resolved: ResolvedItem): string {
21
- const quantity = Number(BigInt(item.quantity.toString()))
22
- const slots = (resolved.moduleSlots ?? []).map(slotToPanelSlot)
19
+ const quantity = Number(BigInt(item.quantity.toString()));
20
+ const slots = (resolved.moduleSlots ?? []).map(slotToPanelSlot);
23
21
  return renderShipPanel({
24
22
  name: `${resolved.name} (Packed)`,
25
23
  tier: resolved.tier,
26
24
  quantity,
27
25
  attributes: resolved.attributes ?? [],
28
26
  slots,
29
- })
27
+ });
30
28
  }