Skip to content

feat: cds v9#433

Draft
haoruikun-cb wants to merge 92 commits intomasterfrom
cds-v9
Draft

feat: cds v9#433
haoruikun-cb wants to merge 92 commits intomasterfrom
cds-v9

Conversation

@haoruikun-cb
Copy link
Copy Markdown
Contributor

What changed? Why?

Root cause (required for bugfixes)

UI changes

iOS Old iOS New
old screenshot new screenshot
Android Old Android New
old screenshot new screenshot
Web Old Web New
old screenshot new screenshot

Testing

How has it been tested?

  • Unit tests
  • Interaction tests
  • Pseudo State tests
  • Manual - Web
  • Manual - Android (Emulator / Device)
  • Manual - iOS (Emulator / Device)

Testing instructions

Illustrations/Icons Checklist

Required if this PR changes files under packages/illustrations/** or packages/icons/**

  • verified visreg changes with Terran (include link to visreg run/approval)
  • all illustration/icons names have been reviewed by Dom and/or Terran

Change management

type=routine
risk=low
impact=sev5

automerge=false

cb-ekuersch and others added 28 commits January 26, 2026 17:24
* bump react and react-dom package versions
* update unsafe ref access when merging internal and customer refs
* upgrade storybook (storybook@latest upgrade)
* upgrade react eslint plugin and disable its new rules for now
* migrate CSF2 stories to CSF3 format (https://storybook.js.org/blog/storybook-csf3-is-here/)
* upgrade react testing library and replace outdated imports
* upgrade react-native & other Expo-managed dependencies
* introduce new test-expo app for mobile testing
* remove react-native accessibility engine and replace with our own fork compatible with our react native version
* remove deprecated @testing-library/jest-native and migrate tests away from react-test-renderer
* migrate toHaveAccessibilityState to granular matchers (toBeChecked, toBeSelected, toBeDisabled)
* update formatting of yarn constraint for matching dependency versions
* create new yarn constraint for enforcing matching peer and dev dependencies
* remove odd react-dom peer dep on common package
* remove unused peer desps of cds-mobile
* remove cds-mobile useStatusBarHeight and swtich to using safe area context insets (#376)
* setup manual mocks for react-native-worklets
* update versions referenced in mobile docs component metadata
* fix layout issues in tooltip and tour for android edge to edge display
* rewrite mobile stepper animation using reanimated (#387)
* chore: fix react version resolution issue
* Add isolated prop to web ThemeProvider and update managed Portal component

* Add documentation for PortalProvider setup

* Improve documentation around ThemeProvider

---------

Co-authored-by: Cody Nova <cody.nova@coinbase.com>
* feat: consolidate border props in Cell component

* fix: lint issues

* chore: address code review feedback

* feat: refactored useResolveResponsiveProp
…or mobile title Text (#401)

* feat: deprecated individual Text components

* feat: updated internal usage of individual text components

* fix: lint issue

* feat: updated deprecation comment for title componnents in mobile to prevent a11y regression

* feat: added default accessibilityRole for header in mobile

* feat: updated web doc for Text on a11y
* feat: added inverse variant and modified tertiary variant for Button

* feat: address code reivew by adjusting doc site wording and remvoe uncessary unit tests

* feat: removed foregroundMuted variant from Button

* feat: removed foregroundMuted variant in Button and IconButton

* feat: added runtime fallback to prevent crashing by the old variant

* feat: clean up examples and revised safety net

* feat: clean up remaining foregroundMuted

* feat: remove safety fallback for Button variants
* feat: shortened Pressable CSS variables

* chore: trigger CI rerun

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
* chore: remove interactable height constant and refactor several components to use content-based sizing instead of predetermined height

* chore: remove more fixed dimensions from components

* refactor InputStack to enable simpler spacing/layout in select/combobox components
* feat: simplify carousel pagination in v9

* feat: limit title line count

* Fix title on mobile
@cb-heimdall
Copy link
Copy Markdown
Collaborator

cb-heimdall commented Feb 24, 2026

🟡 Heimdall Review Status

Requirement Status More Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 1
Global minimum 0
Max 1
1
1 if commit is unverified 0
Sum 1
CODEOWNERS 🟡 See below

🟡 CODEOWNERS

Code Owner Status Calculation
ui-systems-eng-team 🟡 0/1
Denominator calculation
Additional CODEOWNERS Requirement
Show calculation
Sum 0
0
From CODEOWNERS 1
Sum 1

* feat (CDS-1613): button variant migrator

* chore: added jsdoc for button variant transform
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 27, 2026


// Replace assets/index.android.bundle in the APK (cd into patchDir so zip path is correct)
console.log(`\nPatching bundle into APK: ${apk}...`);
execSync(`zip -u ${apk} assets/index.android.bundle`, { cwd: patchDir, stdio: 'inherit' });

Check warning

Code scanning / CodeQL

Shell command built from environment values Medium test

This shell command depends on an uncontrolled
absolute path
.

Copilot Autofix

AI 15 days ago

In general, to fix this type of problem you should stop constructing a single shell command string with interpolated environment or path values and instead pass the command and its arguments separately (for example, using execFileSync or spawn with an argument array). This prevents the shell from re‑parsing the path value and interpreting special characters.

For this specific code, the best fix with minimal behavioral changes is:

  • Replace the execSync call on line 138 that uses a backticked string with a call to spawn (already imported) or execFileSync-style usage where:
    • The command is "zip".
    • The arguments are ['-u', apk, 'assets/index.android.bundle'].
    • cwd remains patchDir, and stdio: 'inherit' is preserved.
    • We ensure the process exits successfully, otherwise we throw an error to mimic execSync’s behavior.

Because spawn is already imported at the top, we can create a small helper in this file (or inline logic) to run zip synchronously and check its exit code. Since we must not assume other project utilities beyond what we see, we’ll add a small runZipUpdate helper function within this file, just above applyBundle, which uses spawn to execute zip with an argument array and returns a promise that rejects on non‑zero exit. We then await it from applyBundle. This preserves functionality while removing the shell command construction.

No other lines need to change, and no new imports are required.

Suggested changeset 1
apps/test-expo/scripts/utils/AndroidBuilder.mjs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/apps/test-expo/scripts/utils/AndroidBuilder.mjs b/apps/test-expo/scripts/utils/AndroidBuilder.mjs
--- a/apps/test-expo/scripts/utils/AndroidBuilder.mjs
+++ b/apps/test-expo/scripts/utils/AndroidBuilder.mjs
@@ -135,8 +135,25 @@
 
     // Replace assets/index.android.bundle in the APK (cd into patchDir so zip path is correct)
     console.log(`\nPatching bundle into APK: ${apk}...`);
-    execSync(`zip -u ${apk} assets/index.android.bundle`, { cwd: patchDir, stdio: 'inherit' });
+    await new Promise((resolve, reject) => {
+      const child = spawn('zip', ['-u', apk, 'assets/index.android.bundle'], {
+        cwd: patchDir,
+        stdio: 'inherit',
+      });
 
+      child.on('error', (err) => {
+        reject(err);
+      });
+
+      child.on('exit', (code) => {
+        if (code === 0) {
+          resolve();
+        } else {
+          reject(new Error(`zip exited with code ${code}`));
+        }
+      });
+    });
+
     // Re-align (zip modification breaks alignment) then re-sign with debug keystore
     execSync(`zipalign -f 4 ${apk} ${alignedApk}`, { stdio: 'inherit' });
     await fs.rename(alignedApk, apk);
EOF
@@ -135,8 +135,25 @@

// Replace assets/index.android.bundle in the APK (cd into patchDir so zip path is correct)
console.log(`\nPatching bundle into APK: ${apk}...`);
execSync(`zip -u ${apk} assets/index.android.bundle`, { cwd: patchDir, stdio: 'inherit' });
await new Promise((resolve, reject) => {
const child = spawn('zip', ['-u', apk, 'assets/index.android.bundle'], {
cwd: patchDir,
stdio: 'inherit',
});

child.on('error', (err) => {
reject(err);
});

child.on('exit', (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`zip exited with code ${code}`));
}
});
});

// Re-align (zip modification breaks alignment) then re-sign with debug keystore
execSync(`zipalign -f 4 ${apk} ${alignedApk}`, { stdio: 'inherit' });
await fs.rename(alignedApk, apk);
Copilot is powered by AI and may make mistakes. Always verify output.
execSync(`zip -u ${apk} assets/index.android.bundle`, { cwd: patchDir, stdio: 'inherit' });

// Re-align (zip modification breaks alignment) then re-sign with debug keystore
execSync(`zipalign -f 4 ${apk} ${alignedApk}`, { stdio: 'inherit' });

Check warning

Code scanning / CodeQL

Shell command built from environment values Medium test

This shell command depends on an uncontrolled
absolute path
.

Copilot Autofix

AI 15 days ago

In general, the fix is to avoid building a single shell command string that includes environment- or configuration-derived paths, and instead execute the program directly with an argument array so that the shell is not invoked and the arguments are not reinterpreted. For Node.js, this means using execFileSync (or spawn/spawnSync) with a command name and an array of arguments, rather than execSync with a combined string.

Concretely in AndroidBuilder.mjs, we should:

  • Import execFileSync from node:child_process.
  • Replace the three execSync calls in applyBundle with execFileSync (or spawnSync) using argument arrays:
    • zip -u ${apk} assets/index.android.bundleexecFileSync('zip', ['-u', apk, 'assets/index.android.bundle'], { cwd: patchDir, stdio: 'inherit' }).
    • zipalign -f 4 ${apk} ${alignedApk}execFileSync('zipalign', ['-f', '4', apk, alignedApk], { stdio: 'inherit' }).
    • apksigner sign --ks ${debugKeystore} --ks-pass pass:android --key-pass pass:android ${apk}execFileSync('apksigner', ['sign', '--ks', debugKeystore, '--ks-pass', 'pass:android', '--key-pass', 'pass:android', apk], { stdio: 'inherit' }).

These changes keep the behavior identical (same commands, same working directory, same stdio handling) while ensuring environment-derived paths are passed as raw arguments rather than interpolated into a shell command string.

Suggested changeset 1
apps/test-expo/scripts/utils/AndroidBuilder.mjs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/apps/test-expo/scripts/utils/AndroidBuilder.mjs b/apps/test-expo/scripts/utils/AndroidBuilder.mjs
--- a/apps/test-expo/scripts/utils/AndroidBuilder.mjs
+++ b/apps/test-expo/scripts/utils/AndroidBuilder.mjs
@@ -1,4 +1,4 @@
-import { execSync, spawn } from 'node:child_process';
+import { execFileSync, execSync, spawn } from 'node:child_process';
 import fs from 'node:fs/promises';
 import path from 'node:path';
 
@@ -135,15 +135,19 @@
 
     // Replace assets/index.android.bundle in the APK (cd into patchDir so zip path is correct)
     console.log(`\nPatching bundle into APK: ${apk}...`);
-    execSync(`zip -u ${apk} assets/index.android.bundle`, { cwd: patchDir, stdio: 'inherit' });
+    execFileSync('zip', ['-u', apk, 'assets/index.android.bundle'], {
+      cwd: patchDir,
+      stdio: 'inherit',
+    });
 
     // Re-align (zip modification breaks alignment) then re-sign with debug keystore
-    execSync(`zipalign -f 4 ${apk} ${alignedApk}`, { stdio: 'inherit' });
+    execFileSync('zipalign', ['-f', '4', apk, alignedApk], { stdio: 'inherit' });
     await fs.rename(alignedApk, apk);
 
     const debugKeystore = path.resolve(process.env.HOME, '.android/debug.keystore');
-    execSync(
-      `apksigner sign --ks ${debugKeystore} --ks-pass pass:android --key-pass pass:android ${apk}`,
+    execFileSync(
+      'apksigner',
+      ['sign', '--ks', debugKeystore, '--ks-pass', 'pass:android', '--key-pass', 'pass:android', apk],
       { stdio: 'inherit' },
     );
 
EOF
@@ -1,4 +1,4 @@
import { execSync, spawn } from 'node:child_process';
import { execFileSync, execSync, spawn } from 'node:child_process';
import fs from 'node:fs/promises';
import path from 'node:path';

@@ -135,15 +135,19 @@

// Replace assets/index.android.bundle in the APK (cd into patchDir so zip path is correct)
console.log(`\nPatching bundle into APK: ${apk}...`);
execSync(`zip -u ${apk} assets/index.android.bundle`, { cwd: patchDir, stdio: 'inherit' });
execFileSync('zip', ['-u', apk, 'assets/index.android.bundle'], {
cwd: patchDir,
stdio: 'inherit',
});

// Re-align (zip modification breaks alignment) then re-sign with debug keystore
execSync(`zipalign -f 4 ${apk} ${alignedApk}`, { stdio: 'inherit' });
execFileSync('zipalign', ['-f', '4', apk, alignedApk], { stdio: 'inherit' });
await fs.rename(alignedApk, apk);

const debugKeystore = path.resolve(process.env.HOME, '.android/debug.keystore');
execSync(
`apksigner sign --ks ${debugKeystore} --ks-pass pass:android --key-pass pass:android ${apk}`,
execFileSync(
'apksigner',
['sign', '--ks', debugKeystore, '--ks-pass', 'pass:android', '--key-pass', 'pass:android', apk],
{ stdio: 'inherit' },
);

Copilot is powered by AI and may make mistakes. Always verify output.

const debugKeystore = path.resolve(process.env.HOME, '.android/debug.keystore');
execSync(
`apksigner sign --ks ${debugKeystore} --ks-pass pass:android --key-pass pass:android ${apk}`,

Check warning

Code scanning / CodeQL

Shell command built from environment values Medium test

This shell command depends on an uncontrolled
absolute path
.
This shell command depends on an uncontrolled
absolute path
.

Copilot Autofix

AI 15 days ago

In general, to fix this class of problem you should avoid constructing a single shell command string with interpolated environment-derived values. Instead, call the command with its arguments specified separately so that Node bypasses the shell and passes arguments directly to the executable. For child_process.execSync, that means switching to execFileSync (or using spawn/spawnSync) with an argument array; each path (like apk and debugKeystore) then becomes a separate element in that array and is not subject to shell parsing.

For this specific code, the safest change that preserves behavior is:

  • Replace the execSync invocation at lines 145–148 with a call to execFileSync('apksigner', [...args...], { stdio: 'inherit' }).
  • Build the argument list as an array: ['sign', '--ks', debugKeystore, '--ks-pass', 'pass:android', '--key-pass', 'pass:android', apk].
  • Import execFileSync from node:child_process alongside the existing imports so it is available.
  • Leave the rest of the method unchanged; we are not altering options, flags, or logging, just how the command is invoked.

Concretely:

  • In apps/test-expo/scripts/utils/AndroidBuilder.mjs, update the import on line 1 from import { execSync, spawn } from 'node:child_process'; to also import execFileSync.
  • Replace the execSync call for apksigner (lines 145–148) with a corresponding execFileSync call using an argument array, keeping stdio: 'inherit' in the options.

No other regions or files need changes to fix this particular alert.

Suggested changeset 1
apps/test-expo/scripts/utils/AndroidBuilder.mjs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/apps/test-expo/scripts/utils/AndroidBuilder.mjs b/apps/test-expo/scripts/utils/AndroidBuilder.mjs
--- a/apps/test-expo/scripts/utils/AndroidBuilder.mjs
+++ b/apps/test-expo/scripts/utils/AndroidBuilder.mjs
@@ -1,4 +1,4 @@
-import { execSync, spawn } from 'node:child_process';
+import { execFileSync, execSync, spawn } from 'node:child_process';
 import fs from 'node:fs/promises';
 import path from 'node:path';
 
@@ -142,8 +142,9 @@
     await fs.rename(alignedApk, apk);
 
     const debugKeystore = path.resolve(process.env.HOME, '.android/debug.keystore');
-    execSync(
-      `apksigner sign --ks ${debugKeystore} --ks-pass pass:android --key-pass pass:android ${apk}`,
+    execFileSync(
+      'apksigner',
+      ['sign', '--ks', debugKeystore, '--ks-pass', 'pass:android', '--key-pass', 'pass:android', apk],
       { stdio: 'inherit' },
     );
 
EOF
@@ -1,4 +1,4 @@
import { execSync, spawn } from 'node:child_process';
import { execFileSync, execSync, spawn } from 'node:child_process';
import fs from 'node:fs/promises';
import path from 'node:path';

@@ -142,8 +142,9 @@
await fs.rename(alignedApk, apk);

const debugKeystore = path.resolve(process.env.HOME, '.android/debug.keystore');
execSync(
`apksigner sign --ks ${debugKeystore} --ks-pass pass:android --key-pass pass:android ${apk}`,
execFileSync(
'apksigner',
['sign', '--ks', debugKeystore, '--ks-pass', 'pass:android', '--key-pass', 'pass:android', apk],
{ stdio: 'inherit' },
);

Copilot is powered by AI and may make mistakes. Always verify output.
adrienzheng-cb and others added 10 commits March 30, 2026 10:06
1. restored and deprecated progressSpringConfig prop and defaultProgressSpringConfig;
2. restored and deprecated animationConfig.
* feat: add indeterminate ProgressCircle (#501)

* feat: add indeterminate ProgressCircle, unify progress size and float-label math

- Add indeterminate prop to ProgressCircle (web & mobile): spinning state with
  configurable weight, default stroke ratio 0.11; animate full SVG on mobile,
  CSS keyframes on web; hide default content when indeterminate.
- Add getProgressSize(weight) in common and deprecate useProgressSize; use
  getProgressSize in ProgressBar and ProgressCircle on both platforms.
- Simplify ProgressBarWithFloatLabel (web & mobile): remove usePreviousValues,
  useIsoEffect, and imperative animation; use shared getEndTranslateX so float
  label trailing edge follows fill end (containerWidth * progress - textWidth);
  web uses useMotionProps + MotionBox, mobile animates translateX to target.
- ProgressBar/ProgressCircle: progress optional with default 0; add originX/
  originY in getProgressCircleParams; web ProgressCircle uses pathLength=1.
- Deprecate Spinner (web & mobile) in favor of indeterminate ProgressCircle.
- ProgressBar tests: update float-label position expectation (80) and accept
  transform none/translateX(0) for zero progress; iconSvgMap regenerated.

* update test and deprecation message

* add more button stories

* a11y fix

* remove 0.11

* add progressCircleSize prop

* update changelogs and package versions

* add progressCircleSize prop to IconButton

* fix removal version

* chore: deprecate CardGroup (#560)

* chore: deprecate CardGroup

* Update changelog

* feat: added cds skills for distribution (#561)

* chore: deprecate old card (#562)

* chore: deprecate old card

1. derpecated Card and related components and types
2. added derepcate-cds-api-skill

* update changelogs

* tweaks

---------

Co-authored-by: Hunter Copp <huntercolecopp@gmail.com>
Co-authored-by: Harry <ruikun.hao@coinbase.com>
* chore: deprecate visualization packages

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: add transform for useMergerefs

* organize

* cleanup

* include both internal and open source name space

* add package scope
* fix: scrubber beacon label immediate updates

* Bump version
* fix: mobile scrubber initial load glitch

* Bump version
* chore: bump version to 1.0.1 and update visual verification instructions

* Update version in cds-code and design-to-code skills to 1.0.1
* Revise visual verification instructions to include asking users for screenshots when inspection tooling is unavailable

* feat: added readme for human readers

* chore: format

* feat: dynamically determine cds import scope for skill

* fix: exclude v7 packages from CDS import scope determination

* feat: enhance CDS package discovery to include runtime detection

- Updated the discovery script to identify the CDS runtime (web or mobile) based on installed packages.
- Revised documentation to reflect changes in the discovery process, consolidating steps for clarity.
* [StepSecurity] Apply security best practices

Signed-off-by: StepSecurity Bot <bot@stepsecurity.io>

* Fix lint

---------

Signed-off-by: StepSecurity Bot <bot@stepsecurity.io>
Co-authored-by: stepsecurity-app[bot] <188008098+stepsecurity-app[bot]@users.noreply.github.com>
Co-authored-by: Hunter Copp <huntercolecopp@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment