@astryxdesign/cli 0.1.4-canary.f949492 → 0.1.4-canary.fd7ab46

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.
@@ -125,7 +125,7 @@ If you don't know all three, run \`npx astryx init --features agents\` to genera
125
125
  lang: 'json',
126
126
  label: 'package.json',
127
127
  code: `"scripts": {
128
- "xds": "node node_modules/@astryxdesign/cli/bin/astryx.mjs"
128
+ "astryx": "node node_modules/@astryxdesign/cli/bin/astryx.mjs"
129
129
  }`,
130
130
  },
131
131
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/cli",
3
- "version": "0.1.4-canary.f949492",
3
+ "version": "0.1.4-canary.fd7ab46",
4
4
  "displayName": "CLI",
5
5
  "description": "Scaffold projects, browse templates, generate themes, and get agent-ready docs from the command line.",
6
6
  "author": "Meta Open Source",
@@ -68,17 +68,17 @@
68
68
  "CHANGELOG.md"
69
69
  ],
70
70
  "dependencies": {
71
- "@clack/prompts": "^1.5.1",
71
+ "@clack/prompts": "^1.7.0",
72
72
  "commander": "^12.1.0",
73
73
  "jiti": "^2.7.0",
74
74
  "jscodeshift": "^17.3.0",
75
75
  "zod": "^4.4.3"
76
76
  },
77
77
  "peerDependencies": {
78
- "@astryxdesign/charts": "0.1.4-canary.f949492",
79
- "@astryxdesign/core": "0.1.4-canary.f949492",
80
- "@astryxdesign/lab": "0.1.4-canary.f949492",
81
- "@astryxdesign/theme-neutral": "0.1.4-canary.f949492",
78
+ "@astryxdesign/charts": "0.1.4-canary.fd7ab46",
79
+ "@astryxdesign/core": "0.1.4-canary.fd7ab46",
80
+ "@astryxdesign/lab": "0.1.4-canary.fd7ab46",
81
+ "@astryxdesign/theme-neutral": "0.1.4-canary.fd7ab46",
82
82
  "gpt-tokenizer": "^3.4.0"
83
83
  },
84
84
  "peerDependenciesMeta": {
@@ -96,10 +96,10 @@
96
96
  }
97
97
  },
98
98
  "devDependencies": {
99
- "@astryxdesign/charts": "0.1.4-canary.f949492",
100
- "@astryxdesign/core": "0.1.4-canary.f949492",
101
- "@astryxdesign/lab": "0.1.4-canary.f949492",
102
- "@astryxdesign/theme-neutral": "0.1.4-canary.f949492",
99
+ "@astryxdesign/charts": "0.1.4-canary.fd7ab46",
100
+ "@astryxdesign/core": "0.1.4-canary.fd7ab46",
101
+ "@astryxdesign/lab": "0.1.4-canary.fd7ab46",
102
+ "@astryxdesign/theme-neutral": "0.1.4-canary.fd7ab46",
103
103
  "gpt-tokenizer": "^3.4.0"
104
104
  },
105
105
  "scripts": {
@@ -19,6 +19,7 @@ describe('registry', () => {
19
19
  '0.1.0',
20
20
  '0.1.2',
21
21
  '0.1.3',
22
+ '0.1.5',
22
23
  ]);
23
24
  });
24
25
  });
@@ -20,6 +20,7 @@ const registry = new Map([
20
20
  ['0.1.0', () => import('./transforms/v0.1.0/index.mjs')],
21
21
  ['0.1.2', () => import('./transforms/v0.1.2/index.mjs')],
22
22
  ['0.1.3', () => import('./transforms/v0.1.3/index.mjs')],
23
+ ['0.1.5', () => import('./transforms/v0.1.5/index.mjs')],
23
24
  ]);
24
25
 
25
26
  // Re-export from the shared utility so registry callers and other consumers
@@ -0,0 +1,93 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ import {describe, it, expect} from 'vitest';
4
+
5
+ async function applyTransform(source) {
6
+ const {default: transform} =
7
+ await import('../rename-switch-label-spacing-default-to-hug.mjs');
8
+ const jscodeshift = (await import('jscodeshift')).default;
9
+ const j = jscodeshift.withParser('tsx');
10
+ const api = {jscodeshift: j, stats: () => {}, report: () => {}};
11
+ const file = {source, path: 'test.tsx'};
12
+ const result = transform(file, api);
13
+ return result ?? source;
14
+ }
15
+
16
+ describe('rename-switch-label-spacing-default-to-hug', () => {
17
+ it('renames Switch labelSpacing="default" to labelSpacing="hug"', async () => {
18
+ const input = `<Switch label="Notify" value={on} labelSpacing="default" />`;
19
+ const output = await applyTransform(input);
20
+ expect(output).toContain("'hug'");
21
+ expect(output).not.toContain('default');
22
+ });
23
+
24
+ it("renames expression-container labelSpacing={'default'}", async () => {
25
+ const input = `<Switch label="Notify" value={on} labelSpacing={'default'} />`;
26
+ const output = await applyTransform(input);
27
+ expect(output).toContain("'hug'");
28
+ expect(output).not.toContain("'default'");
29
+ });
30
+
31
+ it('renames default inside a ternary labelSpacing expression', async () => {
32
+ const input = `<Switch label="Notify" value={on} labelSpacing={isRow ? 'spread' : 'default'} />`;
33
+ const output = await applyTransform(input);
34
+ expect(output).toContain("'hug'");
35
+ expect(output).toContain("'spread'");
36
+ expect(output).not.toContain("'default'");
37
+ });
38
+
39
+ it('does not change labelSpacing="spread"', async () => {
40
+ const input = `<Switch label="Notify" value={on} labelSpacing="spread" />`;
41
+ const output = await applyTransform(input);
42
+ expect(output).toBe(input);
43
+ });
44
+
45
+ it('does not touch labelSpacing="default" on non-Switch components', async () => {
46
+ const input = `<LegacyToggle labelSpacing="default" />`;
47
+ const output = await applyTransform(input);
48
+ expect(output).toBe(input);
49
+ });
50
+
51
+ it('does not touch other props with a "default" value on Switch', async () => {
52
+ const input = `<Switch label="Notify" value={on} data-variant="default" />`;
53
+ const output = await applyTransform(input);
54
+ expect(output).toBe(input);
55
+ });
56
+
57
+ it('does not touch unrelated "default" string literals', async () => {
58
+ const input = `const theme = {mode: 'default', label: 'Default Mode'};`;
59
+ const output = await applyTransform(input);
60
+ expect(output).toBe(input);
61
+ });
62
+
63
+ it('renames object labelSpacing prop in files importing Switch', async () => {
64
+ const input = `import {Switch} from '@astryxdesign/core/Switch';
65
+ const cfg = {labelSpacing: 'default'};`;
66
+ const output = await applyTransform(input);
67
+ expect(output).toContain("labelSpacing: 'hug'");
68
+ expect(output).not.toContain("'default'");
69
+ });
70
+
71
+ it("renames labelSpacing: 'default' as const in files importing Switch", async () => {
72
+ const input = `import {Switch} from '@astryxdesign/core/Switch';
73
+ const ROWS = [{labelSpacing: 'default' as const, label: 'Adjacent'}];`;
74
+ const output = await applyTransform(input);
75
+ expect(output).toContain("'hug' as const");
76
+ expect(output).not.toContain("'default'");
77
+ });
78
+
79
+ it('does not rename object labelSpacing prop when Switch is not imported', async () => {
80
+ const input = `const cfg = {labelSpacing: 'default'};`;
81
+ const output = await applyTransform(input);
82
+ expect(output).toBe(input);
83
+ });
84
+
85
+ it('renames Storybook argTypes options in files importing Switch', async () => {
86
+ const input = `import {Switch} from '@astryxdesign/core/Switch';
87
+ const meta = {argTypes: {labelSpacing: {control: 'select', options: ['default', 'spread']}}};`;
88
+ const output = await applyTransform(input);
89
+ expect(output).toContain("'hug'");
90
+ expect(output).toContain("'spread'");
91
+ expect(output).not.toContain("'default'");
92
+ });
93
+ });
@@ -0,0 +1,19 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file v0.1.5 transform manifest
5
+ *
6
+ * Lists all codemods for the v0.1.5 release in the order they should run.
7
+ */
8
+
9
+ import renameSwitchLabelSpacingDefaultToHug, {
10
+ meta as renameSwitchLabelSpacingDefaultToHugMeta,
11
+ } from './rename-switch-label-spacing-default-to-hug.mjs';
12
+
13
+ export default [
14
+ {
15
+ name: 'rename-switch-label-spacing-default-to-hug',
16
+ transform: renameSwitchLabelSpacingDefaultToHug,
17
+ meta: renameSwitchLabelSpacingDefaultToHugMeta,
18
+ },
19
+ ];
@@ -0,0 +1,140 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Codemod: Rename Switch labelSpacing "default" to "hug"
5
+ * @see https://github.com/facebook/astryx/issues/2889
6
+ *
7
+ * The `labelSpacing="default"` value is renamed to `labelSpacing="hug"` so
8
+ * the name describes the behavior (label and switch hug each other), matching
9
+ * the behavioral register of the sibling `spread` value and the existing
10
+ * `hug`/`fill` layout vocabulary on TabList and SegmentedControl. The old
11
+ * `default` value keeps working as a deprecated alias, so this codemod is a
12
+ * cleanup, not a build fix.
13
+ *
14
+ * Transforms (scoped to Switch):
15
+ * - <Switch labelSpacing="default" /> → <Switch labelSpacing="hug" />
16
+ * - <Switch labelSpacing={'default'} /> → <Switch labelSpacing={'hug'} />
17
+ * - <Switch labelSpacing={cond ? 'default' : x}> → <Switch labelSpacing={cond ? 'hug' : x}>
18
+ * - Storybook argTypes: labelSpacing: { options: [..., 'default', ...] }
19
+ * → replaces 'default' with 'hug' (files importing Switch)
20
+ * - Object properties: { labelSpacing: 'default' } / { labelSpacing: 'default' as const }
21
+ * → { labelSpacing: 'hug' } (files importing Switch)
22
+ *
23
+ * The transform deliberately does NOT touch bare `'default'` strings that are
24
+ * not a `labelSpacing` value (an extremely common string otherwise), so it is
25
+ * safe to run across an entire codebase.
26
+ */
27
+
28
+ export const meta = {
29
+ title: 'Rename Switch labelSpacing "default" to "hug"',
30
+ description:
31
+ 'Renames the `labelSpacing="default"` value to `labelSpacing="hug"` on ' +
32
+ 'Switch. The old value keeps working as a deprecated alias. Also updates ' +
33
+ 'Storybook labelSpacing argTypes options and object-literal `labelSpacing` ' +
34
+ 'props in files that import Switch.',
35
+ pr: '#2889',
36
+ };
37
+
38
+ const OLD_VALUE = 'default';
39
+ const NEW_VALUE = 'hug';
40
+
41
+ /** Prop whose value is being renamed. */
42
+ const TARGET_PROP = 'labelSpacing';
43
+
44
+ /** Components whose `labelSpacing` prop accepts a SwitchLabelSpacing value. */
45
+ const TARGET_COMPONENTS = new Set(['Switch']);
46
+
47
+ export default function transformer(file, api) {
48
+ const j = api.jscodeshift;
49
+ const root = j(file.source);
50
+ let hasChanges = false;
51
+
52
+ /** Rewrite a string-literal node when it equals the old value. */
53
+ function renameStringLiteral(node) {
54
+ if (!node) return false;
55
+ if (
56
+ (node.type === 'StringLiteral' || node.type === 'Literal') &&
57
+ node.value === OLD_VALUE
58
+ ) {
59
+ node.value = NEW_VALUE;
60
+ if (node.raw) node.raw = `'${NEW_VALUE}'`;
61
+ return true;
62
+ }
63
+ return false;
64
+ }
65
+
66
+ /** Recursively rewrite the old value inside ternary/logical expressions. */
67
+ function renameInExpression(node) {
68
+ if (!node) return false;
69
+ let changed = false;
70
+ if (node.type === 'StringLiteral' || node.type === 'Literal') {
71
+ changed = renameStringLiteral(node) || changed;
72
+ } else if (node.type === 'ConditionalExpression') {
73
+ changed = renameInExpression(node.consequent) || changed;
74
+ changed = renameInExpression(node.alternate) || changed;
75
+ } else if (node.type === 'LogicalExpression') {
76
+ changed = renameInExpression(node.left) || changed;
77
+ changed = renameInExpression(node.right) || changed;
78
+ } else if (node.type === 'TSAsExpression') {
79
+ // `'default' as const` → rewrite the inner expression
80
+ changed = renameInExpression(node.expression) || changed;
81
+ }
82
+ return changed;
83
+ }
84
+
85
+ // --- 1. JSX attribute: labelSpacing="default" / labelSpacing={'default'} on Switch ---
86
+ root.find(j.JSXOpeningElement).forEach(path => {
87
+ const name = path.node.name;
88
+ const componentName = name.type === 'JSXIdentifier' ? name.name : null;
89
+ if (!componentName || !TARGET_COMPONENTS.has(componentName)) return;
90
+
91
+ path.node.attributes.forEach(attr => {
92
+ if (attr.type !== 'JSXAttribute') return;
93
+ if (!attr.name || attr.name.name !== TARGET_PROP) return;
94
+
95
+ const value = attr.value;
96
+ if (!value) return;
97
+
98
+ if (value.type === 'StringLiteral' || value.type === 'Literal') {
99
+ if (renameStringLiteral(value)) hasChanges = true;
100
+ } else if (value.type === 'JSXExpressionContainer') {
101
+ if (renameInExpression(value.expression)) hasChanges = true;
102
+ }
103
+ });
104
+ });
105
+
106
+ // Object-property / argTypes transforms only run in files that use a target
107
+ // component — keeps unrelated `{ labelSpacing: 'default' }` strings safe.
108
+ const importsTarget =
109
+ root
110
+ .find(j.ImportSpecifier)
111
+ .filter(p => TARGET_COMPONENTS.has(p.node.imported?.name))
112
+ .size() > 0;
113
+
114
+ if (importsTarget) {
115
+ const PropertyType = j.ObjectProperty ?? j.Property;
116
+
117
+ // --- 2. Object property: { labelSpacing: 'default' } / 'default' as const ---
118
+ root.find(PropertyType, {key: {name: TARGET_PROP}}).forEach(path => {
119
+ if (renameInExpression(path.node.value)) hasChanges = true;
120
+ });
121
+
122
+ // --- 3. Storybook argTypes: labelSpacing: { options: [..., 'default', ...] } ---
123
+ root.find(PropertyType, {key: {name: TARGET_PROP}}).forEach(path => {
124
+ const value = path.node.value;
125
+ if (!value || value.type !== 'ObjectExpression') return;
126
+
127
+ const optionsProp = value.properties.find(
128
+ p => p.key && (p.key.name === 'options' || p.key.value === 'options'),
129
+ );
130
+ if (optionsProp && optionsProp.value.type === 'ArrayExpression') {
131
+ optionsProp.value.elements.forEach(el => {
132
+ if (renameStringLiteral(el)) hasChanges = true;
133
+ });
134
+ }
135
+ });
136
+ }
137
+
138
+ if (!hasChanges) return undefined;
139
+ return root.toSource({quote: 'single'});
140
+ }
@@ -28,20 +28,10 @@ import * as fs from 'node:fs';
28
28
  import * as path from 'node:path';
