Repository: sveltejs/svelte (JavaScript)

Issue:
`$state.snapshot` does not preserve holes in a sparse array
### Describe the bug

When attempting to create an array with empty slots/holes, the empty slots become "undefined" rather than holes, when using `$state.snapshot(state)`:

```svelte
<script>
    let arr = []

    arr[5] = true
    console.log('native', arr) // native (6) [empty x 5, true]

    let state = $state([])
    state[5] = true
    console.log('state', $state.snapshot(state)) // state (6) [undefined, undefined, undefined, undefined, undefined, true]
</script>

<div>
    <code>2 in arr</code>: {2 in arr} <!-- false -->
    <br>
    <code>5 in arr</code>: {5 in arr} <!-- true -->
</div>
<div>
    <code>2 in $state.snapshot(state)</code>: {2 in $state.snapshot(state)} <!-- true (expected false) -->
    <br>
    <code>5 in $state.snapshot(state)</code>: {5 in $state.snapshot(state)} <!-- true -->
</div>
<div>
    <code>2 in state</code>: {2 in state} <!-- false -->
    <br>
    <code>5 in state</code>: {5 in state} <!-- true -->
</div>
```

### Reproduction

https://svelte.dev/playground/08ac2b6b28754224a77b9924f7d1a483?version=5.10.0

### Logs

```shell
Console was cleared
running Svelte compiler version 5.10.0
native (6) [empty × 5, true]
state (6) [undefined, undefined, undefined, undefined, undefined, true]
```


### System Info

```shell
System:
    OS: Windows 11 10.0.22631
    CPU: (12) x64 Intel(R) Core(TM) i7-8700K CPU @ 3.70GHz
    Memory: 17.48 GB / 31.93 GB
  Binaries:
    Node: 20.17.0 - C:\Program Files\nodejs\node.EXE
    npm: 10.8.2 - C:\Program Files\nodejs\npm.CMD
    bun: 1.1.37 - ~\.bun\bin\bun.EXE
  Browsers: {}

### Browser
Vivaldi	7.0.3495.23 (Stable channel) (64-bit) 
Revision	72d89227db29f524d89b1af38163f46e4ed7746e
OS	Windows 11 Version 23H2 (Build 22631.4460)
JavaScript	V8 13.0.245.20
User Agent	Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36
```


### Severity

annoyance


Discussion:
It seems to happen when creating a snapshot:
```
2 in arr: false
5 in arr: true
2 in $state.snapshot(state): true
5 in $state.snapshot(state): true
2 in state: false
5 in state: true
```



Patch:
diff --git a/.changeset/clean-bobcats-fail.md b/.changeset/clean-bobcats-fail.md
new file mode 100644
index 000000000000..00d8286bcb28
--- /dev/null
+++ b/.changeset/clean-bobcats-fail.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: ensure $state.snapshot clones holey arrays correctly
diff --git a/packages/svelte/src/internal/shared/clone.js b/packages/svelte/src/internal/shared/clone.js
index cef14bc14f9e..4632fc3d68ed 100644
--- a/packages/svelte/src/internal/shared/clone.js
+++ b/packages/svelte/src/internal/shared/clone.js
@@ -54,22 +54,25 @@ export function snapshot(value, skip_warning = false) {
  */
 function clone(value, cloned, path, paths, original = null) {
 	if (typeof value === 'object' && value !== null) {
-		const unwrapped = cloned.get(value);
+		var unwrapped = cloned.get(value);
 		if (unwrapped !== undefined) return unwrapped;
 
 		if (value instanceof Map) return /** @type {Snapshot<T>} */ (new Map(value));
 		if (value instanceof Set) return /** @type {Snapshot<T>} */ (new Set(value));
 
 		if (is_array(value)) {
-			const copy = /** @type {Snapshot<any>} */ ([]);
+			var copy = /** @type {Snapshot<any>} */ (Array(value.length));
 			cloned.set(value, copy);
 
 			if (original !== null) {
 				cloned.set(original, copy);
 			}
 
-			for (let i = 0; i < value.length; i += 1) {
-				copy.push(clone(value[i], cloned, DEV ? `${path}[${i}]` : path, paths));
+			for (var i = 0; i < value.length; i += 1) {
+				var element = value[i];
+				if (i in value) {
+					copy[i] = clone(element, cloned, DEV ? `${path}[${i}]` : path, paths);
+				}
 			}
 
 			return copy;
@@ -77,7 +80,7 @@ function clone(value, cloned, path, paths, original = null) {
 
 		if (get_prototype_of(value) === object_prototype) {
 			/** @type {Snapshot<any>} */
-			const copy = {};
+			copy = {};
 			cloned.set(value, copy);
 
 			if (original !== null) {


Test patch:
diff --git a/packages/svelte/tests/runtime-runes/samples/state-snapshot-array-holes/_config.js b/packages/svelte/tests/runtime-runes/samples/state-snapshot-array-holes/_config.js
new file mode 100644
index 000000000000..707c92118bf3
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/state-snapshot-array-holes/_config.js
@@ -0,0 +1,5 @@
+import { test } from '../../test';
+
+export default test({
+	html: `<div>false</div><div>true</div>`
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/state-snapshot-array-holes/main.svelte b/packages/svelte/tests/runtime-runes/samples/state-snapshot-array-holes/main.svelte
new file mode 100644
index 000000000000..cb5c116f37f1
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/state-snapshot-array-holes/main.svelte
@@ -0,0 +1,10 @@
+<script>
+	let arr = []
+	arr[5] = true
+
+	let state = $state([])
+	state[5] = true
+</script>
+
+<div>{2 in $state.snapshot(state)}</div>
+<div>{5 in $state.snapshot(state)}</div>