fix(demos): eliminate each_key_duplicate crash in 22 component demos

The event-trace inspector in each demo keyed its `{#each}` by a
`Date.now()` timestamp (`entry.at` / `h.at`). A single interaction can
stamp `data-event` on several elements within the same millisecond, so
two trace entries share the key and Svelte throws `each_key_duplicate`
(reported on dropdown-menu).

The trace is an ephemeral, 3-item, text-only log with no transitions or
stateful children, so positional reconciliation is correct: drop the
timestamp key (the each becomes unkeyed). The `fmtTime(...at)` display is
left intact.

Swept all 22 demos carrying the pattern (21 keyed by `entry.at` + announce
by `h.at`): dropdown-menu, context-menu, menubar, navigation-menu, listbox,
grid-list, table, tree-view, tree-grid, feed, command, carousel, drag-drop,
clipboard, announce, alert-dialog, button, color-field, link-preview,
range-calendar, time-field, time-range-field.

Verified: `as \w+ (\w+.at)` -> 0 occurrences site-wide; `npm run check`
0 errors; browser repro on dropdown-menu + table (burst of events, no
each_key_duplicate, console clean).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
active-uix
dev 4 months ago
parent e97fdc53c2
commit f8056f438b

@ -280,7 +280,7 @@
{#if trace.length === 0} {#if trace.length === 0}
<span>open the dialog, click Cancel/Action to see Dialog events</span> <span>open the dialog, click Cancel/Action to see Dialog events</span>
{:else} {:else}
{#each trace.slice(0, 3) as entry (entry.at)} {#each trace.slice(0, 3) as entry}
<span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span> <span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span>
<span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span> <span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span>
{/each} {/each}

@ -265,7 +265,7 @@
{#if history.length === 0} {#if history.length === 0}
<span>type a message and Enter, or pick a scenario — the live region inspector above shows what the AT receives</span> <span>type a message and Enter, or pick a scenario — the live region inspector above shows what the AT receives</span>
{:else} {:else}
{#each history.slice(0, 3) as h (h.at)} {#each history.slice(0, 3) as h}
<span><strong>{h.priority}</strong> · "{h.msg}"</span> <span><strong>{h.priority}</strong> · "{h.msg}"</span>
<span style="color: var(--uix-text-faint)">{fmtTime(h.at)}</span> <span style="color: var(--uix-text-faint)">{fmtTime(h.at)}</span>
{/each} {/each}

@ -225,7 +225,7 @@
{#if trace.length === 0} {#if trace.length === 0}
<span>click the button to fire commit-action</span> <span>click the button to fire commit-action</span>
{:else} {:else}
{#each trace.slice(0, 3) as entry (entry.at)} {#each trace.slice(0, 3) as entry}
<span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span> <span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span>
<span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span> <span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span>
{/each} {/each}

@ -328,7 +328,7 @@
{#if trace.length === 0} {#if trace.length === 0}
<span>arrow keys · drag · click indicators</span> <span>arrow keys · drag · click indicators</span>
{:else} {:else}
{#each trace.slice(0, 3) as entry (entry.at)} {#each trace.slice(0, 3) as entry}
<span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span> <span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span>
<span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span> <span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span>
{/each} {/each}

@ -210,7 +210,7 @@
{#if log.length === 0} {#if log.length === 0}
<span>type a value above, click Copy — the Indicator chip pops for {timeout}ms; <code>onCopy</code> / <code>onError</code> entries land here.</span> <span>type a value above, click Copy — the Indicator chip pops for {timeout}ms; <code>onCopy</code> / <code>onError</code> entries land here.</span>
{:else} {:else}
{#each log.slice(0, 3) as entry (entry.at)} {#each log.slice(0, 3) as entry}
<span style="color: {entry.kind === 'err' ? 'var(--color-threat-solid)' : 'inherit'}"><strong>{entry.kind}</strong> · {entry.msg}</span> <span style="color: {entry.kind === 'err' ? 'var(--color-threat-solid)' : 'inherit'}"><strong>{entry.kind}</strong> · {entry.msg}</span>
<span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span> <span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span>
{/each} {/each}

@ -292,7 +292,7 @@
{#if trace.length === 0} {#if trace.length === 0}
<span>edit a segment or play an event to see sema stamps</span> <span>edit a segment or play an event to see sema stamps</span>
{:else} {:else}
{#each trace.slice(0, 3) as entry (entry.at)} {#each trace.slice(0, 3) as entry}
<span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span> <span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span>
<span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span> <span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span>
{/each} {/each}

@ -275,7 +275,7 @@
{#if trace.length === 0} {#if trace.length === 0}
<span>type or arrow-nav to highlight, Enter to invoke</span> <span>type or arrow-nav to highlight, Enter to invoke</span>
{:else} {:else}
{#each trace.slice(0, 3) as entry (entry.at)} {#each trace.slice(0, 3) as entry}
<span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span> <span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span>
<span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span> <span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span>
{/each} {/each}

@ -296,7 +296,7 @@
{#if trace.length === 0} {#if trace.length === 0}
<span>right-click the area to summon the menu</span> <span>right-click the area to summon the menu</span>
{:else} {:else}
{#each trace.slice(0, 3) as entry (entry.at)} {#each trace.slice(0, 3) as entry}
<span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span> <span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span>
<span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span> <span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span>
{/each} {/each}

@ -200,7 +200,7 @@
{:else if trace.length === 0} {:else if trace.length === 0}
<span>drag cards across columns · keyboard: Tab → Space → arrows → Enter / Escape</span> <span>drag cards across columns · keyboard: Tab → Space → arrows → Enter / Escape</span>
{:else} {:else}
{#each trace.slice(0, 3) as entry (entry.at)} {#each trace.slice(0, 3) as entry}
<span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span> <span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span>
<span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span> <span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span>
{/each} {/each}

@ -325,7 +325,7 @@
{#if trace.length === 0} {#if trace.length === 0}
<span>click the trigger to summon the menu</span> <span>click the trigger to summon the menu</span>
{:else} {:else}
{#each trace.slice(0, 3) as entry (entry.at)} {#each trace.slice(0, 3) as entry}
<span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span> <span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span>
<span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span> <span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span>
{/each} {/each}

@ -221,7 +221,7 @@
{#if trace.length === 0} {#if trace.length === 0}
<span>focus an article + PageDown to navigate; scroll to fire sentinel…</span> <span>focus an article + PageDown to navigate; scroll to fire sentinel…</span>
{:else} {:else}
{#each trace.slice(0, 3) as entry (entry.at)} {#each trace.slice(0, 3) as entry}
<span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span> <span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span>
<span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span> <span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span>
{/each} {/each}

@ -190,7 +190,7 @@
{#if trace.length === 0} {#if trace.length === 0}
<span>click row body to select; Tab into row to reach inner button</span> <span>click row body to select; Tab into row to reach inner button</span>
{:else} {:else}
{#each trace.slice(0, 3) as entry (entry.at)} {#each trace.slice(0, 3) as entry}
<span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span> <span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span>
<span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span> <span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span>
{/each} {/each}

@ -195,7 +195,7 @@
{#if trace.length === 0} {#if trace.length === 0}
<span>hover the link to see events</span> <span>hover the link to see events</span>
{:else} {:else}
{#each trace.slice(0, 3) as entry (entry.at)} {#each trace.slice(0, 3) as entry}
<span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span> <span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span>
<span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span> <span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span>
{/each} {/each}

@ -225,7 +225,7 @@
{#if trace.length === 0} {#if trace.length === 0}
<span>focus the listbox + arrow / type to highlight</span> <span>focus the listbox + arrow / type to highlight</span>
{:else} {:else}
{#each trace.slice(0, 3) as entry (entry.at)} {#each trace.slice(0, 3) as entry}
<span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span> <span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span>
<span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span> <span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span>
{/each} {/each}

@ -249,7 +249,7 @@
{#if trace.length === 0} {#if trace.length === 0}
<span>click a trigger or arrow-nav between menus</span> <span>click a trigger or arrow-nav between menus</span>
{:else} {:else}
{#each trace.slice(0, 3) as entry (entry.at)} {#each trace.slice(0, 3) as entry}
<span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span> <span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span>
<span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span> <span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span>
{/each} {/each}

@ -187,7 +187,7 @@
{#if trace.length === 0} {#if trace.length === 0}
<span>hover or focus a trigger to open a mega-menu</span> <span>hover or focus a trigger to open a mega-menu</span>
{:else} {:else}
{#each trace.slice(0, 3) as entry (entry.at)} {#each trace.slice(0, 3) as entry}
<span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span> <span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span>
<span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span> <span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span>
{/each} {/each}

@ -316,7 +316,7 @@
{#if trace.length === 0} {#if trace.length === 0}
<span>click a day to start; hover to preview; click second to commit</span> <span>click a day to start; hover to preview; click second to commit</span>
{:else} {:else}
{#each trace.slice(0, 3) as entry (entry.at)} {#each trace.slice(0, 3) as entry}
<span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span> <span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span>
<span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span> <span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span>
{/each} {/each}

@ -435,7 +435,7 @@
{#if trace.length === 0} {#if trace.length === 0}
<span>click sort, select a row, expand a detail panel…</span> <span>click sort, select a row, expand a detail panel…</span>
{:else} {:else}
{#each trace.slice(0, 3) as entry (entry.at)} {#each trace.slice(0, 3) as entry}
<span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span> <span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span>
<span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span> <span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span>
{/each} {/each}

@ -298,7 +298,7 @@
{#if trace.length === 0} {#if trace.length === 0}
<span>edit a segment or play an event to see sema stamps</span> <span>edit a segment or play an event to see sema stamps</span>
{:else} {:else}
{#each trace.slice(0, 3) as entry (entry.at)} {#each trace.slice(0, 3) as entry}
<span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span> <span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span>
<span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span> <span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span>
{/each} {/each}

@ -210,7 +210,7 @@
{#if trace.length === 0} {#if trace.length === 0}
<span>tab into a segment and arrow up/down to edit</span> <span>tab into a segment and arrow up/down to edit</span>
{:else} {:else}
{#each trace.slice(0, 3) as entry (entry.at)} {#each trace.slice(0, 3) as entry}
<span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span> <span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span>
<span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span> <span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span>
{/each} {/each}

@ -301,7 +301,7 @@
{#if trace.length === 0} {#if trace.length === 0}
<span>click a row, expand a branch, arrow keys to navigate…</span> <span>click a row, expand a branch, arrow keys to navigate…</span>
{:else} {:else}
{#each trace.slice(0, 3) as entry (entry.at)} {#each trace.slice(0, 3) as entry}
<span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span> <span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span>
<span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span> <span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span>
{/each} {/each}

@ -262,7 +262,7 @@
{#if trace.length === 0} {#if trace.length === 0}
<span>arrow keys to navigate, Enter/Space to select, * to expand siblings…</span> <span>arrow keys to navigate, Enter/Space to select, * to expand siblings…</span>
{:else} {:else}
{#each trace.slice(0, 3) as entry (entry.at)} {#each trace.slice(0, 3) as entry}
<span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span> <span><span data-uix-stage-trace-event>{entry.event}</span> · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}</span>
<span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span> <span style="color: var(--uix-text-faint)">{fmtTime(entry.at)}</span>
{/each} {/each}

Loading…
Cancel
Save

Powered by TurnKey Linux.