29
29
  import * as os from 'node:os';
30
30
  import {fileURLToPath} from 'node:url';
31
+ import {ensureCoreBuilt} from './ensure-core-built.mjs';
31
32
 
32
33
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
33
34
  const CLI_BIN = path.resolve(__dirname, '../../bin/astryx.mjs');
34
- const REPO_ROOT = path.resolve(__dirname, '../../../..');
35
- const CORE_THEME_ENTRY = path.join(
36
- REPO_ROOT,
37
- 'packages/core/dist/theme/index.js',
38
- );
39
- // The fix reads core's shipped component declarations to decide whether an
40
- // interface is augmentable; those .d.ts files come from the same core build.
41
- const CORE_BUTTON_DTS = path.join(
42
- REPO_ROOT,
43
- 'packages/core/dist/Button/index.d.ts',
44
- );
45
35
 
46
36
  function runCli(args, cwd) {
47
37
  try {
@@ -68,14 +58,13 @@ function writeTheme(dir, contents) {
68
58
  return file;
69
59
  }
70
60
 
61
+ // Build core through the shared lock helper — this suite previously ran its
62
+ // own unguarded `if (!exists) pnpm -F core build`, and when Vitest scheduled
63
+ // it alongside the other build-theme suites on a fresh checkout, the
64
+ // concurrent builds collided on packages/core/dist (core's build starts by
65
+ // wiping dist), nondeterministically breaking whichever suite was mid-read.
71
66
  beforeAll(() => {
72
- if (!fs.existsSync(CORE_THEME_ENTRY) || !fs.existsSync(CORE_BUTTON_DTS)) {
73
- execFileSync('pnpm', ['-F', '@astryxdesign/core', 'build'], {
74
- cwd: REPO_ROOT,
75
- stdio: 'pipe',
76
- timeout: 180_000,
77
- });
78
- }
67
+ ensureCoreBuilt();
79
68
  }, 200_000);
80
69
 
81
70
  let tmpDir;
@@ -8,15 +8,10 @@ import {Center} from '@astryxdesign/core/Center';
8
8
  export default function AspectRatioCircleImage() {
9
9
  return (
10
10
  <Center width={300}>
11
- <AspectRatio ratio={1} shape="ellipse">
11
+ <AspectRatio ratio={1} shape="ellipse" fit="cover">
12
12
  <img
13
13
  src="https://lookaside.facebook.com/assets/astryx/light-home-square-1.png"
14
14
  alt="Circular image"
15
- style={{
16
- width: '100%',
17
- height: '100%',
18
- objectFit: 'cover',
19
- }}
20
15
  />
21
16
  </AspectRatio>
22
17
  </Center>
@@ -25,16 +25,11 @@ export default function AspectRatioImageGallery() {
25
25
  <Center width={600}>
26
26
  <Grid columns={3} gap={4} width="100%">
27
27
  {images.map(({id, alt}) => (
28
- <AspectRatio key={id} ratio={4 / 3}>
28
+ <AspectRatio key={id} ratio={4 / 3} fit="cover">
29
29
  <img
30
30
  src="https://lookaside.facebook.com/assets/astryx/illustrative-horizontal-1.png"
31
31
  alt={alt}
32
- style={{
33
- width: '100%',
34
- height: '100%',
35
- objectFit: 'cover',
36
- borderRadius: 8,
37
- }}
32
+ style={{borderRadius: 8}}
38
33
  />
39
34
  </AspectRatio>
40
35
  ))}
@@ -34,16 +34,13 @@ export default function AspectRatioShowcase() {
34
34
  <VStack key={label} gap={2} hAlign="center">
35
35
  <AspectRatio
36
36
  ratio={ratio}
37
+ fit="cover"
37
38
  style={{
38
39
  height: 120,
39
40
  width: 'auto',
40
41
  borderRadius: 'var(--radius-container)',
41
42
  }}>
42
- <img
43
- src={src}
44
- alt={alt}
45
- style={{width: '100%', height: '100%', objectFit: 'cover'}}
46
- />
43
+ <img src={src} alt={alt} />
47
44
  </AspectRatio>
48
45
  <Text type="supporting" color="secondary">
49
46
  {label}
@@ -8,15 +8,10 @@ import {Center} from '@astryxdesign/core/Center';
8
8
  export default function AspectRatioSquareImage() {
9
9
  return (
10
10
  <Center width={300}>
11
- <AspectRatio ratio={1}>
11
+ <AspectRatio ratio={1} fit="cover">
12
12
  <img
13
13
  src="https://lookaside.facebook.com/assets/astryx/light-home-square-1.png"
14
14
  alt="1:1 square"
15
- style={{
16
- width: '100%',
17
- height: '100%',
18
- objectFit: 'cover',
19
- }}
20
15
  />
21
16
  </AspectRatio>
22
17
  </Center>
@@ -8,15 +8,10 @@ import {Center} from '@astryxdesign/core/Center';
8
8
  export default function AspectRatioWidescreen() {
9
9
  return (
10
10
  <Center width={600}>
11
- <AspectRatio ratio={16 / 9}>
11
+ <AspectRatio ratio={16 / 9} fit="cover">
12
12
  <img
13
13
  src="https://lookaside.facebook.com/assets/astryx/light-scene-horizontal-1.png"
14
14
  alt="16:9 widescreen"
15
- style={{
16
- width: '100%',
17
- height: '100%',
18
- objectFit: 'cover',
19
- }}
20
15
  />
21
16
  </AspectRatio>
22
17
  </Center>
@@ -439,8 +439,11 @@ export default function KanbanBoardTemplate() {
439
439
  let cb = columnRefCbs.current.get(id);
440
440
  if (!cb) {
441
441
  cb = el => {
442
- if (el) {columnEls.current.set(id, el);}
443
- else {columnEls.current.delete(id);}
442
+ if (el) {
443
+ columnEls.current.set(id, el);
444
+ } else {
445
+ columnEls.current.delete(id);
446
+ }
444
447
  };
445
448
  columnRefCbs.current.set(id, cb);
446
449
  }
@@ -451,8 +454,11 @@ export default function KanbanBoardTemplate() {
451
454
  let cb = cardRefCbs.current.get(id);
452
455
  if (!cb) {
453
456
  cb = el => {
454
- if (el) {cardEls.current.set(id, el);}
455
- else {cardEls.current.delete(id);}
457
+ if (el) {
458
+ cardEls.current.set(id, el);
459
+ } else {
460
+ cardEls.current.delete(id);
461
+ }
456
462
  };
457
463
  cardRefCbs.current.set(id, cb);
458
464
  }
@@ -487,7 +493,9 @@ export default function KanbanBoardTemplate() {
487
493
  ): DropTarget | null => {
488
494
  for (const [colId, el] of Array.from(columnEls.current.entries())) {
489
495
  const r = el.getBoundingClientRect();
490
- if (px < r.left || px > r.right || py < r.top || py > r.bottom) {continue;}
496
+ if (px < r.left || px > r.right || py < r.top || py > r.bottom) {
497
+ continue;
498
+ }
491
499
 
492
500
  const ids = itemsByColumn[colId]
493
501
  .filter(it => it.id !== draggedId)
@@ -496,7 +504,9 @@ export default function KanbanBoardTemplate() {
496
504
  let index = ids.length;
497
505
  for (let i = 0; i < ids.length; i++) {
498
506
  const cardEl = cardEls.current.get(ids[i]);
499
- if (!cardEl) {continue;}
507
+ if (!cardEl) {
508
+ continue;
509
+ }
500
510
  const cr = cardEl.getBoundingClientRect();
501
511
  if (py < cr.top + cr.height / 2) {
502
512
  index = i;
@@ -513,21 +523,27 @@ export default function KanbanBoardTemplate() {
513
523
  const commitDrag = (id: string, target: DropTarget) => {
514
524
  setItems(prev => {
515
525
  const moved = prev.find(it => it.id === id);
516
- if (!moved) {return prev;}
526
+ if (!moved) {
527
+ return prev;
528
+ }
517
529
 
518
530
  const rest = prev.filter(it => it.id !== id);
519
531
  const updated: WorkItem = {...moved, column: target.column};
520
532
  const colItems = rest.filter(it => it.column === target.column);
521
533
  const anchor = colItems[target.index];
522
534
 
523
- if (!anchor) {return [...rest, updated];}
535
+ if (!anchor) {
536
+ return [...rest, updated];
537
+ }
524
538
  const at = rest.indexOf(anchor);
525
539
  return [...rest.slice(0, at), updated, ...rest.slice(at)];
526
540
  });
527
541
  };
528
542
 
529
543
  const onCardPointerDown = (e: ReactPointerEvent, id: string) => {
530
- if (e.button !== 0) {return;}
544
+ if (e.button !== 0) {
545
+ return;
546
+ }
531
547
  // Let the card's own controls (the actions menu) handle the press.
532
548
  if (
533
549
  (e.target as HTMLElement).closest(
@@ -538,7 +554,9 @@ export default function KanbanBoardTemplate() {
538
554
  }
539
555
 
540
556
  const el = cardEls.current.get(id);
541
- if (!el) {return;}
557
+ if (!el) {
558
+ return;
559
+ }
542
560
 
543
561
  const rect = el.getBoundingClientRect();
544
562
  const startX = e.clientX;
@@ -574,7 +592,9 @@ export default function KanbanBoardTemplate() {
574
592
 
575
593
  const onUp = () => {
576
594
  teardownRef.current?.();
577
- if (started && target) {commitDrag(id, target);}
595
+ if (started && target) {
596
+ commitDrag(id, target);
597
+ }
578
598
  setDrag(null);
579
599
  };
580
600
 
@@ -594,7 +614,9 @@ export default function KanbanBoardTemplate() {
594
614
 
595
615
  // Suppress selection while dragging and detach listeners on unmount.
596
616
  useEffect(() => {
597
- if (!isDragging) {return;}
617
+ if (!isDragging) {
618
+ return;
619
+ }
598
620
  const previous = document.body.style.userSelect;
599
621
  document.body.style.userSelect = 'none';
600
622
  return () => {
@@ -612,7 +634,9 @@ export default function KanbanBoardTemplate() {
612
634
  const ghostTarget =
613
635
  drag && drag.target && drag.target.column === colId ? drag : null;
614
636
 
615
- if (visible.length === 0 && !ghostTarget) {return null;}
637
+ if (visible.length === 0 && !ghostTarget) {
638
+ return null;
639
+ }
616
640
 
617
641
  const nodes: ReactNode[] = visible.map(it => (
618
642
  <BoardCard