Skip to content

fix(a11y): make Tooltip keyboard-accessible, dismissible, and hoverable#3429

Merged
bilal-karim merged 5 commits into
mainfrom
a11y/tooltip-keyboard-hover-dismiss
Jun 3, 2026
Merged

fix(a11y): make Tooltip keyboard-accessible, dismissible, and hoverable#3429
bilal-karim merged 5 commits into
mainfrom
a11y/tooltip-keyboard-hover-dismiss

Conversation

@bilal-karim

@bilal-karim bilal-karim commented May 21, 2026

Copy link
Copy Markdown
Member

Summary

  • Critical accessibility fix — the Holocene Tooltip primitive was completely inaccessible to keyboard and screen reader users
  • Addresses WCAG 2.2 SC 1.4.13 (Content on Hover or Focus), which currently scores "Does Not Support"
  • Cross-cutting: also advances SC 2.1.1 (Keyboard), SC 4.1.2 (Name, Role, Value), and unblocks fixes for SC 1.3.2 and SC 1.4.1

What was broken

The Tooltip only responded to mouseenter/mouseleave — keyboard users never saw tooltip content, there was no Escape-to-dismiss, and portal tooltips disappeared when moving the pointer toward the popover.

What this PR adds

  • Keyboard focus support — tooltips appear when any focusable child receives focus (focusin/focusout on the wrapper)
  • Escape to dismiss — pressing Escape hides the tooltip without moving focus; auto-resets when the interaction ends
  • Hoverable popover — pointer can move onto the popover content without it disappearing (diagonal hover bridge)
  • ARIA linkagerole="tooltip" + unique id on the popover, aria-describedby on the wrapper so screen readers announce tooltip content
  • Unified open state — both portal and inline variants now use a single isOpen derived from hover, focus, popover-hover, and dismiss signals

No API changes

All existing consumers work identically. The only observable difference is that tooltips now appear on keyboard focus.

Test plan

Keyboard:

  • Tab through workflow detail action bar buttons — confirm each shows its tooltip on focus
  • With a tooltip visible, press Escape — confirm it dismisses without focus moving
  • Tab away and back — confirm tooltip reappears (dismiss state cleared)

Pointer:

  • Hover a tooltip trigger, then move pointer onto the popover content — confirm it stays open
  • Move pointer off both trigger and popover — confirm it closes
  • With popover visible via hover, press Escape — confirm dismissal

Screen reader:

  • VoiceOver/NVDA: focus a tooltipped button — confirm both the button name and tooltip text are announced
  • Verify portal-based tooltips (usePortal consumers) announce correctly

🤖 Generated with Claude Code

A11y-Audit-Ref: 1.4.13-tooltip

The Tooltip primitive was mouse-only: no focus handlers, no Escape
dismiss, and portal tooltips disappeared when moving the pointer to
the popover content. This failed all three WCAG 2.2 SC 1.4.13
criteria (Content on Hover or Focus).

Changes:
- Show tooltip on keyboard focus via focusin/focusout on wrapper
- Dismiss on Escape (resets when interaction ends)
- Track pointer hover on the popover itself (diagonal hover bridge)
- Unify both portal and inline variants on a single isOpen derived
- Add role="tooltip" and aria-describedby linkage for screen readers

No API changes — all existing consumers work identically, with the
added benefit that tooltips now appear on keyboard focus.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@bilal-karim
bilal-karim requested a review from a team as a code owner May 21, 2026 20:34
@vercel

vercel Bot commented May 21, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
holocene Ready Ready Preview, Comment Jun 3, 2026 4:45pm

Request Review

@temporal-cicd

temporal-cicd Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor
Warnings
⚠️

📊 Strict Mode: 1 error in 1 file (0.1% of 887 total)

src/lib/holocene/tooltip.svelte (1)
  • L59:13: Type 'null' is not assignable to type '"search" | "link" | "success" | "error" | "action" | "activity" | "add-square" | "add" | "apple" | "archives" | "arrow-down" | "arrow-left" | "arrow-up" | "arrow-right" | "ascending" | ... 141 more ... | "xmark-square"'.

Generated by 🚫 dangerJS against cf22e1a

- Use <svelte:window on:keydown> instead of reactive document
  listener (mirrors maximizable.svelte)
- Fix hover state lingering when mouse leaves popover to empty
  space by unifying wrapper + popover into a single hover zone
- Extract HOVER_HIDE_DELAY_MS constant
- Drop redundant isPopoverHovered state

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@bilal-karim bilal-karim changed the title fix: make Tooltip keyboard-accessible, dismissible, and hoverable fix(a11y): make Tooltip keyboard-accessible, dismissible, and hoverable May 22, 2026
@bilal-karim
bilal-karim marked this pull request as ready for review May 25, 2026 16:10
@github-actions github-actions Bot added a11y Accessibility audit PR a11y:no-fix-doc No A11y-Audit-Ref line; audit team triage a11y:bucket-3 Bucket 3: engineer required a11y:sc-1.4.13 and removed a11y:no-fix-doc No A11y-Audit-Ref line; audit team triage labels May 28, 2026
ardiewen and others added 2 commits June 3, 2026 12:18
Align tooltip id generation with the codebase convention used across
Holocene (accordion.svelte, accordion-light.svelte) and the rest of the
app, replacing the ad-hoc Math.random().toString(36) approach.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The group/tooltip marker only existed to drive the group-hover/tooltip:
utilities that previously controlled visibility. Tooltip open/close is
now fully JS-driven via isOpen, leaving this class with no consumers.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@bilal-karim
bilal-karim merged commit 3e8c996 into main Jun 3, 2026
18 checks passed
@bilal-karim
bilal-karim deleted the a11y/tooltip-keyboard-hover-dismiss branch June 3, 2026 17:04
rossedfort added a commit that referenced this pull request Jun 3, 2026
…002] (#3495)

* [DT-4039] Workflow query builder doesn't work with numeric search attributes (#3435)

* fix(DT-4039): Workflow query builder for numeric search attributes

The query builder wrapped Int/Double search attribute values in
double quotes (e.g. `Foo="1"`), which the Visibility backend rejects
for numeric types. Stop quoting numeric values in the serializer.

The tokenizer also merged unquoted values with their conditional
operator (e.g. `Foo=1` tokenized as `Foo`, `=1`), which previously
only mattered for booleans and was patched in the parser by stripping
`=`. With numerics this breaks down for 2-char operators (`>=`, `<=`,
`!=`). Fix the tokenizer to emit the conditional as its own token and
to accept operator-style 2-char conditionals without a trailing space.
Update the boolean parser to read the value from `tokenTwoAhead` to
match the new shape.

* fix(DT-4039): Update integration tests for unquoted numeric search attributes

The desktop and mobile workflow-filter specs asserted the previous
buggy serialization (`HistoryLength`="10"). Update them to match the
new correct shape (`HistoryLength`=10).

* Make Common Errors dismissable (#3471)

* Make common errors dismissable

* Remove common errors

* Capitalize Common Errors

* Add description

* Update description

* [DT-4048] Add accessibility PR triage and notification helpers (#3465)

* Implement helpers for accessibility PRs

* fix formatting

* fix linting

* fix title gating regex

* improve deduplication detection on notify

* Move CLAUDE.md a11y conventions to DT-4049

* feat(DT-4017): Schedules List UI Update (#3467)

* fix(markdown): allow nested lists to render as block (DT-4047) (#3463)

* Add a prop to hide select/deselect controls (#3474)

* DT-4051: pre-populate input when starting standalone activity like this one (#3469)

* fix(a11y): reveal Copyable's CopyButton on focus-within (WCAG 2.1.1) (#3452)

The Copyable primitive hides its CopyButton behind
'invisible group-hover:visible'. Tailwind's 'invisible' compiles
to visibility: hidden, which removes the element from the focus
order -- Tab skips it entirely. Mouse users can hover to reveal
and click; keyboard users in default mode have no way to invoke
copy at all.

Add 'group-focus-within:visible' alongside the existing
'group-hover:visible' so the button becomes visible (and
therefore focus-eligible) whenever focus moves anywhere inside
the wrapping group -- typically when the user Tabs onto the
slot's content (workflow IDs, run IDs, etc.). The next Tab now
lands on the CopyButton, where Enter or Space triggers copy.

Applied to both branches (clickAllToCopy and default mode).

Affects detail-list value cells, event-summary rows, and every
other Copyable consumer in default mode.

Co-authored-by: Claude Opus 4.6 <[email protected]>

* fix: accept autocomplete prop on NumberInput, ChipInput, and Combobox (#3425)

Allows consumers to pass WCAG-defined autocomplete tokens (e.g.
"cc-number", "email") to these input primitives. All three previously
hardcoded autocomplete="off", preventing purpose identification per
WCAG 2.2 SC 1.3.5. Default remains "off" so existing behavior is
unchanged.

Co-authored-by: Claude Opus 4.6 <[email protected]>

* fix: cap ZoomSvg container height to viewport (#3428)

Uses CSS min() to limit the container to the lesser of containerHeight
(default 600px) or viewport height minus 8rem. On narrow landscape
viewports (e.g. 320x500) the container now fits within the screen
instead of overflowing. Desktop behavior is unchanged.

Co-authored-by: Claude Opus 4.6 <[email protected]>

* fix(a11y): accommodate text-spacing overrides on Badge and Chip (WCAG 1.4.12) (#3433)

* fix(a11y): accommodate text-spacing overrides (WCAG 1.4.12)

Three same-family fixes for SC 1.4.12 Text Spacing. WCAG requires
that text-spacing user overrides (line-height 1.5, letter-spacing
0.12em, etc.) not cause content loss.

- Badge: leading-4 (16px) -> leading-[1.5]. text-sm at 1.5 line-
  height is 21px which previously clipped.
- Chip: h-7 -> min-h-7, add leading-[1.5]. Preserves the 28px
  visual minimum but allows growth.
- Table row: h-8 -> min-h-8. The densest text-bearing surface in
  the product. Preserves the 32px default but allows growth under
  user override.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* revert(a11y): undo h-8 -> min-h-8 on table row

Per reviewer feedback: <tr> uses table layout rules, where the
height property already acts as a minimum (the row grows to fit
cell content). min-height on <tr> is either ignored or behaves
differently per browser. The change broke the skeleton table
without providing the intended SC 1.4.12 benefit -- under
text-spacing override, the row would have grown naturally.

Badge and chip portions of the PR remain in place (those are flex
elements where min-h-* works as expected).

Co-Authored-By: Claude Opus 4.6 <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>

* fix(a11y): non-color signal for Label required indicator (WCAG 1.4.1) (#3439)

* fix(a11y): non-color signals for required state and timeline graph nodes (WCAG 1.4.1)

Two SC 1.4.1 Use of Color fixes:

- Label required-field indicator: replace the 6x6 red dot with a
  red asterisk (aria-hidden) so users with color-perception
  differences see a shape signal, not just a color signal. The
  programmatic required state continues to come from native HTML
  `required` on the input (which the 1.3.1 forwarders ensure).
- Timeline graph nodes (workflow-row, history-graph-row-visual):
  add aria-label to the <g role="button"> wrapper so AT users hear
  the workflow id + status (or event type + classification)
  instead of bare "button". Removes the previous dependency on
  reaching the legend tooltip to decode color.

New i18n keys: workflows.row-accessible-name,
events.row-accessible-name.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* revert: remove timeline graph aria-label changes from this PR

Per the PR scope decision, keep this PR focused on the Label
required-indicator fix only. The timeline graph status accessible-
name fix (workflow-row.svelte, history-graph-row-visual.svelte,
and the supporting i18n keys) will ship as its own separate PR
so the two SC 1.4.1 defects can be reviewed independently.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix(a11y): use text-danger so the asterisk actually renders red

The previous text-interactive-error class was a no-op for text
color (interactive-error is only registered under backgroundColor
in the theme plugin, not textColor), so the asterisk inherited
the default text color and rendered black/off-white instead of
red. text-danger maps to --color-text-danger (red.700 light /
red.400 dark) which is the proper red text token.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* refactor: simplify required-asterisk span

The parent <label> already provides text-sm and font-medium, so
the span inherits both. Only text-danger is doing actual work;
the size/weight/leading classes were redundant.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* refactor(label): refine required-asterisk visual alignment

- font-mono: mono asterisks are larger and more geometrically
  centered than proportional ones, making them feel like a proper
  visual indicator rather than a tiny text-style superscript.
- leading-none: tightens the span's line-box to the character.
- translate-y-0.5: small downward nudge so the asterisk aligns
  with the label baseline rather than sitting above it.
- -ml-1: reduces the gap to the preceding label text (8px -> 4px).

Co-Authored-By: Claude Opus 4.6 <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>

* fix(a11y): tabindex on <main> so the skip link moves focus reliably (WCAG 2.4.1) (#3451)

The SkipNavigation primitive renders <a href="#content">, which
targets <main id="content"> in main-content-container.svelte. By
HTML spec, <main> without tabindex is not programmatically
focusable -- so activating the skip link only reliably moves
focus on Chromium-based browsers (Chrome, Edge). Safari and
older Firefox scroll the target into view but leave focus on the
skip link itself, defeating the bypass for keyboard and screen-
reader users.

Adding tabindex="-1" makes <main> programmatically focusable
(so the hash-link navigation lands focus there) while keeping it
out of the natural Tab order. Closes WCAG 2.4.1 sufficient-
technique G1's "programmatically moves focus" requirement across
all browsers.

Co-authored-by: Claude Opus 4.6 <[email protected]>

* fix(a11y): info-and-relationships compliance (WCAG 1.3.1) (#3432)

Four related fixes under SC 1.3.1, grouped per the audit's
correction of the matrix pathway:

- NumberInput / Textarea: forward `required` to the underlying
  native element. Previously the visual red-dot appeared via
  <Label> but the field was not programmatically required, so AT
  users heard an optional-field announcement despite the visible
  indicator.
- RadioGroup: add role="radiogroup" and link the optional
  description via aria-labelledby so AT users hear the group as a
  whole rather than each radio in isolation.
- Tables: add scope="col" to every <th> in product data tables
  (workflows, schedules, workers, batch-operations, child/parent
  workflow relationships). Fixes WCAG technique H63.

Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Ardie Wen <[email protected]>

* fix(query): quote ExecutionDuration Go duration strings in visibility SQL (#3482)

ExecutionDuration is typed as INT in the search attributes store, so
formatValue returns its values unquoted. PR #3435 (DT-4039) added an
explicit unquoted INT path which broke ExecutionDuration because it uses
Go duration strings (30s, 1m30s) that vitess rejects as unquoted tokens.

Add an ExecutionDuration guard before the INT/duration paths: quote
non-integer values as strings, pass plain integers (nanoseconds) through
unquoted.

* Passthrough goto params to link component (#3483)

* feat: add centerButton, menuButton, and linksContent snippets to BottomNavigation (#3485)

* feat: add centerButton snippet to BottomNavigation for custom center content

* feat: add centerButton, menuButton, and linksContent snippets to BottomNavigation

* feat: add bars icon to holocene icon set

* fix: add "for" to validate connection modal title (#3488)

The worker deployment version validate connection modal title now reads
"Validate Connection for <version>" instead of "Validate Connection <version>".

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>

* feat(DT-4069): Update modal backdrop to 50% opaque (#3486)

* refactor: Input & DatePicker - Svelte 5 & afterLabel snippet (#3479)

* Fix decoded object payload summaries (#3491)

* Fix decoded object payload summaries

* Convert event details row to Svelte 5

* fix(DT-4044): only use browser codec endpoint when override option is selected (#3490)

When the user selects "Use Namespace-level setting, where available" and
no namespace codec is configured, the browser endpoint was incorrectly
used as a fallback. Now returns empty string so no codec is invoked.

* fix(a11y): improve 1.1.1 non-text content compliance (#3431)

- nexus-empty-state.svelte: mark Andromeda illustration decorative
  with alt="" + aria-hidden on wrapper. Previously screen readers
  announced "Andromeda, image" (the filename), conveying nothing.
- toast.svelte: add runtime fallback to translate('common.close')
  when closeButtonLabel is missing or empty. TypeScript-required
  prop still flags missing-at-compile-time, but an empty string or
  undefined at runtime now produces a labeled icon button rather
  than aria-label="".

Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Ardie Wen <[email protected]>

* fix(a11y): make Tooltip keyboard-accessible, dismissible, and hoverable (#3429)

* fix: make Tooltip keyboard-accessible, dismissible, and hoverable

The Tooltip primitive was mouse-only: no focus handlers, no Escape
dismiss, and portal tooltips disappeared when moving the pointer to
the popover content. This failed all three WCAG 2.2 SC 1.4.13
criteria (Content on Hover or Focus).

Changes:
- Show tooltip on keyboard focus via focusin/focusout on wrapper
- Dismiss on Escape (resets when interaction ends)
- Track pointer hover on the popover itself (diagonal hover bridge)
- Unify both portal and inline variants on a single isOpen derived
- Add role="tooltip" and aria-describedby linkage for screen readers

No API changes — all existing consumers work identically, with the
added benefit that tooltips now appear on keyboard focus.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* refactor(tooltip): unify hover-zone handling and match repo patterns

- Use <svelte:window on:keydown> instead of reactive document
  listener (mirrors maximizable.svelte)
- Fix hover state lingering when mouse leaves popover to empty
  space by unifying wrapper + popover into a single hover zone
- Extract HOVER_HIDE_DELAY_MS constant
- Drop redundant isPopoverHovered state

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* refactor(tooltip): use crypto.randomUUID() for tooltip id

Align tooltip id generation with the codebase convention used across
Holocene (accordion.svelte, accordion-light.svelte) and the rest of the
app, replacing the ad-hoc Math.random().toString(36) approach.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* refactor(tooltip): drop unused group/tooltip class

The group/tooltip marker only existed to drive the group-hover/tooltip:
utilities that previously controlled visibility. Tooltip open/close is
now fully JS-driven via isOpen, leaving this class with no consumers.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Ardie Wen <[email protected]>

* fix(a11y): name-role-value compliance, partial (WCAG 4.1.2) (#3434)

* fix(a11y): name-role-value compliance (WCAG 4.1.2)

Five fixes under SC 4.1.2 Name, Role, Value:

- Accordion content panel: remove role="textbox". The disclosure
  panel is static content, not an editable text input; AT
  announced "edit text" on every Accordion consumer.
- Accordion + AccordionLight: move slot="action" out of the
  disclosure <button> into a sibling element. Previously
  consumers placing focusable content in the action slot
  produced <button><button>... nested-interactive HTML.
- CopyButton: ship translated default labels via i18n. Empty
  string defaults in CodeBlock previously propagated to an Icon
  that hid itself, producing unlabeled icon-only buttons across
  ~11 CodeBlock instances.
- nav-section: filter out hidden nav items. The NavLinkItem.hidden
  flag was declared on the type and set at source but never
  consulted at render time, so Standalone Activities / Nexus
  links rendered as focusable for AT users on servers that
  cannot serve them.
- Workflow family tree: add aria-label to the two SVG
  <g role="button"> widgets so screen readers announce node
  identity instead of "button".

Deferred to follow-up PRs (audit blockers):
- 4.1.2-button-anchor-empty: audit recommends a two-PR sequenced
  rollout (primitive change + consumer migration).
- 4.1.2-codemirror-no-name: ~5 CodeBlock consumer surfaces need
  design input on label content.
- relationships nested-interactive restructure: site B (button
  wraps Link) and site A's nested <a> extraction are substantial
  SVG/HTML restructures separate from the labeling fix above.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix(a11y): localize workflow family-tree node aria-labels

The family-tree node aria-labels hardcoded the English word "Workflow",
inconsistent with the codebase convention of localizing aria-labels via
translate(). Add a parameterized workflows.family-node-label i18n key and
use it for both the root and child node widgets.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Ardie Wen <[email protected]>

* feat(nexus-operations): standalone nexus operation details page [DT-4002]

Add tab-based detail view for individual nexus operations with live long-polling.
Mirrors standalone activity detail page pattern with nexus-specific fields:
identity, operation, status, timing, attempt, timeout config, cancellation info,
nexus header, and input/outcome payload display.

* feat(nexus-operations): redesign details page to match Figma layout

Restructure the details page around "Operation Event History" with three
subsections: Run Details, Operation Details, Timeout Configuration.

- Add filterable links for endpoint, service, and operation fields
- Add handler namespace and handler operation link from nexus operation links
- Show nexus header code block inline in Operation Details section
- Move last attempt failure into the Attempt section (conditional)
- Add handler namespace note when handler workflow link is present

* fix(nexus-operations): pass namespace to filter links on details page

* fix nexus operation id

---------

Co-authored-by: Andrew Zamojc <[email protected]>
Co-authored-by: Alex Tideman <[email protected]>
Co-authored-by: Ardie Wen <[email protected]>
Co-authored-by: Tegan Churchill <[email protected]>
Co-authored-by: Ross Nelson <[email protected]>
Co-authored-by: Alex Thomsen <[email protected]>
Co-authored-by: Bilal Karim <[email protected]>
Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Ardie Wen <[email protected]>
rossedfort added a commit that referenced this pull request Jun 4, 2026
Auto-generated version bump from 2.50.0 to 2.51.0

Bump type: minor

Changes included:
- [`495e27f7`](495e27f) Use pageSize instead of maximumPageSize (#3422)
- [`42c58c45`](42c58c4) fix: use rem-based width for expanded side-nav (#3426)
- [`ce928ed5`](ce928ed) fix(a11y): meet 4.5:1 contrast for text-subtle and text-warning (WCAG 1.4.3) (#3437)
- [`e8ed8a49`](e8ed8a4) fix(a11y): consistent high-contrast focus rings across all Button variants (WCAG 1.4.11) (#3438)
- [`03918fb5`](03918fb) fix(a11y): prevent horizontal scroll on login page at 320px (WCAG 1.4.10) (#3440)
- [`a11555fb`](a11555f) fix(a11y): use rem-based font-size and unitless line-height in markdown reset (#3430)
- [`057ff323`](057ff32) fix: use empty alt on decorative SDK logo image (#3424)
- [`e435ffd7`](e435ffd) Standalone Activity details page UI updates (#3427)
- [`e09028f3`](e09028f) Make bottom-nav accept linksSnippet instead of sections (#3445)
- [`a22a1e21`](a22a1e2) Add tooltip for SDK version (#3462)
- [`c3b7820e`](c3b7820) [DT-4039] Workflow query builder doesn't work with numeric search attributes (#3435)
- [`5e53881f`](5e53881) Make Common Errors dismissable (#3471)
- [`2665e2f1`](2665e2f) [DT-4048] Add accessibility PR triage and notification helpers (#3465)
- [`8083cf4e`](8083cf4) feat(DT-4017): Schedules List UI Update (#3467)
- [`b5de30e3`](b5de30e) fix(markdown): allow nested lists to render as block (DT-4047) (#3463)
- [`8a6fd2de`](8a6fd2d) Add a prop to hide select/deselect controls (#3474)
- [`33ec1b3e`](33ec1b3) DT-4051: pre-populate input when starting standalone activity like this one (#3469)
- [`0d64fc20`](0d64fc2) fix(a11y): reveal Copyable's CopyButton on focus-within (WCAG 2.1.1) (#3452)
- [`321651c0`](321651c) fix: accept autocomplete prop on NumberInput, ChipInput, and Combobox (#3425)
- [`6567c222`](6567c22) fix: cap ZoomSvg container height to viewport (#3428)
- [`afd3a8ed`](afd3a8e) fix(a11y): accommodate text-spacing overrides on Badge and Chip (WCAG 1.4.12) (#3433)
- [`d264b876`](d264b87) fix(a11y): non-color signal for Label required indicator (WCAG 1.4.1) (#3439)
- [`73c61ea3`](73c61ea) fix(a11y): tabindex on <main> so the skip link moves focus reliably (WCAG 2.4.1) (#3451)
- [`34e582a8`](34e582a) fix(a11y): info-and-relationships compliance (WCAG 1.3.1) (#3432)
- [`f7be7b6c`](f7be7b6) fix(query): quote ExecutionDuration Go duration strings in visibility SQL (#3482)
- [`9f0c2631`](9f0c263) Passthrough goto params to link component (#3483)
- [`2f8c037b`](2f8c037) feat: add centerButton, menuButton, and linksContent snippets to BottomNavigation (#3485)
- [`7f258bac`](7f258ba) fix: add "for" to validate connection modal title (#3488)
- [`350397ab`](350397a) feat(DT-4069): Update modal backdrop to 50% opaque (#3486)
- [`f01baa49`](f01baa4) refactor: Input & DatePicker - Svelte 5 & afterLabel snippet (#3479)
- [`587c892f`](587c892) Fix decoded object payload summaries (#3491)
- [`2b85ef06`](2b85ef0) fix(DT-4044): only use browser codec endpoint when override option is selected (#3490)
- [`468893ba`](468893b) fix(a11y): improve 1.1.1 non-text content compliance (#3431)
- [`3e8c996d`](3e8c996) fix(a11y): make Tooltip keyboard-accessible, dismissible, and hoverable (#3429)
- [`47552538`](4755253) fix(a11y): name-role-value compliance, partial (WCAG 4.1.2) (#3434)
- [`e2c95d54`](e2c95d5) fix(a11y): align DOM order with visual order in mobile bottom-nav (WCAG 1.3.2) (#3441)
- [`634b08e1`](634b08e) a11y(1.4.11): focus rings — lighten dark-mode --color-border-focus-info to indigo.400 so ring-primary/70 meets 3:1 against surface-primary (#3478)
- [`039a555a`](039a555) a11y(1.4.11): Checkbox — add ring-offset so the checked-state focus ring contrasts against the indigo checked background (#3477)
- [`01161e2e`](01161e2) a11y(1.4.10): activity-options drawer — make width responsive so it reflows at 320 px (#3476)
- [`99f0a0a4`](99f0a0a) fix(a11y): filter hidden nav items in mobile bottom-nav (WCAG 4.1.2) (#3494)
- [`d12ebacc`](d12ebac) fix(a11y): add focusin/focusout to saved-query nav hover tooltip (WCAG 2.1.1) (#3453)
- [`c7206af9`](c7206af) fix(a11y): render a warning icon for Chip warning intent (WCAG 1.4.1) (#3450)
- [`b64ed713`](b64ed71) fix(a11y): pair Toast variant background with a severity icon (WCAG 1.4.1) (#3449)
- [`8e2ef708`](8e2ef70) upgrade temporal api version to latest (v1.62.13) (#3502)

Co-authored-by: rossedfort <[email protected]>
bilal-karim added a commit that referenced this pull request Jun 4, 2026
…WCAG 1.4.1) (#3443)

* fix(a11y): announce workflow / event status on timeline graph nodes (WCAG 1.4.1)

Adds aria-label to the SVG <g role="button"> wrapper on each
timeline-graph node so screen readers announce the workflow id
and status (or event type and classification) instead of bare
"button".

Previously, terminal statuses on the timeline graph (Completed,
Failed, Canceled, TimedOut, Fired, Signaled) were distinguishable
only by node color. The color legend lives inside a Tooltip --
keyboard- and AT-reachable since PR #3429, but still requires the
user to leave the graph, decode the color, and come back to
identify each node. This change makes the graph self-describing.

Cross-cutting wins:
- Closes the SC 4.1.2 missing-accessible-name defect on the same
  <g role="button"> widgets.
- Closes the SC 1.3.1 matrix-row item d deferred to the Robust
  wave.

New i18n keys:
- workflows.row-accessible-name = "Workflow {{workflowId}}: {{status}}"
- events.row-accessible-name = "Event {{eventType}}: {{classification}}"

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix(types): use typed lookup tables for translation keys

CI's check-types caught two TS errors in the timeline-graph
aria-label additions:

1. translate(\`workflows.\${workflowStatusKey(status)}\`) -- the
   helper's return type widened to plain string, which doesn't
   satisfy the strongly-typed translation-key union.
2. translate(\`events.event-classification.\${classification.toLowerCase()}\`)
   -- same shape; toLowerCase() returns plain string.
3. event.eventType -- doesn't exist on PendingActivity (a union
   member of WorkflowEventWithPending).

Replaced both dynamic template-literal lookups with `as const`
record literals keyed by the literal-union enum values, so
TypeScript can narrow to the exact translation-key union. Added
an `in` operator narrowing for the eventType access.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* chore: remove unused Retrying key from classification lookup

EventClassification union doesn't include 'Retrying' -- only the
i18n key exists. The lookup entry was unreachable.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix(a11y): announce retrying state on pending-activity timeline events

Previously the aria-label for a pending activity always said
"Pending" regardless of attempt count. The timeline already
communicates retry state visually (line styling), in the legend
("Retry" entry), and in the group-details row text -- but the
per-event aria-label missed it.

Check group.pendingActivity.attempt > 1 (matching the existing
group-details-row.svelte logic) and surface "Retrying" in the
aria-label when applicable. The events.event-classification.retrying
translation key already existed for this purpose.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* refactor(a11y): extract shared getStatusLabel helper

The status/classification -> translated-label mapping existed inline in
workflow-status.svelte and was duplicated by the two new lookup tables
added for the timeline-graph aria-labels (workflow-row,
history-graph-row-visual). Extract the canonical map into
$lib/utilities/get-status-label and reuse it in all three call sites so
there is a single source of truth.

No behavior change: the helper map is a verbatim copy of the canonical
Record<Status, string>; the history graph normalizes its lowercase
'pending'/'retrying' values to the PascalCase Status union for lookup
while leaving `classification` untouched for styling.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* fix(a11y): announce status on timeline-graph row nodes (WCAG 1.4.1)

timeline-graph-row's <g role="button"> contained only SVG primitives and
<Text> (whose accessible-name contribution is unreliable across screen
readers), so it announced as bare "button" — the same defect this PR
fixes on workflow-row and history-graph-row-visual. Add an aria-label via
the shared getStatusLabel helper.

group-details-row's <g role="button"> is intentionally left for a
separate fix: it wraps foreignObject HTML (already name-contributing) and
nested interactive elements, so it needs a nested-interactive restructure
rather than an aria-label that would mask its content.

A11y-Audit-Ref: 1.4.1-timeline-graph-status

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* test(a11y): cover graph node accessible names + getStatusLabel

- Unit test for the shared getStatusLabel helper (workflow statuses, event
  classifications, pending/retrying, and the Unknown fallback).
- Integration test asserting the timeline graph's SVG <g role="button">
  nodes expose accessible names: the workflow node ("Workflow <id>: Running")
  and event nodes ("Event LongActivity: Scheduled", "Event customSignal:
  Signaled"). Verified red — the assertions fail if the aria-label regresses.

history-graph-row-visual shares the same getStatusLabel +
events.row-accessible-name mechanism (covered by the unit test and the
identical timeline-graph-row path); its /history graph view did not render
nodes deterministically in the integration harness.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* refactor(a11y): split status label helper by domain

Avoid collisions between workflow statuses and event classifications that
share a name (Running, Completed, Failed, Canceled, Terminated, TimedOut).
The previous single combined map silently merged them and routed event
nodes through the workflows.* namespace, so a future divergence between
workflows.completed and events.event-classification.completed would have
mislabeled event nodes.

Now expose two domain-scoped resolvers, each backed by an exhaustive
Record over its own union (so a new status in either domain forces a label):
- getWorkflowStatusLabel  -> workflows.* (workflow-row)
- getEventClassificationLabel -> events.event-classification.* (history-graph-row-visual, timeline-graph-row)

getStatusLabel remains as the polymorphic resolver for WorkflowStatus.svelte,
which renders one badge for any status type and cannot know the domain from
the value; it prefers the workflow label on shared names, preserving that
component's existing behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Ardie Wen <[email protected]>
rossedfort added a commit that referenced this pull request Jun 30, 2026
* feat(nexus-operations): DT-4005 standalone nexus operations API integrations (#3444)

* feat(nexus-operations): add types and API routes for standalone nexus operations

Adds NexusOperationExecution types, extends APIRouteParameters with operationId,
and registers 6 new API routes (list, describe/start, poll, cancel, terminate, count).
Adds route-for helpers mirroring the standalone activities pattern.

DT-4005

* feat(nexus-operations): add services for standalone nexus operations

Adds fetchPaginatedNexusOperations, startStandaloneNexusOperation,
getNexusOperationExecution, pollNexusOperationExecution, cancel, and terminate.
Adds count and count-by-status service functions.

DT-4005

* feat(nexus-operations): add stores, poller, and filters for standalone nexus operations

Adds nexus operation writable stores (refresh, loading, updating, count, error, query).
Adds StandaloneNexusOperationPoller with long-poll loop mirroring the activity poller.
Adds nexusOperationFilters store to filters.ts.

DT-4005

* test(nexus-operations): add route-for-base-path test cases for standalone nexus operations

* feat(nexus-operations): add capability flag, guard component, and configurable table columns (#3466)

Adds Phase 2 (DT-4006) infrastructure for standalone nexus operations:

- NamespaceCapabilities type with standaloneNexusOperations field in src/lib/types/index.ts
- Guard component that checks both capability flag and minimum server version (1.31.0)
- Disabled state component with Dynamic Config instructions
- i18n namespace for standalone-nexus-operations
- TABLE_TYPE.NEXUS_OPERATIONS, NexusOperationHeaderLabels, default/available column arrays,
  persistedNexusOperationsTableColumns store, configurableTableColumns derived store update,
  availableNexusOperationColumns export, and exhaustive switch cases in configurable-table-columns.ts

* feat(nexus-operations): standalone nexus operations list page [DT-4003] (#3470)

* feat(nexus-operations): add standalone nexus operations list page

- Add list page route at namespaces/[namespace]/nexus-operations
- Add page component with status count filters and configurable table
- Add filter bar with search attribute support
- Add configurable table with header/body cells and empty state
- Add get-nexus-operation-status-and-count utility and tests
- Add nexus operation search attributes to search-attributes store
- Wire nav layout item for standalone nexus operations
- Add i18n strings for nexus operations page

* feat(nexus-operations): add saved views panel, CTA button, and rich empty state

Match Figma design for standalone nexus operations list page:
- Add saved views panel with system/user views, expand/collapse, save/edit/delete/share
- Add 'Start a Standalone Nexus Operation' CTA button via headerActions snippet
- Add rich empty state with value proposition, docs links, and GitHub code samples
- Add i18n strings for all new UI copy
- Add savedNexusQueryNavOpen persist store and savedNexusQueries/systemNexusViews stores

* Update api version to v1.62.12 for SANO APIs and capability

* fix capability name

* feat(nexus-operations): standalone nexus operation details page [DT-4002] (#3495)

* [DT-4039] Workflow query builder doesn't work with numeric search attributes (#3435)

* fix(DT-4039): Workflow query builder for numeric search attributes

The query builder wrapped Int/Double search attribute values in
double quotes (e.g. `Foo="1"`), which the Visibility backend rejects
for numeric types. Stop quoting numeric values in the serializer.

The tokenizer also merged unquoted values with their conditional
operator (e.g. `Foo=1` tokenized as `Foo`, `=1`), which previously
only mattered for booleans and was patched in the parser by stripping
`=`. With numerics this breaks down for 2-char operators (`>=`, `<=`,
`!=`). Fix the tokenizer to emit the conditional as its own token and
to accept operator-style 2-char conditionals without a trailing space.
Update the boolean parser to read the value from `tokenTwoAhead` to
match the new shape.

* fix(DT-4039): Update integration tests for unquoted numeric search attributes

The desktop and mobile workflow-filter specs asserted the previous
buggy serialization (`HistoryLength`="10"). Update them to match the
new correct shape (`HistoryLength`=10).

* Make Common Errors dismissable (#3471)

* Make common errors dismissable

* Remove common errors

* Capitalize Common Errors

* Add description

* Update description

* [DT-4048] Add accessibility PR triage and notification helpers (#3465)

* Implement helpers for accessibility PRs

* fix formatting

* fix linting

* fix title gating regex

* improve deduplication detection on notify

* Move CLAUDE.md a11y conventions to DT-4049

* feat(DT-4017): Schedules List UI Update (#3467)

* fix(markdown): allow nested lists to render as block (DT-4047) (#3463)

* Add a prop to hide select/deselect controls (#3474)

* DT-4051: pre-populate input when starting standalone activity like this one (#3469)

* fix(a11y): reveal Copyable's CopyButton on focus-within (WCAG 2.1.1) (#3452)

The Copyable primitive hides its CopyButton behind
'invisible group-hover:visible'. Tailwind's 'invisible' compiles
to visibility: hidden, which removes the element from the focus
order -- Tab skips it entirely. Mouse users can hover to reveal
and click; keyboard users in default mode have no way to invoke
copy at all.

Add 'group-focus-within:visible' alongside the existing
'group-hover:visible' so the button becomes visible (and
therefore focus-eligible) whenever focus moves anywhere inside
the wrapping group -- typically when the user Tabs onto the
slot's content (workflow IDs, run IDs, etc.). The next Tab now
lands on the CopyButton, where Enter or Space triggers copy.

Applied to both branches (clickAllToCopy and default mode).

Affects detail-list value cells, event-summary rows, and every
other Copyable consumer in default mode.

Co-authored-by: Claude Opus 4.6 <[email protected]>

* fix: accept autocomplete prop on NumberInput, ChipInput, and Combobox (#3425)

Allows consumers to pass WCAG-defined autocomplete tokens (e.g.
"cc-number", "email") to these input primitives. All three previously
hardcoded autocomplete="off", preventing purpose identification per
WCAG 2.2 SC 1.3.5. Default remains "off" so existing behavior is
unchanged.

Co-authored-by: Claude Opus 4.6 <[email protected]>

* fix: cap ZoomSvg container height to viewport (#3428)

Uses CSS min() to limit the container to the lesser of containerHeight
(default 600px) or viewport height minus 8rem. On narrow landscape
viewports (e.g. 320x500) the container now fits within the screen
instead of overflowing. Desktop behavior is unchanged.

Co-authored-by: Claude Opus 4.6 <[email protected]>

* fix(a11y): accommodate text-spacing overrides on Badge and Chip (WCAG 1.4.12) (#3433)

* fix(a11y): accommodate text-spacing overrides (WCAG 1.4.12)

Three same-family fixes for SC 1.4.12 Text Spacing. WCAG requires
that text-spacing user overrides (line-height 1.5, letter-spacing
0.12em, etc.) not cause content loss.

- Badge: leading-4 (16px) -> leading-[1.5]. text-sm at 1.5 line-
  height is 21px which previously clipped.
- Chip: h-7 -> min-h-7, add leading-[1.5]. Preserves the 28px
  visual minimum but allows growth.
- Table row: h-8 -> min-h-8. The densest text-bearing surface in
  the product. Preserves the 32px default but allows growth under
  user override.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* revert(a11y): undo h-8 -> min-h-8 on table row

Per reviewer feedback: <tr> uses table layout rules, where the
height property already acts as a minimum (the row grows to fit
cell content). min-height on <tr> is either ignored or behaves
differently per browser. The change broke the skeleton table
without providing the intended SC 1.4.12 benefit -- under
text-spacing override, the row would have grown naturally.

Badge and chip portions of the PR remain in place (those are flex
elements where min-h-* works as expected).

Co-Authored-By: Claude Opus 4.6 <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>

* fix(a11y): non-color signal for Label required indicator (WCAG 1.4.1) (#3439)

* fix(a11y): non-color signals for required state and timeline graph nodes (WCAG 1.4.1)

Two SC 1.4.1 Use of Color fixes:

- Label required-field indicator: replace the 6x6 red dot with a
  red asterisk (aria-hidden) so users with color-perception
  differences see a shape signal, not just a color signal. The
  programmatic required state continues to come from native HTML
  `required` on the input (which the 1.3.1 forwarders ensure).
- Timeline graph nodes (workflow-row, history-graph-row-visual):
  add aria-label to the <g role="button"> wrapper so AT users hear
  the workflow id + status (or event type + classification)
  instead of bare "button". Removes the previous dependency on
  reaching the legend tooltip to decode color.

New i18n keys: workflows.row-accessible-name,
events.row-accessible-name.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* revert: remove timeline graph aria-label changes from this PR

Per the PR scope decision, keep this PR focused on the Label
required-indicator fix only. The timeline graph status accessible-
name fix (workflow-row.svelte, history-graph-row-visual.svelte,
and the supporting i18n keys) will ship as its own separate PR
so the two SC 1.4.1 defects can be reviewed independently.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix(a11y): use text-danger so the asterisk actually renders red

The previous text-interactive-error class was a no-op for text
color (interactive-error is only registered under backgroundColor
in the theme plugin, not textColor), so the asterisk inherited
the default text color and rendered black/off-white instead of
red. text-danger maps to --color-text-danger (red.700 light /
red.400 dark) which is the proper red text token.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* refactor: simplify required-asterisk span

The parent <label> already provides text-sm and font-medium, so
the span inherits both. Only text-danger is doing actual work;
the size/weight/leading classes were redundant.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* refactor(label): refine required-asterisk visual alignment

- font-mono: mono asterisks are larger and more geometrically
  centered than proportional ones, making them feel like a proper
  visual indicator rather than a tiny text-style superscript.
- leading-none: tightens the span's line-box to the character.
- translate-y-0.5: small downward nudge so the asterisk aligns
  with the label baseline rather than sitting above it.
- -ml-1: reduces the gap to the preceding label text (8px -> 4px).

Co-Authored-By: Claude Opus 4.6 <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>

* fix(a11y): tabindex on <main> so the skip link moves focus reliably (WCAG 2.4.1) (#3451)

The SkipNavigation primitive renders <a href="#content">, which
targets <main id="content"> in main-content-container.svelte. By
HTML spec, <main> without tabindex is not programmatically
focusable -- so activating the skip link only reliably moves
focus on Chromium-based browsers (Chrome, Edge). Safari and
older Firefox scroll the target into view but leave focus on the
skip link itself, defeating the bypass for keyboard and screen-
reader users.

Adding tabindex="-1" makes <main> programmatically focusable
(so the hash-link navigation lands focus there) while keeping it
out of the natural Tab order. Closes WCAG 2.4.1 sufficient-
technique G1's "programmatically moves focus" requirement across
all browsers.

Co-authored-by: Claude Opus 4.6 <[email protected]>

* fix(a11y): info-and-relationships compliance (WCAG 1.3.1) (#3432)

Four related fixes under SC 1.3.1, grouped per the audit's
correction of the matrix pathway:

- NumberInput / Textarea: forward `required` to the underlying
  native element. Previously the visual red-dot appeared via
  <Label> but the field was not programmatically required, so AT
  users heard an optional-field announcement despite the visible
  indicator.
- RadioGroup: add role="radiogroup" and link the optional
  description via aria-labelledby so AT users hear the group as a
  whole rather than each radio in isolation.
- Tables: add scope="col" to every <th> in product data tables
  (workflows, schedules, workers, batch-operations, child/parent
  workflow relationships). Fixes WCAG technique H63.

Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Ardie Wen <[email protected]>

* fix(query): quote ExecutionDuration Go duration strings in visibility SQL (#3482)

ExecutionDuration is typed as INT in the search attributes store, so
formatValue returns its values unquoted. PR #3435 (DT-4039) added an
explicit unquoted INT path which broke ExecutionDuration because it uses
Go duration strings (30s, 1m30s) that vitess rejects as unquoted tokens.

Add an ExecutionDuration guard before the INT/duration paths: quote
non-integer values as strings, pass plain integers (nanoseconds) through
unquoted.

* Passthrough goto params to link component (#3483)

* feat: add centerButton, menuButton, and linksContent snippets to BottomNavigation (#3485)

* feat: add centerButton snippet to BottomNavigation for custom center content

* feat: add centerButton, menuButton, and linksContent snippets to BottomNavigation

* feat: add bars icon to holocene icon set

* fix: add "for" to validate connection modal title (#3488)

The worker deployment version validate connection modal title now reads
"Validate Connection for <version>" instead of "Validate Connection <version>".

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>

* feat(DT-4069): Update modal backdrop to 50% opaque (#3486)

* refactor: Input & DatePicker - Svelte 5 & afterLabel snippet (#3479)

* Fix decoded object payload summaries (#3491)

* Fix decoded object payload summaries

* Convert event details row to Svelte 5

* fix(DT-4044): only use browser codec endpoint when override option is selected (#3490)

When the user selects "Use Namespace-level setting, where available" and
no namespace codec is configured, the browser endpoint was incorrectly
used as a fallback. Now returns empty string so no codec is invoked.

* fix(a11y): improve 1.1.1 non-text content compliance (#3431)

- nexus-empty-state.svelte: mark Andromeda illustration decorative
  with alt="" + aria-hidden on wrapper. Previously screen readers
  announced "Andromeda, image" (the filename), conveying nothing.
- toast.svelte: add runtime fallback to translate('common.close')
  when closeButtonLabel is missing or empty. TypeScript-required
  prop still flags missing-at-compile-time, but an empty string or
  undefined at runtime now produces a labeled icon button rather
  than aria-label="".

Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Ardie Wen <[email protected]>

* fix(a11y): make Tooltip keyboard-accessible, dismissible, and hoverable (#3429)

* fix: make Tooltip keyboard-accessible, dismissible, and hoverable

The Tooltip primitive was mouse-only: no focus handlers, no Escape
dismiss, and portal tooltips disappeared when moving the pointer to
the popover content. This failed all three WCAG 2.2 SC 1.4.13
criteria (Content on Hover or Focus).

Changes:
- Show tooltip on keyboard focus via focusin/focusout on wrapper
- Dismiss on Escape (resets when interaction ends)
- Track pointer hover on the popover itself (diagonal hover bridge)
- Unify both portal and inline variants on a single isOpen derived
- Add role="tooltip" and aria-describedby linkage for screen readers

No API changes — all existing consumers work identically, with the
added benefit that tooltips now appear on keyboard focus.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* refactor(tooltip): unify hover-zone handling and match repo patterns

- Use <svelte:window on:keydown> instead of reactive document
  listener (mirrors maximizable.svelte)
- Fix hover state lingering when mouse leaves popover to empty
  space by unifying wrapper + popover into a single hover zone
- Extract HOVER_HIDE_DELAY_MS constant
- Drop redundant isPopoverHovered state

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* refactor(tooltip): use crypto.randomUUID() for tooltip id

Align tooltip id generation with the codebase convention used across
Holocene (accordion.svelte, accordion-light.svelte) and the rest of the
app, replacing the ad-hoc Math.random().toString(36) approach.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* refactor(tooltip): drop unused group/tooltip class

The group/tooltip marker only existed to drive the group-hover/tooltip:
utilities that previously controlled visibility. Tooltip open/close is
now fully JS-driven via isOpen, leaving this class with no consumers.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Ardie Wen <[email protected]>

* fix(a11y): name-role-value compliance, partial (WCAG 4.1.2) (#3434)

* fix(a11y): name-role-value compliance (WCAG 4.1.2)

Five fixes under SC 4.1.2 Name, Role, Value:

- Accordion content panel: remove role="textbox". The disclosure
  panel is static content, not an editable text input; AT
  announced "edit text" on every Accordion consumer.
- Accordion + AccordionLight: move slot="action" out of the
  disclosure <button> into a sibling element. Previously
  consumers placing focusable content in the action slot
  produced <button><button>... nested-interactive HTML.
- CopyButton: ship translated default labels via i18n. Empty
  string defaults in CodeBlock previously propagated to an Icon
  that hid itself, producing unlabeled icon-only buttons across
  ~11 CodeBlock instances.
- nav-section: filter out hidden nav items. The NavLinkItem.hidden
  flag was declared on the type and set at source but never
  consulted at render time, so Standalone Activities / Nexus
  links rendered as focusable for AT users on servers that
  cannot serve them.
- Workflow family tree: add aria-label to the two SVG
  <g role="button"> widgets so screen readers announce node
  identity instead of "button".

Deferred to follow-up PRs (audit blockers):
- 4.1.2-button-anchor-empty: audit recommends a two-PR sequenced
  rollout (primitive change + consumer migration).
- 4.1.2-codemirror-no-name: ~5 CodeBlock consumer surfaces need
  design input on label content.
- relationships nested-interactive restructure: site B (button
  wraps Link) and site A's nested <a> extraction are substantial
  SVG/HTML restructures separate from the labeling fix above.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix(a11y): localize workflow family-tree node aria-labels

The family-tree node aria-labels hardcoded the English word "Workflow",
inconsistent with the codebase convention of localizing aria-labels via
translate(). Add a parameterized workflows.family-node-label i18n key and
use it for both the root and child node widgets.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Ardie Wen <[email protected]>

* feat(nexus-operations): standalone nexus operation details page [DT-4002]

Add tab-based detail view for individual nexus operations with live long-polling.
Mirrors standalone activity detail page pattern with nexus-specific fields:
identity, operation, status, timing, attempt, timeout config, cancellation info,
nexus header, and input/outcome payload display.

* feat(nexus-operations): redesign details page to match Figma layout

Restructure the details page around "Operation Event History" with three
subsections: Run Details, Operation Details, Timeout Configuration.

- Add filterable links for endpoint, service, and operation fields
- Add handler namespace and handler operation link from nexus operation links
- Show nexus header code block inline in Operation Details section
- Move last attempt failure into the Attempt section (conditional)
- Add handler namespace note when handler workflow link is present

* fix(nexus-operations): pass namespace to filter links on details page

* fix nexus operation id

---------

Co-authored-by: Andrew Zamojc <[email protected]>
Co-authored-by: Alex Tideman <[email protected]>
Co-authored-by: Ardie Wen <[email protected]>
Co-authored-by: Tegan Churchill <[email protected]>
Co-authored-by: Ross Nelson <[email protected]>
Co-authored-by: Alex Thomsen <[email protected]>
Co-authored-by: Bilal Karim <[email protected]>
Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Ardie Wen <[email protected]>

* fix(nexus-operations): align fetchNexusOperationCountByStatus return type with StatusCounts prop

Return Promise<Required<CountWorkflowExecutionsResponse>> and default groups to [] to match the
pattern used by fetchWorkflowCountByExecutionStatus.

* feat(nexus-operations): standalone nexus operation commands (start/cancel/terminate) [DT-4008] (#3504)

* feat(nexus-operations): add cancel/terminate modals and actions to nexus operation header [DT-4008]

Add cancel confirmation modal, terminate confirmation modal, and nexus
operation actions component. Wire actions into the header with
Request Cancellation button and More Actions menu (Terminate + Start Like
This One). Add route-for helper and i18n strings for the start form.

* feat(nexus-operations): add start standalone nexus operation form and route [DT-4008]

Adds the start nexus operation form with fields for operation ID (with random UUID generation), endpoint, service, operation name, payload input, timeouts, and advanced options (nexus header, search attributes, user metadata, ID policies).

* feat(nexus-operations): pre-fill input payload when starting nexus operation like this one [DT-4008]

Adds fetchInitialValuesForStartNexusOperation to decode the source operation's input payload, encoding, message type, user metadata, and search attributes. The start form pre-populates these fields on mount when operationId and runId are present in the URL, and auto-expands advanced options when there is pre-filled data.

* fix nav width for cloud

* add cloud escape hatch to SANO guard

* add a guard for SANO actions

* fix SANO system saved views

* add runId to routes, links, etc. and fix table columns

* fix import

* fix case of runId

* fix details

* Fix links to use Links property, fix Result/Failure type and rendering, don't make timeouts required

* remove version check from guard

* fix tests

* add integration tests

* update start SANO form to match current designs

* fix breadcrumb text

* add close time to default columns

* fix(sano): show field-level error and toast on duplicate operation ID (409)

When the API returns 409 for a rejected duplicate operation ID, display an
error toast, a field-level hint on the operationId input, and focus the field.

* fix(sano): show completed operation error state for allow-duplicate-failed-only policy

When idReusePolicy is ALLOW_DUPLICATE_FAILED_ONLY and a 409 is returned, show
a distinct toast and field-level error indicating the ID is in use by a completed operation.

* fix(sano): show conflict error state for use-existing ID conflict policy

* refactor(sano): extract 409 error handlers from onUpdate catch block

---------

Co-authored-by: Andrew Zamojc <[email protected]>
Co-authored-by: Alex Tideman <[email protected]>
Co-authored-by: Ardie Wen <[email protected]>
Co-authored-by: Tegan Churchill <[email protected]>
Co-authored-by: Ross Nelson <[email protected]>
Co-authored-by: Alex Thomsen <[email protected]>
Co-authored-by: Bilal Karim <[email protected]>
Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Ardie Wen <[email protected]>
Co-authored-by: Your Name <[email protected]>
Alex-Tideman added a commit that referenced this pull request Jul 11, 2026
* feat(nexus-operations): DT-4005 standalone nexus operations API integrations (#3444)

* feat(nexus-operations): add types and API routes for standalone nexus operations

Adds NexusOperationExecution types, extends APIRouteParameters with operationId,
and registers 6 new API routes (list, describe/start, poll, cancel, terminate, count).
Adds route-for helpers mirroring the standalone activities pattern.

DT-4005

* feat(nexus-operations): add services for standalone nexus operations

Adds fetchPaginatedNexusOperations, startStandaloneNexusOperation,
getNexusOperationExecution, pollNexusOperationExecution, cancel, and terminate.
Adds count and count-by-status service functions.

DT-4005

* feat(nexus-operations): add stores, poller, and filters for standalone nexus operations

Adds nexus operation writable stores (refresh, loading, updating, count, error, query).
Adds StandaloneNexusOperationPoller with long-poll loop mirroring the activity poller.
Adds nexusOperationFilters store to filters.ts.

DT-4005

* test(nexus-operations): add route-for-base-path test cases for standalone nexus operations

* feat(nexus-operations): add capability flag, guard component, and configurable table columns (#3466)

Adds Phase 2 (DT-4006) infrastructure for standalone nexus operations:

- NamespaceCapabilities type with standaloneNexusOperations field in src/lib/types/index.ts
- Guard component that checks both capability flag and minimum server version (1.31.0)
- Disabled state component with Dynamic Config instructions
- i18n namespace for standalone-nexus-operations
- TABLE_TYPE.NEXUS_OPERATIONS, NexusOperationHeaderLabels, default/available column arrays,
  persistedNexusOperationsTableColumns store, configurableTableColumns derived store update,
  availableNexusOperationColumns export, and exhaustive switch cases in configurable-table-columns.ts

* feat(nexus-operations): standalone nexus operations list page [DT-4003] (#3470)

* feat(nexus-operations): add standalone nexus operations list page

- Add list page route at namespaces/[namespace]/nexus-operations
- Add page component with status count filters and configurable table
- Add filter bar with search attribute support
- Add configurable table with header/body cells and empty state
- Add get-nexus-operation-status-and-count utility and tests
- Add nexus operation search attributes to search-attributes store
- Wire nav layout item for standalone nexus operations
- Add i18n strings for nexus operations page

* feat(nexus-operations): add saved views panel, CTA button, and rich empty state

Match Figma design for standalone nexus operations list page:
- Add saved views panel with system/user views, expand/collapse, save/edit/delete/share
- Add 'Start a Standalone Nexus Operation' CTA button via headerActions snippet
- Add rich empty state with value proposition, docs links, and GitHub code samples
- Add i18n strings for all new UI copy
- Add savedNexusQueryNavOpen persist store and savedNexusQueries/systemNexusViews stores

* Update api version to v1.62.12 for SANO APIs and capability

* fix capability name

* feat(nexus-operations): standalone nexus operation details page [DT-4002] (#3495)

* [DT-4039] Workflow query builder doesn't work with numeric search attributes (#3435)

* fix(DT-4039): Workflow query builder for numeric search attributes

The query builder wrapped Int/Double search attribute values in
double quotes (e.g. `Foo="1"`), which the Visibility backend rejects
for numeric types. Stop quoting numeric values in the serializer.

The tokenizer also merged unquoted values with their conditional
operator (e.g. `Foo=1` tokenized as `Foo`, `=1`), which previously
only mattered for booleans and was patched in the parser by stripping
`=`. With numerics this breaks down for 2-char operators (`>=`, `<=`,
`!=`). Fix the tokenizer to emit the conditional as its own token and
to accept operator-style 2-char conditionals without a trailing space.
Update the boolean parser to read the value from `tokenTwoAhead` to
match the new shape.

* fix(DT-4039): Update integration tests for unquoted numeric search attributes

The desktop and mobile workflow-filter specs asserted the previous
buggy serialization (`HistoryLength`="10"). Update them to match the
new correct shape (`HistoryLength`=10).

* Make Common Errors dismissable (#3471)

* Make common errors dismissable

* Remove common errors

* Capitalize Common Errors

* Add description

* Update description

* [DT-4048] Add accessibility PR triage and notification helpers (#3465)

* Implement helpers for accessibility PRs

* fix formatting

* fix linting

* fix title gating regex

* improve deduplication detection on notify

* Move CLAUDE.md a11y conventions to DT-4049

* feat(DT-4017): Schedules List UI Update (#3467)

* fix(markdown): allow nested lists to render as block (DT-4047) (#3463)

* Add a prop to hide select/deselect controls (#3474)

* DT-4051: pre-populate input when starting standalone activity like this one (#3469)

* fix(a11y): reveal Copyable's CopyButton on focus-within (WCAG 2.1.1) (#3452)

The Copyable primitive hides its CopyButton behind
'invisible group-hover:visible'. Tailwind's 'invisible' compiles
to visibility: hidden, which removes the element from the focus
order -- Tab skips it entirely. Mouse users can hover to reveal
and click; keyboard users in default mode have no way to invoke
copy at all.

Add 'group-focus-within:visible' alongside the existing
'group-hover:visible' so the button becomes visible (and
therefore focus-eligible) whenever focus moves anywhere inside
the wrapping group -- typically when the user Tabs onto the
slot's content (workflow IDs, run IDs, etc.). The next Tab now
lands on the CopyButton, where Enter or Space triggers copy.

Applied to both branches (clickAllToCopy and default mode).

Affects detail-list value cells, event-summary rows, and every
other Copyable consumer in default mode.

Co-authored-by: Claude Opus 4.6 <[email protected]>

* fix: accept autocomplete prop on NumberInput, ChipInput, and Combobox (#3425)

Allows consumers to pass WCAG-defined autocomplete tokens (e.g.
"cc-number", "email") to these input primitives. All three previously
hardcoded autocomplete="off", preventing purpose identification per
WCAG 2.2 SC 1.3.5. Default remains "off" so existing behavior is
unchanged.

Co-authored-by: Claude Opus 4.6 <[email protected]>

* fix: cap ZoomSvg container height to viewport (#3428)

Uses CSS min() to limit the container to the lesser of containerHeight
(default 600px) or viewport height minus 8rem. On narrow landscape
viewports (e.g. 320x500) the container now fits within the screen
instead of overflowing. Desktop behavior is unchanged.

Co-authored-by: Claude Opus 4.6 <[email protected]>

* fix(a11y): accommodate text-spacing overrides on Badge and Chip (WCAG 1.4.12) (#3433)

* fix(a11y): accommodate text-spacing overrides (WCAG 1.4.12)

Three same-family fixes for SC 1.4.12 Text Spacing. WCAG requires
that text-spacing user overrides (line-height 1.5, letter-spacing
0.12em, etc.) not cause content loss.

- Badge: leading-4 (16px) -> leading-[1.5]. text-sm at 1.5 line-
  height is 21px which previously clipped.
- Chip: h-7 -> min-h-7, add leading-[1.5]. Preserves the 28px
  visual minimum but allows growth.
- Table row: h-8 -> min-h-8. The densest text-bearing surface in
  the product. Preserves the 32px default but allows growth under
  user override.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* revert(a11y): undo h-8 -> min-h-8 on table row

Per reviewer feedback: <tr> uses table layout rules, where the
height property already acts as a minimum (the row grows to fit
cell content). min-height on <tr> is either ignored or behaves
differently per browser. The change broke the skeleton table
without providing the intended SC 1.4.12 benefit -- under
text-spacing override, the row would have grown naturally.

Badge and chip portions of the PR remain in place (those are flex
elements where min-h-* works as expected).

Co-Authored-By: Claude Opus 4.6 <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>

* fix(a11y): non-color signal for Label required indicator (WCAG 1.4.1) (#3439)

* fix(a11y): non-color signals for required state and timeline graph nodes (WCAG 1.4.1)

Two SC 1.4.1 Use of Color fixes:

- Label required-field indicator: replace the 6x6 red dot with a
  red asterisk (aria-hidden) so users with color-perception
  differences see a shape signal, not just a color signal. The
  programmatic required state continues to come from native HTML
  `required` on the input (which the 1.3.1 forwarders ensure).
- Timeline graph nodes (workflow-row, history-graph-row-visual):
  add aria-label to the <g role="button"> wrapper so AT users hear
  the workflow id + status (or event type + classification)
  instead of bare "button". Removes the previous dependency on
  reaching the legend tooltip to decode color.

New i18n keys: workflows.row-accessible-name,
events.row-accessible-name.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* revert: remove timeline graph aria-label changes from this PR

Per the PR scope decision, keep this PR focused on the Label
required-indicator fix only. The timeline graph status accessible-
name fix (workflow-row.svelte, history-graph-row-visual.svelte,
and the supporting i18n keys) will ship as its own separate PR
so the two SC 1.4.1 defects can be reviewed independently.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix(a11y): use text-danger so the asterisk actually renders red

The previous text-interactive-error class was a no-op for text
color (interactive-error is only registered under backgroundColor
in the theme plugin, not textColor), so the asterisk inherited
the default text color and rendered black/off-white instead of
red. text-danger maps to --color-text-danger (red.700 light /
red.400 dark) which is the proper red text token.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* refactor: simplify required-asterisk span

The parent <label> already provides text-sm and font-medium, so
the span inherits both. Only text-danger is doing actual work;
the size/weight/leading classes were redundant.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* refactor(label): refine required-asterisk visual alignment

- font-mono: mono asterisks are larger and more geometrically
  centered than proportional ones, making them feel like a proper
  visual indicator rather than a tiny text-style superscript.
- leading-none: tightens the span's line-box to the character.
- translate-y-0.5: small downward nudge so the asterisk aligns
  with the label baseline rather than sitting above it.
- -ml-1: reduces the gap to the preceding label text (8px -> 4px).

Co-Authored-By: Claude Opus 4.6 <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>

* fix(a11y): tabindex on <main> so the skip link moves focus reliably (WCAG 2.4.1) (#3451)

The SkipNavigation primitive renders <a href="#content">, which
targets <main id="content"> in main-content-container.svelte. By
HTML spec, <main> without tabindex is not programmatically
focusable -- so activating the skip link only reliably moves
focus on Chromium-based browsers (Chrome, Edge). Safari and
older Firefox scroll the target into view but leave focus on the
skip link itself, defeating the bypass for keyboard and screen-
reader users.

Adding tabindex="-1" makes <main> programmatically focusable
(so the hash-link navigation lands focus there) while keeping it
out of the natural Tab order. Closes WCAG 2.4.1 sufficient-
technique G1's "programmatically moves focus" requirement across
all browsers.

Co-authored-by: Claude Opus 4.6 <[email protected]>

* fix(a11y): info-and-relationships compliance (WCAG 1.3.1) (#3432)

Four related fixes under SC 1.3.1, grouped per the audit's
correction of the matrix pathway:

- NumberInput / Textarea: forward `required` to the underlying
  native element. Previously the visual red-dot appeared via
  <Label> but the field was not programmatically required, so AT
  users heard an optional-field announcement despite the visible
  indicator.
- RadioGroup: add role="radiogroup" and link the optional
  description via aria-labelledby so AT users hear the group as a
  whole rather than each radio in isolation.
- Tables: add scope="col" to every <th> in product data tables
  (workflows, schedules, workers, batch-operations, child/parent
  workflow relationships). Fixes WCAG technique H63.

Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Ardie Wen <[email protected]>

* fix(query): quote ExecutionDuration Go duration strings in visibility SQL (#3482)

ExecutionDuration is typed as INT in the search attributes store, so
formatValue returns its values unquoted. PR #3435 (DT-4039) added an
explicit unquoted INT path which broke ExecutionDuration because it uses
Go duration strings (30s, 1m30s) that vitess rejects as unquoted tokens.

Add an ExecutionDuration guard before the INT/duration paths: quote
non-integer values as strings, pass plain integers (nanoseconds) through
unquoted.

* Passthrough goto params to link component (#3483)

* feat: add centerButton, menuButton, and linksContent snippets to BottomNavigation (#3485)

* feat: add centerButton snippet to BottomNavigation for custom center content

* feat: add centerButton, menuButton, and linksContent snippets to BottomNavigation

* feat: add bars icon to holocene icon set

* fix: add "for" to validate connection modal title (#3488)

The worker deployment version validate connection modal title now reads
"Validate Connection for <version>" instead of "Validate Connection <version>".

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>

* feat(DT-4069): Update modal backdrop to 50% opaque (#3486)

* refactor: Input & DatePicker - Svelte 5 & afterLabel snippet (#3479)

* Fix decoded object payload summaries (#3491)

* Fix decoded object payload summaries

* Convert event details row to Svelte 5

* fix(DT-4044): only use browser codec endpoint when override option is selected (#3490)

When the user selects "Use Namespace-level setting, where available" and
no namespace codec is configured, the browser endpoint was incorrectly
used as a fallback. Now returns empty string so no codec is invoked.

* fix(a11y): improve 1.1.1 non-text content compliance (#3431)

- nexus-empty-state.svelte: mark Andromeda illustration decorative
  with alt="" + aria-hidden on wrapper. Previously screen readers
  announced "Andromeda, image" (the filename), conveying nothing.
- toast.svelte: add runtime fallback to translate('common.close')
  when closeButtonLabel is missing or empty. TypeScript-required
  prop still flags missing-at-compile-time, but an empty string or
  undefined at runtime now produces a labeled icon button rather
  than aria-label="".

Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Ardie Wen <[email protected]>

* fix(a11y): make Tooltip keyboard-accessible, dismissible, and hoverable (#3429)

* fix: make Tooltip keyboard-accessible, dismissible, and hoverable

The Tooltip primitive was mouse-only: no focus handlers, no Escape
dismiss, and portal tooltips disappeared when moving the pointer to
the popover content. This failed all three WCAG 2.2 SC 1.4.13
criteria (Content on Hover or Focus).

Changes:
- Show tooltip on keyboard focus via focusin/focusout on wrapper
- Dismiss on Escape (resets when interaction ends)
- Track pointer hover on the popover itself (diagonal hover bridge)
- Unify both portal and inline variants on a single isOpen derived
- Add role="tooltip" and aria-describedby linkage for screen readers

No API changes — all existing consumers work identically, with the
added benefit that tooltips now appear on keyboard focus.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* refactor(tooltip): unify hover-zone handling and match repo patterns

- Use <svelte:window on:keydown> instead of reactive document
  listener (mirrors maximizable.svelte)
- Fix hover state lingering when mouse leaves popover to empty
  space by unifying wrapper + popover into a single hover zone
- Extract HOVER_HIDE_DELAY_MS constant
- Drop redundant isPopoverHovered state

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* refactor(tooltip): use crypto.randomUUID() for tooltip id

Align tooltip id generation with the codebase convention used across
Holocene (accordion.svelte, accordion-light.svelte) and the rest of the
app, replacing the ad-hoc Math.random().toString(36) approach.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* refactor(tooltip): drop unused group/tooltip class

The group/tooltip marker only existed to drive the group-hover/tooltip:
utilities that previously controlled visibility. Tooltip open/close is
now fully JS-driven via isOpen, leaving this class with no consumers.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Ardie Wen <[email protected]>

* fix(a11y): name-role-value compliance, partial (WCAG 4.1.2) (#3434)

* fix(a11y): name-role-value compliance (WCAG 4.1.2)

Five fixes under SC 4.1.2 Name, Role, Value:

- Accordion content panel: remove role="textbox". The disclosure
  panel is static content, not an editable text input; AT
  announced "edit text" on every Accordion consumer.
- Accordion + AccordionLight: move slot="action" out of the
  disclosure <button> into a sibling element. Previously
  consumers placing focusable content in the action slot
  produced <button><button>... nested-interactive HTML.
- CopyButton: ship translated default labels via i18n. Empty
  string defaults in CodeBlock previously propagated to an Icon
  that hid itself, producing unlabeled icon-only buttons across
  ~11 CodeBlock instances.
- nav-section: filter out hidden nav items. The NavLinkItem.hidden
  flag was declared on the type and set at source but never
  consulted at render time, so Standalone Activities / Nexus
  links rendered as focusable for AT users on servers that
  cannot serve them.
- Workflow family tree: add aria-label to the two SVG
  <g role="button"> widgets so screen readers announce node
  identity instead of "button".

Deferred to follow-up PRs (audit blockers):
- 4.1.2-button-anchor-empty: audit recommends a two-PR sequenced
  rollout (primitive change + consumer migration).
- 4.1.2-codemirror-no-name: ~5 CodeBlock consumer surfaces need
  design input on label content.
- relationships nested-interactive restructure: site B (button
  wraps Link) and site A's nested <a> extraction are substantial
  SVG/HTML restructures separate from the labeling fix above.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix(a11y): localize workflow family-tree node aria-labels

The family-tree node aria-labels hardcoded the English word "Workflow",
inconsistent with the codebase convention of localizing aria-labels via
translate(). Add a parameterized workflows.family-node-label i18n key and
use it for both the root and child node widgets.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Ardie Wen <[email protected]>

* feat(nexus-operations): standalone nexus operation details page [DT-4002]

Add tab-based detail view for individual nexus operations with live long-polling.
Mirrors standalone activity detail page pattern with nexus-specific fields:
identity, operation, status, timing, attempt, timeout config, cancellation info,
nexus header, and input/outcome payload display.

* feat(nexus-operations): redesign details page to match Figma layout

Restructure the details page around "Operation Event History" with three
subsections: Run Details, Operation Details, Timeout Configuration.

- Add filterable links for endpoint, service, and operation fields
- Add handler namespace and handler operation link from nexus operation links
- Show nexus header code block inline in Operation Details section
- Move last attempt failure into the Attempt section (conditional)
- Add handler namespace note when handler workflow link is present

* fix(nexus-operations): pass namespace to filter links on details page

* fix nexus operation id

---------

Co-authored-by: Andrew Zamojc <[email protected]>
Co-authored-by: Alex Tideman <[email protected]>
Co-authored-by: Ardie Wen <[email protected]>
Co-authored-by: Tegan Churchill <[email protected]>
Co-authored-by: Ross Nelson <[email protected]>
Co-authored-by: Alex Thomsen <[email protected]>
Co-authored-by: Bilal Karim <[email protected]>
Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Ardie Wen <[email protected]>

* fix(nexus-operations): align fetchNexusOperationCountByStatus return type with StatusCounts prop

Return Promise<Required<CountWorkflowExecutionsResponse>> and default groups to [] to match the
pattern used by fetchWorkflowCountByExecutionStatus.

* feat(nexus-operations): standalone nexus operation commands (start/cancel/terminate) [DT-4008] (#3504)

* feat(nexus-operations): add cancel/terminate modals and actions to nexus operation header [DT-4008]

Add cancel confirmation modal, terminate confirmation modal, and nexus
operation actions component. Wire actions into the header with
Request Cancellation button and More Actions menu (Terminate + Start Like
This One). Add route-for helper and i18n strings for the start form.

* feat(nexus-operations): add start standalone nexus operation form and route [DT-4008]

Adds the start nexus operation form with fields for operation ID (with random UUID generation), endpoint, service, operation name, payload input, timeouts, and advanced options (nexus header, search attributes, user metadata, ID policies).

* feat(nexus-operations): pre-fill input payload when starting nexus operation like this one [DT-4008]

Adds fetchInitialValuesForStartNexusOperation to decode the source operation's input payload, encoding, message type, user metadata, and search attributes. The start form pre-populates these fields on mount when operationId and runId are present in the URL, and auto-expands advanced options when there is pre-filled data.

* fix nav width for cloud

* add cloud escape hatch to SANO guard

* add a guard for SANO actions

* fix SANO system saved views

* add runId to routes, links, etc. and fix table columns

* fix import

* fix case of runId

* fix details

* Fix links to use Links property, fix Result/Failure type and rendering, don't make timeouts required

* remove version check from guard

* fix tests

* add integration tests

* update start SANO form to match current designs

* fix breadcrumb text

* add close time to default columns

* fix(sano): show field-level error and toast on duplicate operation ID (409)

When the API returns 409 for a rejected duplicate operation ID, display an
error toast, a field-level hint on the operationId input, and focus the field.

* fix(sano): show completed operation error state for allow-duplicate-failed-only policy

When idReusePolicy is ALLOW_DUPLICATE_FAILED_ONLY and a 409 is returned, show
a distinct toast and field-level error indicating the ID is in use by a completed operation.

* fix(sano): show conflict error state for use-existing ID conflict policy

* refactor(sano): extract 409 error handlers from onUpdate catch block

---------

Co-authored-by: Andrew Zamojc <[email protected]>
Co-authored-by: Alex Tideman <[email protected]>
Co-authored-by: Ardie Wen <[email protected]>
Co-authored-by: Tegan Churchill <[email protected]>
Co-authored-by: Ross Nelson <[email protected]>
Co-authored-by: Alex Thomsen <[email protected]>
Co-authored-by: Bilal Karim <[email protected]>
Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Ardie Wen <[email protected]>
Co-authored-by: Your Name <[email protected]>
ardiewen added a commit that referenced this pull request Jul 13, 2026
…3607)

* a11y(1.4.13): migrate saved-query nav tooltips to Tooltip primitive

Replaces the hand-rolled JS-positioned tooltip in saved-query-views.svelte
and standalone-activities/saved-views.svelte with the Holocene Tooltip
primitive (PR #3429).

Each file previously maintained ~40 lines of custom state and DOM
positioning logic (showTooltip, tooltipText, tooltipX/Y, positionTooltipFrom,
onQueryBtnEnter/Move/Leave, use:portal floating <div>) that failed three
SC 1.4.13 sub-criteria:

- Dismissible: no Escape handler
- Hoverable: pointer crossing the wrapper boundary fired onmouseleave
  before reaching the fixed <div>, dismissing the tooltip
- ARIA: plain <div role="tooltip"> with no aria-describedby on the trigger

The Tooltip primitive supplies all three:
- Escape-to-dismiss via svelte:window keydown (dismissed flag auto-resets)
- Hover bridge: 120 ms HOVER_HIDE_DELAY_MS lets the pointer transit
  onto the Portal-rendered popover; mouseenter on the popover cancels the
  hide timer
- aria-describedby on the wrapper <div>; role="tooltip" + id on the popover

hide={$savedQueryNavOpen} preserves the existing gate: tooltip is
suppressed while the nav rail is expanded (full name already visible).
usePortal delegates positioning to the Portal/Floating-UI integration,
replacing the manual getBoundingClientRect arithmetic and also closing
the 1.4.4 fixed-pixel-width gap on that surface.

Net: ~80 lines removed across two files; no new lines of tooltip logic added.

A11y-Audit-Ref: 1.4.13-saved-query-hand-rolled-tooltip

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>

* chore: fix prettier formatting on tooltip migration files

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>

* fix: restore max width to avoid visual regression

* a11y(4.1.2): add accessible name to icon-only saved-query buttons

Collapsed rail buttons render icon-only (name span is gated behind
$savedQueryNavOpen), leaving the <button> with no accessible name — SR
announces just "button". The tooltip conveys the label visually only.
Add aria-label={view.name} so the focusable button has a stable name in
both collapsed and expanded states.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* a11y(4.1.2): add accessible name to icon-only share button

The share button's "Share" text span is gated behind $savedQueryNavOpen,
so a collapsed rail leaves only the copy icon and no accessible name.
Add aria-label="Share" so it announces in both states.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* a11y(1.4.13): make Tooltip Escape dismissal capture-phase

The Escape handler was a bubble-phase window listener, so it never
received the event when the tooltip trigger stopped keydown propagation
(e.g. Holocene Button's on:keydown|stopPropagation). Keyboard Escape
therefore failed to dismiss saved-query nav tooltips. Capture-phase runs
before the target's stopPropagation, fixing dismissal for all
Button-triggered Tooltip consumers.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

---------

Co-authored-by: Claude Sonnet 4.6 <[email protected]>
Co-authored-by: Ardie Wen <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a11y:bucket-3 Bucket 3: engineer required a11y:sc-1.4.13 a11y Accessibility audit PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants