Skip to content

feat(schema, vibe): schema explorer - #451

Merged
rapids-bot[bot] merged 4 commits into
rapidsai:mainfrom
johanpel:schema-visualize
Aug 7, 2026
Merged

feat(schema, vibe): schema explorer#451
rapids-bot[bot] merged 4 commits into
rapidsai:mainfrom
johanpel:schema-visualize

Conversation

@johanpel

@johanpel johanpel commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Adds a little playground for YAML DSL to capture schemas and visualizes the entity tree plus shows you what a resource timeline would look like approximately.

Screen.Recording.2026-08-07.at.12.26.56.mov

This is 100% vibe coded, so I'm proposing to open up two directories under the repo:

  • experimental/ for experimental stuff, might have some dead code due to building stuff up bottom-up, and stuff might not be checked by CI for breaking changes, place for folks to iterate on stuff quickly
  • experimental/vibe same as above but fully vibe coded, use at your own risk

@johanpel
johanpel marked this pull request as ready for review August 7, 2026 10:36
@johanpel

johanpel commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot
rapids-bot Bot merged commit 8acccff into rapidsai:main Aug 7, 2026
22 of 23 checks passed
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds an experimental Svelte Schema Explorer, a reusable schema-viewer package, YAML-to-WebAssembly parsing, example schemas, comprehensive tests, workspace configuration, documentation, and GitHub Pages deployment.

Changes

Schema Explorer workspace and YAML parsing

Layer / File(s) Summary
Workspace, YAML parser, and explorer application
experimental/vibe/ui/*, experimental/vibe/ui/yaml-wasm/*, experimental/vibe/ui/schema-explorer/*
Adds the pnpm/Cargo workspace, YAML WebAssembly parser, Svelte explorer application, editor, graph configuration, selection details, example schemas, and parser verification scripts.
Schema viewer library
experimental/vibe/ui/schema-viewer/src/lib/*, experimental/vibe/ui/schema-viewer/src/public.ts, experimental/vibe/ui/schema-viewer/src/register.ts
Adds schema extraction, graph and timeline models, ELK layouts, selection utilities, XYFlow adapters, public types, and custom-element registration.
Viewer components and styling
experimental/vibe/ui/schema-viewer/src/components/*, experimental/vibe/ui/schema-viewer/src/styles.css
Adds graph, resource timeline, FSM, resource, record, event, and data-type components with interactive selection, hover, layout, and viewport behavior.
Validation and fixtures
experimental/vibe/ui/schema-viewer/tests/*
Adds component, schema, layout, XYFlow, and resource-timeline tests with browser shims and a comprehensive schema fixture.
Build and deployment integration
.github/workflows/schema-explorer-pages.yml, experimental/README.md, experimental/vibe/README.md, experimental/vibe/ui/*/README.md
Adds experimental-workspace documentation and a GitHub Pages workflow for building and deploying the explorer.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • rapidsai/quent#406: Introduces the fsms: YAML DSL consumed by the explorer and FSM visualization.
  • rapidsai/quent#449: Introduces the Path-based schema model consumed by the parser and viewer utilities.

Suggested labels: feature request, non-breaking

Suggested reviewers: dhruv9vats, mbrobbel

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the schema explorer feature and matches the primary changes in the pull request.
Description check ✅ Passed The description explains the feature, its experimental scope, and includes a screenshot, but it omits testing details and related issue information.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

🟡 Other comments (11)
experimental/vibe/ui/schema-viewer/tests/components.test.ts-221-243 (1)

221-243: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Harden the assertions that use possibly undefined values.

fsm.textContent?.trim() and state.dataset.state can be undefined. toMatchObject then compares undefined against a missing or undefined property, so the assertion can pass without validating the payload. Capture the values first and assert that they are defined.

♻️ Proposed hardening
     const fsm = graph.querySelector<HTMLButtonElement>(
       '[data-quent-role="timeline-fsm-select"]',
     )!;
+    const fsmName = fsm.textContent?.trim();
+    expect(fsmName).toBeTruthy();
     fsm.click();
     expect(onSelect.mock.calls.at(-1)?.[0].detail).toMatchObject({
       kind: 'entity',
-      entity: { name: fsm.textContent?.trim() },
+      entity: { name: fsmName },
     });
     const state = graph.querySelector<HTMLButtonElement>(
       '[data-quent-role="timeline-fsm-state"]',
     )!;
+    const stateName = state.dataset.state;
+    expect(stateName).toBeTruthy();
     state.dispatchEvent(new PointerEvent('pointerenter', { bubbles: true }));
     expect(onHover.mock.calls.at(-1)?.[0].detail).toMatchObject({
       kind: 'fsm-state',
-      state: state.dataset.state,
+      state: stateName,
     });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@experimental/vibe/ui/schema-viewer/tests/components.test.ts` around lines 221
- 243, Harden the assertions in the timeline FSM test by capturing the trimmed
`fsm.textContent` and `state.dataset.state` values, asserting each is defined,
then using those validated values in the `onSelect` and `onHover`/`onSelect`
payload expectations. Remove optional chaining from the expected values so
missing data cannot make `toMatchObject` pass.
experimental/vibe/ui/schema-viewer/tests/xyflow.test.ts-226-239 (1)

226-239: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Strengthen the label assertions; they can pass without checking anything.

Two problems exist here:

  1. Line 232 pairs flow.edges[index] with layout.references[index]. The adapter filters references, so the two arrays are not guaranteed to be index-aligned.
  2. edge.data?.labelX === position?.x returns true when both sides are undefined. If the adapter stops emitting labelX, this test still passes.

Add a length assertion and require the values to be defined. The same empty-array risk applies to lines 258-276, where .every() runs without a prior length assertion on flow.edges.

♻️ Proposed hardening
+    expect(flow.edges.length).toBe(layout.references.length);
+    expect(flow.edges.length).toBeGreaterThan(0);
     expect(
       layout.references.every(
         (reference) => reference.labelPosition !== null,
       ),
     ).toBe(true);
     expect(
       flow.edges.every((edge, index) => {
         const position = layout.references[index]?.labelPosition;
         return (
+          position !== undefined &&
+          position !== null &&
+          Number.isFinite(edge.data?.labelX) &&
           edge.data?.labelX === position?.x &&
           edge.data?.labelY === position?.y
         );
       }),
     ).toBe(true);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@experimental/vibe/ui/schema-viewer/tests/xyflow.test.ts` around lines 226 -
239, Strengthen the label assertions in the xyflow test by asserting the
relevant edge and reference collections have the expected nonzero/matching
lengths before using every. In the flow.edges validation, match each edge to its
corresponding layout reference through the adapter’s stable identity rather than
array indexes, and require labelX, labelY, and the reference coordinates to be
defined before comparing them. Apply the same flow.edges length assertion to the
checks covering lines 258–276.
experimental/vibe/ui/schema-explorer/scripts/build-yaml-wasm.sh-7-18 (1)

7-18: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Pin the wasm-bindgen CLI to version 0.2.126.

The script accepts any wasm-bindgen executable from PATH. Invoke the CLI from a project-managed installation that matches the crate and lockfile version 0.2.126.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@experimental/vibe/ui/schema-explorer/scripts/build-yaml-wasm.sh` around lines
7 - 18, Update the wasm-bindgen invocation in the build script to use the
project-managed CLI pinned to version 0.2.126, rather than resolving an
arbitrary executable from PATH. Preserve the existing arguments, output
directory, and generated name.
experimental/vibe/ui/schema-explorer/src/yaml-schema.ts-10-15 (1)

10-15: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Reset the failed initialization promise.

If initialize() rejects once, initialization remains rejected. Every later YAML edit then fails before parsing, even if the WASM resource becomes available. Clear the cached promise when initialization fails.

Proposed fix
 let initialization: Promise<unknown> | null = null;

 export async function parseYamlSchema(source: string): Promise<Schema> {
-  initialization ??= initialize();
+  initialization ??= initialize().catch((error) => {
+    initialization = null;
+    throw error;
+  });
   await initialization;
   return JSON.parse(parse_schema_json(source)) as Schema;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@experimental/vibe/ui/schema-explorer/src/yaml-schema.ts` around lines 10 -
15, Update parseYamlSchema’s cached initialization flow so a rejected
initialize() promise clears initialization before the rejection propagates.
Preserve successful promise reuse and parsing behavior, while allowing a later
invocation to retry initialization.
experimental/vibe/ui/schema-explorer/package.json-9-9 (1)

9-9: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use the package-local Vite configuration.

Change --config ../vite.config.ts to --config ./vite.config.ts. The src workspace exists, and --tsconfig ../tsconfig.json resolves to the package-local tsconfig.json.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@experimental/vibe/ui/schema-explorer/package.json` at line 9, Update the
package.json check script to pass the package-local Vite configuration via
./vite.config.ts instead of ../vite.config.ts, while preserving the existing
workspace and TypeScript configuration arguments.
experimental/vibe/ui/schema-explorer/scripts/verify-yaml-wasm.mjs-1-3 (1)

1-3: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Complete the contribution prerequisites before merge. PR #451 has no linked approved Quent maintainer issue. Add a Signed-off-by: trailer to all four commits. Wait for the pending CodeRabbit check. The PR title and all other required checks pass.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@experimental/vibe/ui/schema-explorer/scripts/verify-yaml-wasm.mjs` around
lines 1 - 3, Complete the contribution prerequisites for PR `#451` by linking an
approved Quent maintainer issue, adding a Signed-off-by trailer to each of the
four commits, and waiting for the pending CodeRabbit check to pass before
merging.

Source: Coding guidelines

experimental/vibe/ui/schema-viewer/package.json-25-25 (1)

25-25: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Remove the unsupported --config option.

svelte-check 4.7.4 does not support --config, so the check script fails before tsc --noEmit. Keep --tsconfig ../tsconfig.json; with --workspace ./src, it resolves to the package's existing tsconfig.json.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@experimental/vibe/ui/schema-viewer/package.json` at line 25, Update the
package check script to remove the unsupported --config argument from the
svelte-check invocation while preserving --workspace ./src, --tsconfig
../tsconfig.json, and the subsequent tsc --noEmit command.
experimental/vibe/ui/schema-viewer/src/styles.css-318-318 (1)

318-318: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix the stylelint error. Use lowercase currentcolor.

Stylelint reports value-keyword-case on this line. The lint job fails, so the pull request cannot merge.

🛠️ Proposed fix
-  border: 1px solid currentColor;
+  border: 1px solid currentcolor;

As per coding guidelines: "All CI checks must pass before a pull request is merged."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@experimental/vibe/ui/schema-viewer/src/styles.css` at line 318, Update the
border declaration to use the lowercase CSS keyword currentcolor instead of
currentColor, resolving the value-keyword-case stylelint error while preserving
the existing border styling.

Sources: Coding guidelines, Linters/SAST tools

experimental/vibe/ui/schema-viewer/src/components/ResourceTimeline.svelte-216-226 (1)

216-226: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

The aria-label on line 218 is ignored.

The element is a div with no role. ARIA does not expose aria-label on a generic container, so assistive technology announces nothing for the capacity chart.

Add role="img" to make the label effective. Mark the bar elements as decorative.

🛠️ Proposed fix
                     <div
                       class="quent-resource-timeline__bins"
+                      role="img"
                       aria-label={`${capacity.name} illustrative utilization`}
                     >
                       {`#each` capacity.bins as bin (bin.id)}
                         <i
+                          aria-hidden="true"
                           data-quent-role="timeline-capacity-bin"
                           style={`height:${bin.height}px`}
                         ></i>
                       {/each}
                     </div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@experimental/vibe/ui/schema-viewer/src/components/ResourceTimeline.svelte`
around lines 216 - 226, Update the capacity chart container in the
ResourceTimeline markup to add role="img" alongside its existing aria-label,
then mark each timeline capacity bin element identified by
data-quent-role="timeline-capacity-bin" as decorative with aria-hidden="true".
experimental/vibe/ui/schema-viewer/src/components/details/RecordsSection.svelte-42-55 (1)

42-55: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Selection state is visual only in the details sections. Both components mark the selected item with CSS classes and provide no accessible state. Screen reader users cannot tell which item is selected. Compute the selectionMatches result once per item and bind it to aria-pressed.

  • experimental/vibe/ui/schema-viewer/src/components/details/RecordsSection.svelte#L42-L55: add {@const isSelected = selectionMatches(selection, recordSelection)}, set aria-pressed={isSelected} on the record button, and reuse isSelected in classNames.
  • experimental/vibe/ui/schema-viewer/src/components/details/ResourcesSection.svelte#L128-L140: add the same isSelected constant for roleSelection, set aria-pressed={isSelected} on the resource-record button, and apply the equivalent change to the resource title button at Lines 62-68.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@experimental/vibe/ui/schema-viewer/src/components/details/RecordsSection.svelte`
around lines 42 - 55, Make selection state accessible in RecordsSection.svelte
and ResourcesSection.svelte by computing each item’s selectionMatches result
once as isSelected, adding aria-pressed={isSelected}, and reusing isSelected for
the selected CSS classes. Apply this to the record button in
RecordsSection.svelte#L42-L55, the resource-record button in
ResourcesSection.svelte#L128-L140, and the resource title button in
ResourcesSection.svelte#L62-L68.
experimental/vibe/ui/schema-viewer/src/components/EntityGraph.svelte-100-132 (1)

100-132: 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

Defer inactive view computations.

ResourceTimeline does not receive active and always calls buildResourceTimeline. GraphView shadows its active prop with a local lifecycle flag, so it starts layoutEntityGraph while hidden. Use the prop to guard both computations.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@experimental/vibe/ui/schema-viewer/src/components/EntityGraph.svelte` around
lines 100 - 132, Update the EntityGraph view rendering to pass the active state
into ResourceTimeline and ensure both ResourceTimeline’s buildResourceTimeline
and GraphView’s layoutEntityGraph execute only when their active prop is true.
Remove or rename GraphView’s local active shadowing flag so the component prop
controls the computation and hidden views defer work.
🧹 Nitpick comments (16)
experimental/vibe/ui/schema-explorer/src/SelectionBreadcrumbs.svelte (1)

38-91: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add an exhaustiveness guard to buildBreadcrumbs.

The switch covers the seven current SchemaSelection kinds and has no default case. The function then relies on the union staying exhaustive. If a new kind is added to SchemaSelection in schema-viewer/src/lib/types.ts, this function returns undefined. Line 99 then iterates undefined and the breadcrumb bar throws at runtime.

Add a never check so a new kind produces a compile error instead of a runtime failure.

♻️ Proposed exhaustiveness guard
       case 'resource-record':
         return [
           {
             label: pathKey(value.resource),
             kind: 'Resource',
             selection: {
               kind: 'resource',
               resource: value.resource,
             },
           },
           { label: pathKey(value.record), kind: 'Record' },
         ];
+      default: {
+        const unhandled: never = value;
+        throw new Error(
+          `Unhandled selection kind: ${(unhandled as SchemaSelection).kind}`,
+        );
+      }
     }
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@experimental/vibe/ui/schema-explorer/src/SelectionBreadcrumbs.svelte` around
lines 38 - 91, Update buildBreadcrumbs so its switch over SchemaSelection
handles the unreachable fallthrough with a never-based exhaustiveness check.
Ensure every current selection kind still returns its breadcrumbs, while any
future kind causes a compile-time error rather than allowing the function to
return undefined.
experimental/vibe/ui/schema-viewer/src/lib/layout.ts (1)

510-526: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Clear the cached engine promise when initialization fails.

Line 511 caches the promise in engine. If the dynamic import or the worker construction fails, the rejected promise stays cached. Every later layoutEntityGraph and layoutFsmTopology call then rejects with the same error, and the view cannot recover without a page reload.

♻️ Proposed change
 function layoutEngine(): Promise<ELK> {
   engine ??= typeof Worker === 'undefined'
     ? import('elkjs/lib/elk.bundled.js').then(
         ({ default: ElkConstructor }) =>
           new ElkConstructor({
             algorithms: ['layered'],
           }),
       )
     : import('elkjs/lib/elk-api.js').then(
         ({ default: ElkConstructor }) =>
           new ElkConstructor({
             algorithms: ['layered'],
             workerFactory: () => new ElkWorker(),
           }) as ELK,
       );
+  engine.catch(() => {
+    engine = undefined;
+  });
   return engine;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@experimental/vibe/ui/schema-viewer/src/lib/layout.ts` around lines 510 - 526,
Update layoutEngine to clear the cached engine promise when dynamic import or
ELK/worker construction fails, so subsequent layoutEntityGraph and
layoutFsmTopology calls retry initialization instead of reusing a rejected
promise.
experimental/vibe/ui/schema-viewer/src/lib/types.ts (1)

69-90: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Derive ResolvedEntityGraphConfig from EntityGraphConfig.

Every key is restated manually. A new optional key in EntityGraphConfig will not appear here, and the compiler will not report the omission. Required<EntityGraphConfig> keeps both types in sync.

♻️ Proposed refactor
-export interface ResolvedEntityGraphConfig {
-  direction: NonNullable<EntityGraphConfig['direction']>;
-  edgeRouting: NonNullable<EntityGraphConfig['edgeRouting']>;
-  density: NonNullable<EntityGraphConfig['density']>;
-  layeringStrategy: NonNullable<EntityGraphConfig['layeringStrategy']>;
-  nodePlacementStrategy: NonNullable<
-    EntityGraphConfig['nodePlacementStrategy']
-  >;
-  hierarchicalGreedySwitch: boolean;
-  layoutThoroughness: number;
-  highDegreeNodeTreatment: boolean;
-  groupNamespaces: boolean;
-  references: NonNullable<EntityGraphConfig['references']>;
-  referenceLabels: NonNullable<EntityGraphConfig['referenceLabels']>;
-  showNodeMetadata: boolean;
-  showViewSwitcher: boolean;
-  fitPadding: number;
-  minZoom: number;
-  maxZoom: number;
-  nodeWidth: number;
-  nodeHeight: number;
-}
+export type ResolvedEntityGraphConfig = Required<EntityGraphConfig>;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@experimental/vibe/ui/schema-viewer/src/lib/types.ts` around lines 69 - 90,
Update ResolvedEntityGraphConfig to derive its keys from EntityGraphConfig using
Required<EntityGraphConfig>, preserving the existing resolved non-nullable
property behavior while ensuring newly added configuration keys are checked and
included automatically.
experimental/vibe/ui/schema-viewer/src/styles.css (1)

31-46: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Wrap these rules in :where() for consistent specificity.

The file wraps almost every selector in :where() to give zero specificity. That lets a consumer override the styles through the classes hooks declared in EntityGraphClasses and SchemaDetailsClasses in src/lib/types.ts.

These rules are not wrapped:

  • .quent-schema-name and .quent-schema-name--title on lines 31-46.
  • .quent-schema-details__fsm-flow-node-* on lines 761-822.

They carry class specificity (0,1,0). A consumer class passed through classes also carries (0,1,0), so source order decides the winner, not the consumer. The override contract is unreliable for these elements.

Wrap them in :where() to match the rest of the file.

♻️ Proposed refactor
-.quent-schema-name {
+:where(.quent-schema-name) {
   padding: 0;
-.quent-schema-name--title {
+:where(.quent-schema-name--title) {
   color: inherit;
 }
-.quent-schema-details__fsm-flow-node-wrapper {
+:where(.quent-schema-details__fsm-flow-node-wrapper) {
   display: grid;

Apply the same change to the remaining .quent-schema-details__fsm-flow-node-* rules on lines 772-822.

Note: --selected, --entry, and --exit modifiers must still win over the base rule. Keep their source order after the base rule, because :where() removes the specificity tiebreak.

Also applies to: 761-822

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@experimental/vibe/ui/schema-viewer/src/styles.css` around lines 31 - 46, Wrap
the `.quent-schema-name` and `.quent-schema-name--title` selectors in
`:where()`, and apply the same zero-specificity wrapping to all
`.quent-schema-details__fsm-flow-node-*` rules. Preserve the existing source
order so the `--selected`, `--entry`, and `--exit` modifiers remain after and
override the base flow-node rule.
experimental/vibe/ui/schema-viewer/src/components/details/DataTypeDisplay.svelte (2)

24-26: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated classNames helper in two detail components. Both files declare the same three-line helper with the same signature and the same body. The shared root cause is a missing class-name utility in the package. Other detail components will copy it again.

  • experimental/vibe/ui/schema-viewer/src/components/details/DataTypeDisplay.svelte#L24-L26: remove the local helper and import classNames from a shared module, for example src/lib/classNames.ts.
  • experimental/vibe/ui/schema-viewer/src/components/details/EntityEventsSection.svelte#L31-L33: remove the local helper and import the same shared classNames.

The library already uses an inline [...].filter(Boolean).join(' ') pattern in src/lib/xyflow.ts and in src/components/ResourceTimeline.svelte. Move that pattern into the shared module too, so one implementation covers every call site.

♻️ Proposed shared module
// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

export function classNames(
  ...values: Array<string | undefined | null | false>
): string {
  return values.filter(Boolean).join(' ');
}

Then in each component:

-  function classNames(...values: Array<string | undefined | false>): string {
-    return values.filter(Boolean).join(' ');
-  }
+  import { classNames } from '../../lib/classNames';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@experimental/vibe/ui/schema-viewer/src/components/details/DataTypeDisplay.svelte`
around lines 24 - 26, Create a shared classNames utility in
experimental/vibe/ui/schema-viewer/src/lib/classNames.ts using the existing
filter-and-join behavior, then remove the local classNames helper from
DataTypeDisplay.svelte at lines 24-26 and EntityEventsSection.svelte at lines
31-33 and import the shared function in both components.

58-60: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use a distinct DataTypePart.kind for each reference label.

dataTypeParts currently emits reference-label for both labels, so extend the discriminant for target and data labels. Select data-quent-role from part.kind, not from the display text in part.value.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@experimental/vibe/ui/schema-viewer/src/components/details/DataTypeDisplay.svelte`
around lines 58 - 60, Update the DataTypePart discriminant used by dataTypeParts
so target and data reference labels receive distinct kind values instead of
sharing reference-label. In DataTypeDisplay, derive data-quent-role from
part.kind rather than comparing part.value, preserving the existing target and
data role mappings.
experimental/vibe/ui/schema-viewer/src/components/GraphView.svelte (1)

95-95: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Rename the local cancellation flag. It shadows the active prop.

Line 95 declares let active = true; inside the effect. The component already has an active prop, destructured at line 58 and passed to FitViewOnLayout on line 241. Inside the effect body the prop is unreachable.

Rename the local variable to cancelled or current. This prevents a future edit from reading the wrong active.

♻️ Proposed refactor
-    let active = true;
+    let cancelled = false;
       .then((result) => {
-        if (!active) {
+        if (cancelled) {
           return;
         }
       .catch((error: unknown) => {
-        if (!active) {
+        if (cancelled) {
           return;
         }
     return () => {
-      active = false;
+      cancelled = true;
     };

Also applies to: 150-152

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@experimental/vibe/ui/schema-viewer/src/components/GraphView.svelte` at line
95, Rename the effect-local `active` cancellation flag and its references around
lines 150–152 to `cancelled` or `current`, while leaving the component `active`
prop and its `FitViewOnLayout` usage unchanged.
experimental/vibe/ui/schema-viewer/src/components/details/RecordsSection.svelte (1)

24-26: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the duplicated classNames helper.

classNames is defined identically in this file, ResourcesSection.svelte, and FsmTransitionAttributes.svelte. Move it to a shared module under src/lib and import it. This removes the duplication and keeps one definition to maintain.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@experimental/vibe/ui/schema-viewer/src/components/details/RecordsSection.svelte`
around lines 24 - 26, Extract the duplicated classNames helper from
RecordsSection.svelte into a shared module under src/lib, then import and use
that shared function in RecordsSection.svelte, ResourcesSection.svelte, and
FsmTransitionAttributes.svelte. Remove the local definitions while preserving
the existing filtering and space-joining behavior.
experimental/vibe/ui/schema-viewer/src/lib/schema.ts (2)

365-403: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Narrow the roles parameter type.

collectResourceConsumers and CollectResourceConsumerInput type roles as Map<string, unknown>, but the code only calls roles.has(recordKey). Pass a ReadonlySet<string> of record keys, or the concrete role type from buildResources. This keeps the contract explicit and prevents accidental misuse of the erased value type.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@experimental/vibe/ui/schema-viewer/src/lib/schema.ts` around lines 365 - 403,
Narrow the roles contract in collectResourceConsumers and
CollectResourceConsumerInput from Map<string, unknown> to ReadonlySet<string>,
since consumers only require has(recordKey). Update the corresponding caller,
such as buildResources, to pass the record-key set while preserving all existing
membership checks.

89-101: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Precompute reference counts to avoid a quadratic scan.

references.filter(...) runs once per entity. The cost is O(entities × references). Build a count map once, then read it per node.

♻️ Proposed refactor
+  const referenceCounts = new Map<string, number>();
+  for (const reference of references) {
+    const key = pathKey(reference.source);
+    referenceCounts.set(key, (referenceCounts.get(key) ?? 0) + 1);
+  }
+
   return {
     nodes: schema.entities.map(([path, entity]) => {
       const resource = parseResource(entity.annotations);
       return {
         id: pathKey(path),
         path,
         eventCount: Object.keys(entity.events).length,
-        referenceCount: references.filter(
-          (reference) => pathKey(reference.source) === pathKey(path),
-        ).length,
+        referenceCount: referenceCounts.get(pathKey(path)) ?? 0,
         fsm: parseFsm(entity) !== null,
         resource: resource?.kind === 'definition',
       };
     }),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@experimental/vibe/ui/schema-viewer/src/lib/schema.ts` around lines 89 - 101,
Update the schema node-building flow around the `nodes` mapping to precompute
reference counts in a map keyed by `pathKey(reference.source)` before iterating
entities. Replace the per-entity `references.filter(...)` scan with a map
lookup, defaulting to zero when no references exist, while preserving the
existing node fields and values.
.github/workflows/schema-explorer-pages.yml (1)

34-39: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Derive the base path from configure-pages instead of hardcoding /quent/.

The build runs before Set up Pages, and SCHEMA_EXPLORER_BASE is hardcoded to /quent/. If the repository is renamed or forked, every asset URL in the deployed site breaks. actions/configure-pages outputs base_path. Move that step before the build and read the output.

♻️ Proposed change
+      - name: Set up Pages
+        id: pages
+        uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6
       - name: Install dependencies
         run: pixi run --frozen pnpm --dir experimental/vibe/ui install --frozen-lockfile
       - name: Build
         run: pixi run --frozen pnpm --dir experimental/vibe/ui build
         env:
-          SCHEMA_EXPLORER_BASE: /quent/
-      - name: Set up Pages
-        uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6
+          SCHEMA_EXPLORER_BASE: ${{ steps.pages.outputs.base_path }}/
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/schema-explorer-pages.yml around lines 34 - 39, Move the
“Set up Pages” step using configure-pages before “Build”, then replace the
hardcoded SCHEMA_EXPLORER_BASE value with the step’s base_path output via its
step id. Ensure the configure-pages step exposes a unique id and the build
consumes that output.

Source: Path instructions

experimental/vibe/ui/schema-viewer/src/components/details/ResourcesSection.svelte (1)

26-34: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use the shared record type instead of indexing usages.

resourceGroups types the bounds records with ResourceDefinition['usages']. This couples the bounds group to the usages field. ResourceRecord is exported from ./lib/types. Use it directly so a future divergence between usages and bounds produces a type error instead of a silent mismatch.

♻️ Proposed change
   import type {
     ResourceDefinition,
+    ResourceRecord,
     SchemaDetailsClasses,
     SchemaSelection,
   } from '../../lib/types';
@@
   function resourceGroups(resource: ResourceDefinition): Array<{
     role: 'usage' | 'bounds';
-    records: ResourceDefinition['usages'];
+    records: ResourceRecord[];
   }> {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@experimental/vibe/ui/schema-viewer/src/components/details/ResourcesSection.svelte`
around lines 26 - 34, Update the records type in resourceGroups to use the
exported ResourceRecord type from ./lib/types instead of
ResourceDefinition['usages'], while preserving the existing usage and bounds
group behavior.
experimental/vibe/ui/schema-viewer/src/components/FitViewOnLayout.svelte (1)

34-70: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Track fit options synchronously and include them in the fit guard.

Read the options before tick. Store the options used by the last successful fit. Otherwise, an option change still returns because fittedVersion === nextVersion. Read fittedVersion with untrack to avoid the extra effect pass. useViewportInitialized is available in @xyflow/svelte 1.6.2.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@experimental/vibe/ui/schema-viewer/src/components/FitViewOnLayout.svelte`
around lines 34 - 70, Update the effect in FitViewOnLayout to synchronously
capture the current fit options before tick, and include those options in the
already-fitted guard so option changes trigger a new fit even when version is
unchanged. Store the options associated with the last successful fit, read
fittedVersion via untrack, and use the available useViewportInitialized API
where applicable.
experimental/vibe/ui/schema-viewer/src/svelte.d.ts (1)

4-9: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the wildcard *.svelte shim. The package already runs svelte-check, which generates component types. This shim makes TypeScript resolve Svelte imports as Component<Record<string, unknown>> and bypasses prop checks for components such as EntityNodeComponent and the details components. If tsc still needs a shim, ensure it does not override generated declarations.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@experimental/vibe/ui/schema-viewer/src/svelte.d.ts` around lines 4 - 9,
Remove the wildcard *.svelte module declaration from svelte.d.ts so generated
declarations from svelte-check provide component types and prop validation for
EntityNodeComponent and the details components; only retain a non-overriding
fallback if tsc requires one.
experimental/vibe/ui/schema-viewer/src/components/details/FsmTopologyGraph.svelte (1)

72-76: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Log the layout error before you discard it.

The catch block sets failed and drops the error object. The user sees "FSM layout failed." with no cause. ELK layout failures are hard to reproduce, and the schema explorer is a debugging tool.

Keep the flag and record the error.

♻️ Proposed change
-      .catch(() => {
+      .catch((error: unknown) => {
         if (active) {
+          console.error('FSM topology layout failed', error);
           failed = true;
         }
       });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@experimental/vibe/ui/schema-viewer/src/components/details/FsmTopologyGraph.svelte`
around lines 72 - 76, Update the layout promise catch block in FsmTopologyGraph
to accept the caught error and log it before setting failed, preserving the
active guard and existing failure state behavior.
experimental/vibe/ui/schema-viewer/src/lib/xyflow.ts (1)

349-365: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the unreachable fallback branch.

paths is non-empty at line 349 because of the guard at line 335. Every entry in paths holds at least two points, so points.slice(1) yields at least one segment. segments is therefore never empty, and Array.prototype.reduce without an initial value never throws here. longestSegment is always a truthy object, so lines 362-365 never execute.

Drop the condition and the second return to remove the dead code.

♻️ Proposed simplification
   const longestSegment = segments.reduce((longest, segment) =>
     segment.length > longest.length ? segment : longest
   );
-  if (longestSegment) {
-    return {
-      path: paths.map((value) => value.path).join(' '),
-      label: {
-        x: (longestSegment.start.x + longestSegment.end.x) / 2,
-        y: (longestSegment.start.y + longestSegment.end.y) / 2,
-      },
-    };
-  }
-
   return {
     path: paths.map((value) => value.path).join(' '),
-    label: paths.at(-1)!.points.at(-1)!,
+    label: {
+      x: (longestSegment.start.x + longestSegment.end.x) / 2,
+      y: (longestSegment.start.y + longestSegment.end.y) / 2,
+    },
   };
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@experimental/vibe/ui/schema-viewer/src/lib/xyflow.ts` around lines 349 - 365,
In the path-label calculation around the segments reduce, remove the always-true
longestSegment condition and delete the fallback return using paths.at. Return
the computed path and midpoint label directly after determining longestSegment.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@experimental/vibe/ui/package.json`:
- Around line 15-17: Update the schema:ci script in package.json to run cargo
fmt --check and the required locked workspace Clippy command with all targets
and features and warnings denied, alongside the existing Rust tests. Keep the
existing JavaScript checks and build steps unchanged.

In `@experimental/vibe/ui/pnpm-workspace.yaml`:
- Line 1: Remove the no-review declaration comment from pnpm-workspace.yaml
after reviewing and validating the submitted changes; leave the workspace
configuration otherwise unchanged.

In
`@experimental/vibe/ui/schema-viewer/src/components/details/FsmTopologyGraph.svelte`:
- Around line 94-133: Add keyboard selection handling in FsmFlowNode.svelte for
focusable FSM nodes, activating on Enter and Space and invoking the existing
onSelect callback with the node’s FSM state. Wire the handler through the node
component’s available keyboard event path rather than relying on unsupported
SvelteFlow onnodekeydown.

In `@experimental/vibe/ui/schema-viewer/src/components/GraphView.svelte`:
- Around line 92-106: Update the layout $effect in GraphView.svelte to invoke
onLayoutStart through Svelte’s untrack, preventing callback identity changes
from retriggering the expensive layout. Keep the existing callback payload and
layout initialization behavior unchanged.

In `@experimental/vibe/ui/schema-viewer/src/components/RecordDetails.svelte`:
- Around line 35-43: Extract the duplicated selection event and empty-state UI
across all three details components. In
experimental/vibe/ui/schema-viewer/src/components/RecordDetails.svelte:35-43,
ResourceDetails.svelte:35-43, and FsmDetails.svelte:37-45, add and use the
shared emitSchemaSelection(host, detail) helper from lib/events.ts instead of
each local emitSelection, and replace each identical “No schema selected.” block
with a shared empty-state component.

In `@experimental/vibe/ui/schema-viewer/src/components/ResourceTimeline.svelte`:
- Around line 77-122: Extract the duplicated entity-cell markup into a Svelte 5
snippet accepting row and tree-mark glyph parameters, preserving the existing
namespace rendering, badge logic, class lists, and data-quent-role attributes.
Replace both entity-cell copies in the timeline with calls to the snippet,
passing the respective row and glyph values: ● for the first and ○ for the
second.

In `@experimental/vibe/ui/schema-viewer/src/lib/config.ts`:
- Around line 38-52: Update the configuration merge in the config resolver to
remove properties whose values are undefined before spreading config over
DEFAULT_ENTITY_GRAPH_CONFIG. Preserve explicitly provided enum and boolean
values while ensuring fields such as direction and edgeRouting always retain
their defaults when omitted or explicitly undefined.

In `@experimental/vibe/ui/schema-viewer/src/lib/viewRegistry.ts`:
- Around line 8-17: Replace the positional entries in ENTITY_GRAPH_VIEW_REGISTRY
with an id-keyed mapping from EntityGraphView identifiers to their components,
then derive the registry by iterating ENTITY_GRAPH_VIEWS so every declared view
is represented. Preserve the GraphView and ResourceTimeline bindings by matching
their ids rather than array indexes, and update consumers if necessary to
accommodate the resulting derived type instead of the current tuple-literal
type.

In `@experimental/vibe/ui/schema-viewer/vite.config.ts`:
- Around line 35-43: Remove the duplicated isSchemaViewerElement function from
experimental/vibe/ui/schema-viewer/vite.config.ts lines 35-43 and
experimental/vibe/ui/schema-viewer/vitest.config.ts lines 30-38, then import and
use the shared helper in both configurations so they retain the same
custom-element component contract.
- Around line 9-12: Replace URL.pathname with fileURLToPath for the cacheDir URL
in experimental/vibe/ui/schema-viewer/vite.config.ts (lines 9-12), adding the
node:url import. Apply the same conversion and import in
experimental/vibe/ui/schema-viewer/vitest.config.ts (lines 8-11) for the
schema-viewer-test cache directory.
- Around line 23-32: Update the library build configuration in vite.config.ts to
externalize both svelte and `@xyflow/svelte`, including all their subpath imports,
through the Rollup external configuration. Keep the existing library entry,
format, file naming, and CSS settings unchanged.

In `@experimental/vibe/ui/yaml-wasm/src/lib.rs`:
- Line 7: Replace the fixed NAMESPACE_SEPARATOR scheme used by the parsing and
rewriting logic with a per-parse marker guaranteed not to occur in source, or
limit encoding/decoding to structured namespace fields. Update the relevant
parse flow and line-56 replacement to use that marker consistently, and add a
regression test covering a scalar containing the current marker while preserving
the scalar unchanged.

---

Other comments:
In `@experimental/vibe/ui/schema-explorer/package.json`:
- Line 9: Update the package.json check script to pass the package-local Vite
configuration via ./vite.config.ts instead of ../vite.config.ts, while
preserving the existing workspace and TypeScript configuration arguments.

In `@experimental/vibe/ui/schema-explorer/scripts/build-yaml-wasm.sh`:
- Around line 7-18: Update the wasm-bindgen invocation in the build script to
use the project-managed CLI pinned to version 0.2.126, rather than resolving an
arbitrary executable from PATH. Preserve the existing arguments, output
directory, and generated name.

In `@experimental/vibe/ui/schema-explorer/scripts/verify-yaml-wasm.mjs`:
- Around line 1-3: Complete the contribution prerequisites for PR `#451` by
linking an approved Quent maintainer issue, adding a Signed-off-by trailer to
each of the four commits, and waiting for the pending CodeRabbit check to pass
before merging.

In `@experimental/vibe/ui/schema-explorer/src/yaml-schema.ts`:
- Around line 10-15: Update parseYamlSchema’s cached initialization flow so a
rejected initialize() promise clears initialization before the rejection
propagates. Preserve successful promise reuse and parsing behavior, while
allowing a later invocation to retry initialization.

In `@experimental/vibe/ui/schema-viewer/package.json`:
- Line 25: Update the package check script to remove the unsupported --config
argument from the svelte-check invocation while preserving --workspace ./src,
--tsconfig ../tsconfig.json, and the subsequent tsc --noEmit command.

In
`@experimental/vibe/ui/schema-viewer/src/components/details/RecordsSection.svelte`:
- Around line 42-55: Make selection state accessible in RecordsSection.svelte
and ResourcesSection.svelte by computing each item’s selectionMatches result
once as isSelected, adding aria-pressed={isSelected}, and reusing isSelected for
the selected CSS classes. Apply this to the record button in
RecordsSection.svelte#L42-L55, the resource-record button in
ResourcesSection.svelte#L128-L140, and the resource title button in
ResourcesSection.svelte#L62-L68.

In `@experimental/vibe/ui/schema-viewer/src/components/EntityGraph.svelte`:
- Around line 100-132: Update the EntityGraph view rendering to pass the active
state into ResourceTimeline and ensure both ResourceTimeline’s
buildResourceTimeline and GraphView’s layoutEntityGraph execute only when their
active prop is true. Remove or rename GraphView’s local active shadowing flag so
the component prop controls the computation and hidden views defer work.

In `@experimental/vibe/ui/schema-viewer/src/components/ResourceTimeline.svelte`:
- Around line 216-226: Update the capacity chart container in the
ResourceTimeline markup to add role="img" alongside its existing aria-label,
then mark each timeline capacity bin element identified by
data-quent-role="timeline-capacity-bin" as decorative with aria-hidden="true".

In `@experimental/vibe/ui/schema-viewer/src/styles.css`:
- Line 318: Update the border declaration to use the lowercase CSS keyword
currentcolor instead of currentColor, resolving the value-keyword-case stylelint
error while preserving the existing border styling.

In `@experimental/vibe/ui/schema-viewer/tests/components.test.ts`:
- Around line 221-243: Harden the assertions in the timeline FSM test by
capturing the trimmed `fsm.textContent` and `state.dataset.state` values,
asserting each is defined, then using those validated values in the `onSelect`
and `onHover`/`onSelect` payload expectations. Remove optional chaining from the
expected values so missing data cannot make `toMatchObject` pass.

In `@experimental/vibe/ui/schema-viewer/tests/xyflow.test.ts`:
- Around line 226-239: Strengthen the label assertions in the xyflow test by
asserting the relevant edge and reference collections have the expected
nonzero/matching lengths before using every. In the flow.edges validation, match
each edge to its corresponding layout reference through the adapter’s stable
identity rather than array indexes, and require labelX, labelY, and the
reference coordinates to be defined before comparing them. Apply the same
flow.edges length assertion to the checks covering lines 258–276.

---

Nitpick comments:
In @.github/workflows/schema-explorer-pages.yml:
- Around line 34-39: Move the “Set up Pages” step using configure-pages before
“Build”, then replace the hardcoded SCHEMA_EXPLORER_BASE value with the step’s
base_path output via its step id. Ensure the configure-pages step exposes a
unique id and the build consumes that output.

In `@experimental/vibe/ui/schema-explorer/src/SelectionBreadcrumbs.svelte`:
- Around line 38-91: Update buildBreadcrumbs so its switch over SchemaSelection
handles the unreachable fallthrough with a never-based exhaustiveness check.
Ensure every current selection kind still returns its breadcrumbs, while any
future kind causes a compile-time error rather than allowing the function to
return undefined.

In
`@experimental/vibe/ui/schema-viewer/src/components/details/DataTypeDisplay.svelte`:
- Around line 24-26: Create a shared classNames utility in
experimental/vibe/ui/schema-viewer/src/lib/classNames.ts using the existing
filter-and-join behavior, then remove the local classNames helper from
DataTypeDisplay.svelte at lines 24-26 and EntityEventsSection.svelte at lines
31-33 and import the shared function in both components.
- Around line 58-60: Update the DataTypePart discriminant used by dataTypeParts
so target and data reference labels receive distinct kind values instead of
sharing reference-label. In DataTypeDisplay, derive data-quent-role from
part.kind rather than comparing part.value, preserving the existing target and
data role mappings.

In
`@experimental/vibe/ui/schema-viewer/src/components/details/FsmTopologyGraph.svelte`:
- Around line 72-76: Update the layout promise catch block in FsmTopologyGraph
to accept the caught error and log it before setting failed, preserving the
active guard and existing failure state behavior.

In
`@experimental/vibe/ui/schema-viewer/src/components/details/RecordsSection.svelte`:
- Around line 24-26: Extract the duplicated classNames helper from
RecordsSection.svelte into a shared module under src/lib, then import and use
that shared function in RecordsSection.svelte, ResourcesSection.svelte, and
FsmTransitionAttributes.svelte. Remove the local definitions while preserving
the existing filtering and space-joining behavior.

In
`@experimental/vibe/ui/schema-viewer/src/components/details/ResourcesSection.svelte`:
- Around line 26-34: Update the records type in resourceGroups to use the
exported ResourceRecord type from ./lib/types instead of
ResourceDefinition['usages'], while preserving the existing usage and bounds
group behavior.

In `@experimental/vibe/ui/schema-viewer/src/components/FitViewOnLayout.svelte`:
- Around line 34-70: Update the effect in FitViewOnLayout to synchronously
capture the current fit options before tick, and include those options in the
already-fitted guard so option changes trigger a new fit even when version is
unchanged. Store the options associated with the last successful fit, read
fittedVersion via untrack, and use the available useViewportInitialized API
where applicable.

In `@experimental/vibe/ui/schema-viewer/src/components/GraphView.svelte`:
- Line 95: Rename the effect-local `active` cancellation flag and its references
around lines 150–152 to `cancelled` or `current`, while leaving the component
`active` prop and its `FitViewOnLayout` usage unchanged.

In `@experimental/vibe/ui/schema-viewer/src/lib/layout.ts`:
- Around line 510-526: Update layoutEngine to clear the cached engine promise
when dynamic import or ELK/worker construction fails, so subsequent
layoutEntityGraph and layoutFsmTopology calls retry initialization instead of
reusing a rejected promise.

In `@experimental/vibe/ui/schema-viewer/src/lib/schema.ts`:
- Around line 365-403: Narrow the roles contract in collectResourceConsumers and
CollectResourceConsumerInput from Map<string, unknown> to ReadonlySet<string>,
since consumers only require has(recordKey). Update the corresponding caller,
such as buildResources, to pass the record-key set while preserving all existing
membership checks.
- Around line 89-101: Update the schema node-building flow around the `nodes`
mapping to precompute reference counts in a map keyed by
`pathKey(reference.source)` before iterating entities. Replace the per-entity
`references.filter(...)` scan with a map lookup, defaulting to zero when no
references exist, while preserving the existing node fields and values.

In `@experimental/vibe/ui/schema-viewer/src/lib/types.ts`:
- Around line 69-90: Update ResolvedEntityGraphConfig to derive its keys from
EntityGraphConfig using Required<EntityGraphConfig>, preserving the existing
resolved non-nullable property behavior while ensuring newly added configuration
keys are checked and included automatically.

In `@experimental/vibe/ui/schema-viewer/src/lib/xyflow.ts`:
- Around line 349-365: In the path-label calculation around the segments reduce,
remove the always-true longestSegment condition and delete the fallback return
using paths.at. Return the computed path and midpoint label directly after
determining longestSegment.

In `@experimental/vibe/ui/schema-viewer/src/styles.css`:
- Around line 31-46: Wrap the `.quent-schema-name` and
`.quent-schema-name--title` selectors in `:where()`, and apply the same
zero-specificity wrapping to all `.quent-schema-details__fsm-flow-node-*` rules.
Preserve the existing source order so the `--selected`, `--entry`, and `--exit`
modifiers remain after and override the base flow-node rule.

In `@experimental/vibe/ui/schema-viewer/src/svelte.d.ts`:
- Around line 4-9: Remove the wildcard *.svelte module declaration from
svelte.d.ts so generated declarations from svelte-check provide component types
and prop validation for EntityNodeComponent and the details components; only
retain a non-overriding fallback if tsc requires one.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Enterprise

Run ID: 1c47c791-4265-44dc-bc19-70703d86e4f4

📥 Commits

Reviewing files that changed from the base of the PR and between 002b2a8 and 0f9d32d.

⛔ Files ignored due to path filters (3)
  • experimental/vibe/ui/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • experimental/vibe/ui/schema-explorer/wasm/quent_yaml_bg.wasm is excluded by !**/*.wasm
  • experimental/vibe/ui/yaml-wasm/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (84)
  • .github/workflows/schema-explorer-pages.yml
  • experimental/README.md
  • experimental/vibe/README.md
  • experimental/vibe/ui/.gitignore
  • experimental/vibe/ui/README.md
  • experimental/vibe/ui/eslint.config.js
  • experimental/vibe/ui/package.json
  • experimental/vibe/ui/pnpm-workspace.yaml
  • experimental/vibe/ui/schema-explorer/README.md
  • experimental/vibe/ui/schema-explorer/index.html
  • experimental/vibe/ui/schema-explorer/package.json
  • experimental/vibe/ui/schema-explorer/scripts/build-yaml-wasm.sh
  • experimental/vibe/ui/schema-explorer/scripts/verify-yaml-wasm.mjs
  • experimental/vibe/ui/schema-explorer/src/App.svelte
  • experimental/vibe/ui/schema-explorer/src/EntityNode.svelte
  • experimental/vibe/ui/schema-explorer/src/GraphConfigBar.svelte
  • experimental/vibe/ui/schema-explorer/src/SelectionBreadcrumbs.svelte
  • experimental/vibe/ui/schema-explorer/src/YamlEditor.svelte
  • experimental/vibe/ui/schema-explorer/src/main.ts
  • experimental/vibe/ui/schema-explorer/src/models/dynamo-inference.yaml
  • experimental/vibe/ui/schema-explorer/src/models/hello.yaml
  • experimental/vibe/ui/schema-explorer/src/models/simple.yaml
  • experimental/vibe/ui/schema-explorer/src/models/simulator.yaml
  • experimental/vibe/ui/schema-explorer/src/models/sirius.yaml
  • experimental/vibe/ui/schema-explorer/src/style.css
  • experimental/vibe/ui/schema-explorer/src/yaml-models.ts
  • experimental/vibe/ui/schema-explorer/src/yaml-schema.ts
  • experimental/vibe/ui/schema-explorer/svelte.config.js
  • experimental/vibe/ui/schema-explorer/tsconfig.json
  • experimental/vibe/ui/schema-explorer/vite.config.ts
  • experimental/vibe/ui/schema-explorer/wasm/quent_yaml.d.ts
  • experimental/vibe/ui/schema-explorer/wasm/quent_yaml.js
  • experimental/vibe/ui/schema-explorer/wasm/quent_yaml_bg.wasm.d.ts
  • experimental/vibe/ui/schema-viewer/README.md
  • experimental/vibe/ui/schema-viewer/package.json
  • experimental/vibe/ui/schema-viewer/src/components/ElkFlowEdge.svelte
  • experimental/vibe/ui/schema-viewer/src/components/EntityEvents.svelte
  • experimental/vibe/ui/schema-viewer/src/components/EntityFlowNode.svelte
  • experimental/vibe/ui/schema-viewer/src/components/EntityGraph.svelte
  • experimental/vibe/ui/schema-viewer/src/components/FitViewOnLayout.svelte
  • experimental/vibe/ui/schema-viewer/src/components/FsmDetails.svelte
  • experimental/vibe/ui/schema-viewer/src/components/FsmFlowNode.svelte
  • experimental/vibe/ui/schema-viewer/src/components/GraphView.svelte
  • experimental/vibe/ui/schema-viewer/src/components/GraphViewportController.svelte
  • experimental/vibe/ui/schema-viewer/src/components/NamespaceFlowNode.svelte
  • experimental/vibe/ui/schema-viewer/src/components/RecordDetails.svelte
  • experimental/vibe/ui/schema-viewer/src/components/ResourceDetails.svelte
  • experimental/vibe/ui/schema-viewer/src/components/ResourceTimeline.svelte
  • experimental/vibe/ui/schema-viewer/src/components/details/DataTypeDisplay.svelte
  • experimental/vibe/ui/schema-viewer/src/components/details/EntityEventsSection.svelte
  • experimental/vibe/ui/schema-viewer/src/components/details/FsmSection.svelte
  • experimental/vibe/ui/schema-viewer/src/components/details/FsmTopologyGraph.svelte
  • experimental/vibe/ui/schema-viewer/src/components/details/FsmTransitionAttributes.svelte
  • experimental/vibe/ui/schema-viewer/src/components/details/RecordsSection.svelte
  • experimental/vibe/ui/schema-viewer/src/components/details/ResourcesSection.svelte
  • experimental/vibe/ui/schema-viewer/src/index.ts
  • experimental/vibe/ui/schema-viewer/src/lib/config.ts
  • experimental/vibe/ui/schema-viewer/src/lib/constants.ts
  • experimental/vibe/ui/schema-viewer/src/lib/layout.ts
  • experimental/vibe/ui/schema-viewer/src/lib/resourceTimeline.ts
  • experimental/vibe/ui/schema-viewer/src/lib/schema.ts
  • experimental/vibe/ui/schema-viewer/src/lib/selection.ts
  • experimental/vibe/ui/schema-viewer/src/lib/types.ts
  • experimental/vibe/ui/schema-viewer/src/lib/viewRegistry.ts
  • experimental/vibe/ui/schema-viewer/src/lib/xyflow.ts
  • experimental/vibe/ui/schema-viewer/src/public.ts
  • experimental/vibe/ui/schema-viewer/src/register.ts
  • experimental/vibe/ui/schema-viewer/src/styles.css
  • experimental/vibe/ui/schema-viewer/src/svelte.d.ts
  • experimental/vibe/ui/schema-viewer/svelte.config.js
  • experimental/vibe/ui/schema-viewer/tests/components.test.ts
  • experimental/vibe/ui/schema-viewer/tests/fixtures/TestEntityNode.svelte
  • experimental/vibe/ui/schema-viewer/tests/fixtures/sample-schema.ts
  • experimental/vibe/ui/schema-viewer/tests/resourceTimeline.test.ts
  • experimental/vibe/ui/schema-viewer/tests/schema.test.ts
  • experimental/vibe/ui/schema-viewer/tests/setup.ts
  • experimental/vibe/ui/schema-viewer/tests/xyflow.test.ts
  • experimental/vibe/ui/schema-viewer/tsconfig.build.json
  • experimental/vibe/ui/schema-viewer/tsconfig.json
  • experimental/vibe/ui/schema-viewer/vite.config.ts
  • experimental/vibe/ui/schema-viewer/vitest.config.ts
  • experimental/vibe/ui/tsconfig.base.json
  • experimental/vibe/ui/yaml-wasm/Cargo.toml
  • experimental/vibe/ui/yaml-wasm/src/lib.rs

Comment on lines +15 to +17
"test": "cargo test --manifest-path yaml-wasm/Cargo.toml && pnpm --filter @quent/schema-viewer test && pnpm --filter @quent-experimental/schema-explorer wasm:test",
"build": "pnpm --filter @quent/schema-viewer build && pnpm --filter @quent-experimental/schema-explorer build",
"schema:ci": "pnpm lint && pnpm check && pnpm test && pnpm build",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Run the required Rust checks from schema:ci.

schema:ci runs cargo test only. It does not run cargo fmt --check or the required locked Clippy command with warnings denied. Add these checks to the CI path before this workspace can merge Rust changes.

As per coding guidelines, “Rust changes must be formatted with cargo fmt and linted with cargo clippy --workspace --all-targets --all-features --locked -- -D warnings; warnings are not permitted.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@experimental/vibe/ui/package.json` around lines 15 - 17, Update the schema:ci
script in package.json to run cargo fmt --check and the required locked
workspace Clippy command with all targets and features and warnings denied,
alongside the existing Rust tests. Keep the existing JavaScript checks and build
steps unchanged.

Source: Coding guidelines

@@ -0,0 +1,22 @@
# Generated entirely by coding agents from prompts without human review or refinement.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Remove the no-review declaration.

This declaration states that no human reviewed or refined the submitted changes. Review and validate the changes before submission, then remove this statement.

As per coding guidelines, “Contributors must understand and be able to explain every submitted change, including AI-assisted changes.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@experimental/vibe/ui/pnpm-workspace.yaml` at line 1, Remove the no-review
declaration comment from pnpm-workspace.yaml after reviewing and validating the
submitted changes; leave the workspace configuration otherwise unchanged.

Source: Coding guidelines

Comment on lines +94 to +133
function selectState(
{ node }: Parameters<NodeEventWithPointer<MouseEvent | TouchEvent, FsmFlowNode>>[0],
): void {
if (!node.data.entry && !node.data.exit) {
onSelect({
kind: 'fsm-state',
entity: entity.path,
state: node.data.state,
});
}
}
</script>

<div
class="quent-schema-details__fsm-graph"
data-quent-role="fsm-graph"
style:height={layout
? `${Math.min(Math.max(layout.height, 240), 480)}px`
: '12rem'}
>
{#if failed}
<p class="quent-schema-details__muted">FSM layout failed.</p>
{:else if !layout}
<p class="quent-schema-details__muted">Laying out FSM topology.</p>
{:else}
<SvelteFlow
bind:nodes
bind:edges
{nodeTypes}
{edgeTypes}
fitView
fitViewOptions={{ padding: 0.12, minZoom: 0.5, maxZoom: 1.5 }}
minZoom={0.5}
maxZoom={1.5}
{...READ_ONLY_FLOW_CONFIG}
onnodeclick={selectState}
aria-label={`FSM topology for ${pathKey(entity.path)}`}
colorMode="light"
proOptions={{ hideAttribution: true }}
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check the SvelteFlow node keyboard event surface and existing keyboard handling.
set -euo pipefail

echo "=== existing keyboard handlers in the viewer ==="
rg -n -C4 'onnodekeydown|onkeydown|KeyboardEvent' experimental/vibe/ui/schema-viewer/src || true

echo "=== SvelteFlow node event prop types ==="
fd -t f -p 'node_modules/@xyflow/svelte' -e d.ts 2>/dev/null | head -20
rg -n 'onnodekeydown|NodeEventWithPointer|NodeTargetEventWith' \
  --iglob '*.d.ts' node_modules/@xyflow 2>/dev/null | head -20 \
  || echo "package types not present in sandbox"

Repository: rapidsai/quent

Length of output: 279


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== FsmTopologyGraph.svelte ==="
sed -n '1,180p' experimental/vibe/ui/schema-viewer/src/components/details/FsmTopologyGraph.svelte

echo "=== xyflow adapter ==="
sed -n '1,240p' experimental/vibe/ui/schema-viewer/src/lib/xyflow.ts

echo "=== SvelteFlow dependency declarations ==="
rg -n -C3 '`@xyflow/svelte`|svelte-flow|xyflow' \
  experimental/vibe/ui/schema-viewer package.json pnpm-lock.yaml yarn.lock package-lock.json 2>/dev/null || true

echo "=== node component event and focus handling ==="
rg -n -C4 'focusable|tabindex|onkeydown|keydown|NodeEventWithPointer|nodeclick' \
  experimental/vibe/ui/schema-viewer/src

Repository: rapidsai/quent

Length of output: 42150


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== FSM adapter node construction ==="
sed -n '225,325p' experimental/vibe/ui/schema-viewer/src/lib/xyflow.ts

echo "=== FSM node component ==="
sed -n '1,180p' experimental/vibe/ui/schema-viewer/src/components/FsmFlowNode.svelte

echo "=== dependency versions and lockfile catalog ==="
sed -n '1,80p' experimental/vibe/ui/schema-viewer/package.json
rg -n -C3 '`@xyflow/svelte`|xyflow' pnpm-workspace.yaml pnpm-lock.yaml experimental/vibe/ui/schema-viewer/package.json 2>/dev/null | head -100

echo "=== tests covering FSM node accessibility or selection ==="
rg -n -C4 'fsm-state|fsm-flow-node|focusable|tabindex|keyboard|Enter|Space|nodeclick' \
  experimental/vibe/ui/schema-viewer/tests experimental/vibe/ui/schema-viewer/src

Repository: rapidsai/quent

Length of output: 6254


🌐 Web query:

@xyflow/svelte SvelteFlow onnodekeydown NodeEventWithPointer focusable node keyboard events

💡 Result:

In @xyflow/svelte, there is no public-facing onNodeKeyDown event prop exposed on the SvelteFlow component API [1][2][3]. Keyboard interaction handling is primarily managed internally by the library's NodeWrapper component [4]. Keyboard events for nodes are processed as follows: 1. Built-in Keyboard Support: The library internally handles specific keyboard interactions for focusable nodes, such as moving selected nodes with arrow keys and managing selection states with keys like 'Backspace' or 'Escape' [4]. These behaviors can be controlled via props like deleteKey and disableKeyboardA11y [1][4]. 2. Custom Keyboard Event Handling: If you need to implement custom keyboard logic for a node, the recommended approach is to handle the keydown event directly within your custom node component [5]. Since custom nodes are standard Svelte components, you can add an event listener to your node's container or any specific element inside it:

...
3. Focusable Nodes: Svelte Flow supports node focusability [6][7]. When nodesFocusable is set to true (default), nodes can receive focus, which enables the internal keyboard movement and selection features [4][6]. 4. Canvas-level Events: If you are looking to capture keyboard events on the SvelteFlow canvas itself (when no node is selected), you can set tabIndex={0} on the SvelteFlow container element [8]. This allows the container to receive focus and capture keydown events that aren't otherwise intercepted by nodes or internal components [8]. In summary, rather than using an onNodeKeyDown prop, you should handle custom keyboard interactions within the scope of your custom node components or, for global canvas events, ensure the SvelteFlow container is focusable via tabIndex [4][8].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== tracked lock/workspace manifests ==="
git ls-files '*lock*' '*workspace*.yaml' '*package.json' | head -80

echo "=== xyflow catalog/version declarations ==="
for f in $(git ls-files '*lock*' '*workspace*.yaml' '*package.json'); do
  if rg -q '`@xyflow/svelte`|xyflow' "$f"; then
    echo "--- $f"
    rg -n -C3 '`@xyflow/svelte`|xyflow' "$f"
  fi
done

echo "=== repository tests for FSM selection ==="
rg -n -C6 'FsmTopologyGraph|fsm-state|data-quent-role.*fsm-state|onSelect' \
  experimental/vibe/ui/schema-viewer/tests experimental/vibe/ui/schema-viewer/src/components/details \
  || true

Repository: rapidsai/quent

Length of output: 47974


🌐 Web query:

site:github.com/xyflow/xyflow packages/svelte NodeWrapper.svelte onkeydown Enter Space node focusable

💡 Result:

In @xyflow/svelte, the NodeWrapper component manages node focusability and accessibility through the focusable property [1][2]. When a node is marked as focusable, it is rendered with tabIndex={0} and given an appropriate ARIA role (defaulting to "group") to support keyboard navigation [1][2]. The component handles keyboard interactions within NodeWrapper.svelte via an onkeydown handler [1][2]. While the internal implementation focuses on selection and movement—such as using specific keys for node selection or arrow keys for moving nodes [1]—it does not explicitly implement native HTML button-like "Enter" or "Space" activation by default for custom node types unless specifically configured by the developer [1]. If you need to trigger actions on "Enter" or "Space" for a focusable node, you can utilize the domAttributes prop [3][2]. Since recent updates allow passing custom attributes to the node's DOM element via domAttributes [2], you can attach your own event handlers: <div onkeydown={(event) => { if (event.key === 'Enter' || event.key === ' ') { // Your custom activation logic } }} tabindex="0" role="button" >... Key technical details include: - Focusability: Controlled by the focusable boolean in the node definition [3][2]. - Accessibility: The NodeWrapper applies tabIndex={0} and roles to ensure screen readers and keyboard users can interact with nodes [1][2]. - Customization: Use domAttributes to add specific event listeners or additional ARIA attributes to the root node element [3][2]. If you are experiencing issues where keyboard interaction is completely missing, ensure your node is defined with focusable: true and check that disableKeyboardA11y is not set to true in your SvelteFlow configuration [1][4].

Citations:


Handle keyboard selection in FsmFlowNode.svelte.

@xyflow/svelte does not expose onnodekeydown. Focusable FSM nodes need a keydown handler that activates selection for Enter and Space, then connects that action to onSelect.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@experimental/vibe/ui/schema-viewer/src/components/details/FsmTopologyGraph.svelte`
around lines 94 - 133, Add keyboard selection handling in FsmFlowNode.svelte for
focusable FSM nodes, activating on Enter and Space and invoking the existing
onSelect callback with the node’s FSM state. Wire the handler through the node
component’s available keyboard event path rather than relying on unsupported
SvelteFlow onnodekeydown.

Comment on lines +92 to +106
$effect(() => {
const currentModel = model;
const currentConfig = config;
let active = true;
const referenceCount = currentModel.references.filter(
(reference) =>
reference.target &&
referenceMatchesFilter(reference, currentConfig.references),
).length;
const started = performance.now();
layoutStatus = 'loading';
onLayoutStart({
nodeCount: currentModel.nodes.length,
referenceCount,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Find every call site that passes onLayoutStart to GraphView.
set -euo pipefail

rg -nP -C6 'onLayoutStart' experimental/vibe/ui --glob '*.svelte' --glob '*.ts'

Repository: rapidsai/quent

Length of output: 5705


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- GraphView effect and props ---'
sed -n '1,180p' experimental/vibe/ui/schema-viewer/src/components/GraphView.svelte

printf '%s\n' '--- EntityGraph call site and surrounding script ---'
sed -n '1,180p' experimental/vibe/ui/schema-viewer/src/components/EntityGraph.svelte

printf '%s\n' '--- GraphView usages ---'
rg -n -P -C8 '<GraphView|GraphView' experimental/vibe/ui/schema-viewer --glob '*.svelte' --glob '*.ts'

printf '%s\n' '--- Svelte versions and configuration ---'
rg -n -i -C3 '"svelte"|"`@sveltejs`' experimental/vibe/ui --glob 'package.json' --glob 'pnpm-lock.yaml' --glob 'package-lock.json' --glob 'yarn.lock' --glob '*.config.*' --glob 'svelte.config.*'

Repository: rapidsai/quent

Length of output: 25975


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- Custom-element consumers and event handlers ---'
rg -n -P -C8 'quent-entity-graph|quent-layout-start|quent-layout-complete|quent-layout-error' . \
  --glob '*.svelte' --glob '*.ts' --glob '*.tsx' --glob '*.js' --glob '*.html' --glob '*.md'

printf '%s\n' '--- All assignments and reads of GraphView callback props ---'
rg -n -P -C4 'onLayout(Start|Complete|Error)|\$effect|untrack' experimental/vibe/ui/schema-viewer \
  --glob '*.svelte' --glob '*.ts'

printf '%s\n' '--- Relevant package metadata ---'
sed -n '1,90p' experimental/vibe/ui/schema-viewer/package.json
rg -n -i -C4 'svelte:' experimental/vibe/ui --glob 'package.json' --glob 'pnpm-workspace.yaml' --glob 'catalog*' --glob 'pnpm-lock.yaml'

Repository: rapidsai/quent

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

entity_graph = Path("experimental/vibe/ui/schema-viewer/src/components/EntityGraph.svelte").read_text()
graph_view = Path("experimental/vibe/ui/schema-viewer/src/components/GraphView.svelte").read_text()
app = Path("experimental/vibe/ui/schema-explorer/src/App.svelte").read_text()

assert re.search(
    r"onLayoutStart=\{\(detail: EntityGraphLayoutStart\) =>\s*"
    r"emit<EntityGraphLayoutStart>\('quent-layout-start', detail\)\}",
    entity_graph,
)
assert re.search(
    r"\$effect\(\(\) => \{.*?onLayoutStart\(\{",
    graph_view,
    re.S,
)
assert "untrack" not in graph_view

for name in ("handleSelection", "handleHover", "handleViewChange"):
    match = re.search(
        rf"function {name}\b.*?(?=\n\s*function |\n\s*let |\n</script>)",
        app,
        re.S,
    )
    if match:
        print(f"--- {name} ---")
        print(match.group(0).strip())

print("--- source invariant ---")
print("EntityGraph passes an inline onLayoutStart callback: yes")
print("GraphView reads onLayoutStart in the synchronous layout effect: yes")
print("GraphView currently uses untrack: no")
PY

node - <<'JS'
const first = (detail) => detail;
const second = (detail) => detail;
console.log('inline callback identity changes across parent updates:', first !== second);
JS

Repository: rapidsai/quent

Length of output: 807


Read onLayoutStart through untrack. EntityGraph.svelte creates a new inline callback on each update. Because the layout effect reads onLayoutStart synchronously, unrelated updates can restart the expensive ELK layout. Wrap the callback invocation in untrack.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@experimental/vibe/ui/schema-viewer/src/components/GraphView.svelte` around
lines 92 - 106, Update the layout $effect in GraphView.svelte to invoke
onLayoutStart through Svelte’s untrack, preventing callback identity changes
from retriggering the expensive layout. Keep the existing callback payload and
layout initialization behavior unchanged.

Comment on lines +35 to +43
function emitSelection(detail: SchemaSelection): void {
$host().dispatchEvent(
new CustomEvent<SchemaSelection>('quent-select', {
detail,
bubbles: true,
composed: true,
}),
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Extract the duplicated quent-select dispatch and empty-state markup. The three details custom elements repeat an identical emitSelection helper and an identical "No schema selected." block. The shared root cause is a missing helper module and a missing shared empty-state component. A change to the event contract or the empty-state markup must currently be applied in three places.

  • experimental/vibe/ui/schema-viewer/src/components/RecordDetails.svelte#L35-L43: replace the local emitSelection with a shared emitSchemaSelection(host, detail) helper, and replace the empty-state block at Lines 47-50 with the shared component.
  • experimental/vibe/ui/schema-viewer/src/components/ResourceDetails.svelte#L35-L43: use the same shared helper and the same shared empty-state component.
  • experimental/vibe/ui/schema-viewer/src/components/FsmDetails.svelte#L37-L45: use the same shared helper and the same shared empty-state component.
♻️ Proposed shared helper

Add experimental/vibe/ui/schema-viewer/src/lib/events.ts:

// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import type { SchemaSelection } from './types';

export function emitSchemaSelection(
  host: HTMLElement,
  detail: SchemaSelection,
): void {
  host.dispatchEvent(
    new CustomEvent<SchemaSelection>('quent-select', {
      detail,
      bubbles: true,
      composed: true,
    }),
  );
}

Then in each component:

+  import { emitSchemaSelection } from '../lib/events';
+
-  function emitSelection(detail: SchemaSelection): void {
-    $host().dispatchEvent(
-      new CustomEvent<SchemaSelection>('quent-select', {
-        detail,
-        bubbles: true,
-        composed: true,
-      }),
-    );
-  }
+  const emitSelection = (detail: SchemaSelection): void =>
+    emitSchemaSelection($host(), detail);
📍 Affects 3 files
  • experimental/vibe/ui/schema-viewer/src/components/RecordDetails.svelte#L35-L43 (this comment)
  • experimental/vibe/ui/schema-viewer/src/components/ResourceDetails.svelte#L35-L43
  • experimental/vibe/ui/schema-viewer/src/components/FsmDetails.svelte#L37-L45
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@experimental/vibe/ui/schema-viewer/src/components/RecordDetails.svelte`
around lines 35 - 43, Extract the duplicated selection event and empty-state UI
across all three details components. In
experimental/vibe/ui/schema-viewer/src/components/RecordDetails.svelte:35-43,
ResourceDetails.svelte:35-43, and FsmDetails.svelte:37-45, add and use the
shared emitSchemaSelection(host, detail) helper from lib/events.ts instead of
each local emitSelection, and replace each identical “No schema selected.” block
with a shared empty-state component.

Comment on lines +8 to +17
export const ENTITY_GRAPH_VIEW_REGISTRY = [
{
...ENTITY_GRAPH_VIEWS[0],
component: GraphView,
},
{
...ENTITY_GRAPH_VIEWS[1],
component: ResourceTimeline,
},
] as const;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Key the registry by view id, not by array position.

The registry binds components to ENTITY_GRAPH_VIEWS[0] and ENTITY_GRAPH_VIEWS[1]. This creates two silent failure modes against the ENTITY_GRAPH_VIEWS contract in src/lib/types.ts lines 107-110:

  • If the array order changes, GraphView renders for resource-timeline and ResourceTimeline renders for graph. The code still compiles.
  • If a third view is appended, the registry omits it. The code still compiles.

Map the components by EntityGraphView id and derive the registry from the array. The compiler then requires an entry for every view id.

♻️ Proposed refactor
 import GraphView from '../components/GraphView.svelte';
 import ResourceTimeline from '../components/ResourceTimeline.svelte';
-import { ENTITY_GRAPH_VIEWS } from './types';
+import { ENTITY_GRAPH_VIEWS } from './types';
+import type { EntityGraphView } from './types';
 
-export const ENTITY_GRAPH_VIEW_REGISTRY = [
-  {
-    ...ENTITY_GRAPH_VIEWS[0],
-    component: GraphView,
-  },
-  {
-    ...ENTITY_GRAPH_VIEWS[1],
-    component: ResourceTimeline,
-  },
-] as const;
+const ENTITY_GRAPH_VIEW_COMPONENTS: Record<
+  EntityGraphView,
+  typeof GraphView | typeof ResourceTimeline
+> = {
+  graph: GraphView,
+  'resource-timeline': ResourceTimeline,
+};
+
+export const ENTITY_GRAPH_VIEW_REGISTRY = ENTITY_GRAPH_VIEWS.map((view) => ({
+  ...view,
+  component: ENTITY_GRAPH_VIEW_COMPONENTS[view.id],
+}));

Note: consumers that rely on the tuple literal type of the current as const export need a check after this change.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const ENTITY_GRAPH_VIEW_REGISTRY = [
{
...ENTITY_GRAPH_VIEWS[0],
component: GraphView,
},
{
...ENTITY_GRAPH_VIEWS[1],
component: ResourceTimeline,
},
] as const;
import GraphView from '../components/GraphView.svelte';
import ResourceTimeline from '../components/ResourceTimeline.svelte';
import { ENTITY_GRAPH_VIEWS } from './types';
import type { EntityGraphView } from './types';
const ENTITY_GRAPH_VIEW_COMPONENTS: Record<
EntityGraphView,
typeof GraphView | typeof ResourceTimeline
> = {
graph: GraphView,
'resource-timeline': ResourceTimeline,
};
export const ENTITY_GRAPH_VIEW_REGISTRY = ENTITY_GRAPH_VIEWS.map((view) => ({
...view,
component: ENTITY_GRAPH_VIEW_COMPONENTS[view.id],
}));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@experimental/vibe/ui/schema-viewer/src/lib/viewRegistry.ts` around lines 8 -
17, Replace the positional entries in ENTITY_GRAPH_VIEW_REGISTRY with an
id-keyed mapping from EntityGraphView identifiers to their components, then
derive the registry by iterating ENTITY_GRAPH_VIEWS so every declared view is
represented. Preserve the GraphView and ResourceTimeline bindings by matching
their ids rather than array indexes, and update consumers if necessary to
accommodate the resulting derived type instead of the current tuple-literal
type.

Comment on lines +9 to +12
cacheDir: new URL(
'../node_modules/.vite/schema-viewer',
import.meta.url,
).pathname,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

URL.pathname is used as a filesystem path in both Vite configuration files. URL.pathname keeps percent-encoding and returns a leading-slash drive path on Windows, so the resolved cacheDir is invalid on Windows and wrong for any path that contains a space. Use fileURLToPath from node:url in both files.

  • experimental/vibe/ui/schema-viewer/vite.config.ts#L9-L12: wrap the new URL('../node_modules/.vite/schema-viewer', import.meta.url) expression in fileURLToPath and add the node:url import.
  • experimental/vibe/ui/schema-viewer/vitest.config.ts#L8-L11: wrap the new URL('../node_modules/.vite/schema-viewer-test', import.meta.url) expression in fileURLToPath and add the node:url import.
📍 Affects 2 files
  • experimental/vibe/ui/schema-viewer/vite.config.ts#L9-L12 (this comment)
  • experimental/vibe/ui/schema-viewer/vitest.config.ts#L8-L11
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@experimental/vibe/ui/schema-viewer/vite.config.ts` around lines 9 - 12,
Replace URL.pathname with fileURLToPath for the cacheDir URL in
experimental/vibe/ui/schema-viewer/vite.config.ts (lines 9-12), adding the
node:url import. Apply the same conversion and import in
experimental/vibe/ui/schema-viewer/vitest.config.ts (lines 8-11) for the
schema-viewer-test cache directory.

Comment on lines +23 to +32
build: {
lib: {
entry: {
index: 'src/index.ts',
},
formats: ['es'],
fileName: (_format, entryName) => `${entryName}.js`,
cssFileName: 'schema-viewer',
},
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect the schema-viewer manifest for peerDependencies and runtime deps.
set -euo pipefail

fd -t f 'package.json' experimental/vibe/ui --exec sh -c 'echo "=== $1 ==="; cat "$1"' _ {}

Repository: rapidsai/quent

Length of output: 3859


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import json
from pathlib import Path

manifest = Path("experimental/vibe/ui/schema-viewer/package.json")
config = Path("experimental/vibe/ui/schema-viewer/vite.config.ts")

pkg = json.loads(manifest.read_text())
print("manifest dependencies:")
for name in ("svelte", "`@xyflow/svelte`"):
    locations = [
        section for section in ("dependencies", "optionalDependencies", "peerDependencies", "devDependencies")
        if name in pkg.get(section, {})
    ]
    print(f"  {name}: {locations}")

text = config.read_text()
print("rollup external configuration:")
for line_no, line in enumerate(text.splitlines(), 1):
    if "external" in line or "rollupOptions" in line:
        print(f"  {line_no}: {line}")
PY

echo "=== schema-viewer config ==="
cat -n experimental/vibe/ui/schema-viewer/vite.config.ts

echo "=== `@xyflow/svelte` references ==="
rg -n --glob '*.{ts,svelte,json}' '`@xyflow/svelte`|svelte' experimental/vibe/ui/schema-viewer/src experimental/vibe/ui/schema-viewer/package.json

Repository: rapidsai/quent

Length of output: 7837


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== lockfile entries for `@xyflow/svelte` ==="
rg -n -A18 -B3 '(`@xyflow/svelte`|`@xyflow`%2fsvelte)' pnpm-lock.yaml experimental/vibe/ui 2>/dev/null || true

echo "=== dependency declarations across workspace manifests ==="
rg -n -A4 -B2 '"`@xyflow/svelte`"|"svelte"' --glob 'package.json' --glob 'pnpm-lock.yaml' .

echo "=== source imports from `@xyflow/svelte` ==="
rg -l --glob '*.{ts,svelte}' "from '`@xyflow/svelte`'|from \"`@xyflow/svelte`\"|`@xyflow/svelte/dist/style.css`" experimental/vibe/ui/schema-viewer/src

Repository: rapidsai/quent

Length of output: 34638


🌐 Web query:

Vite library mode automatic externalization peerDependencies build.rollupOptions.external @xyflow/svelte peerDependencies svelte

💡 Result:

Vite does not currently provide a native, automatic feature to externalize peerDependencies in library mode [1][2]. By design, Vite bundles dependencies by default to ensure the library is self-contained [3][4]. To prevent your library from bundling its peer dependencies (such as svelte when working with @xyflow/svelte), you must explicitly configure them in your vite.config.ts [3][5]. To avoid bundling peer dependencies like svelte, update your build configuration to include them in rollupOptions.external [6][5]. Common Implementation Pattern A common and recommended approach is to programmatically extract dependencies from your package.json, which ensures that your configuration stays in sync with your manifest [6][5]: import { defineConfig } from 'vite'; import pkg from './package.json'; export default defineConfig({ build: { lib: { entry: 'src/index.ts', formats: ['es', 'cjs'], }, rollupOptions: { // Externalize all packages listed in peerDependencies external: [...Object.keys(pkg.peerDependencies || {}), ], }, }, }); Important Considerations 1. Sub-path Matching: Simply listing a package name (e.g., 'svelte') in external will not automatically externalize its sub-paths (e.g., 'svelte/internal'). If your library imports sub-paths, you should use a regular expression or a function in the external array to match them [6]: external: [/^svelte($|/)/] 2. Why Manual Configuration is Required: While there have been community proposals to automate this (e.g., inferring from package.json) [1][2], Vite maintainers have historically prioritized keeping core library mode configurations explicit to support diverse use cases and avoid unexpected breaking changes in how dependencies are resolved [1][2]. 3. Context for @xyflow/svelte: When building libraries that depend on framework-specific packages like @xyflow/svelte or svelte, keeping them external is critical [6][5]. If they are bundled, consumers may encounter errors due to multiple versions of the framework being initialized or conflicts with the framework's internal runtime [6][7][4]. Always ensure that any package listed as a peerDependency in your package.json is also accounted for in your build.rollupOptions.external config [6].

Citations:


Externalize svelte and @xyflow/svelte in the library build. svelte is a peer dependency, but Vite does not add peer dependencies to Rollup externalization automatically. @xyflow/svelte is a regular dependency. Add subpath-aware external rules for both packages to prevent duplicate Svelte runtimes in consumers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@experimental/vibe/ui/schema-viewer/vite.config.ts` around lines 23 - 32,
Update the library build configuration in vite.config.ts to externalize both
svelte and `@xyflow/svelte`, including all their subpath imports, through the
Rollup external configuration. Keep the existing library entry, format, file
naming, and CSS settings unchanged.

Comment on lines +35 to +43
function isSchemaViewerElement(filename: string): boolean {
return [
'/EntityGraph.svelte',
'/EntityEvents.svelte',
'/FsmDetails.svelte',
'/RecordDetails.svelte',
'/ResourceDetails.svelte',
].some((suffix) => filename.endsWith(suffix));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

isSchemaViewerElement is duplicated across the build and test configuration. Both copies declare the same contract: which Svelte components compile as custom elements. If the two lists diverge, the tests exercise a different compilation mode than the shipped bundle, and no error is reported.

  • experimental/vibe/ui/schema-viewer/vite.config.ts#L35-L43: remove the local function and import the shared helper.
  • experimental/vibe/ui/schema-viewer/vitest.config.ts#L30-L38: remove the local function and import the same shared helper.
📍 Affects 2 files
  • experimental/vibe/ui/schema-viewer/vite.config.ts#L35-L43 (this comment)
  • experimental/vibe/ui/schema-viewer/vitest.config.ts#L30-L38
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@experimental/vibe/ui/schema-viewer/vite.config.ts` around lines 35 - 43,
Remove the duplicated isSchemaViewerElement function from
experimental/vibe/ui/schema-viewer/vite.config.ts lines 35-43 and
experimental/vibe/ui/schema-viewer/vitest.config.ts lines 30-38, then import and
use the shared helper in both configurations so they retain the same
custom-element component contract.

use serde_json::Value;
use wasm_bindgen::prelude::*;

const NAMESPACE_SEPARATOR: &str = "QuentNamespaceSeparator";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Prevent collisions with the namespace marker.

QuentNamespaceSeparator is valid YAML scalar content. If an annotation or other scalar contains this literal, line 56 rewrites it to :: even though line 20 did not encode it. The parser then returns altered schema data.

Generate a marker that is absent from source for each parse, or encode and decode only structured namespace fields. Add a regression test for a scalar that contains the current marker.

Proposed direction
-fn parse_schema_value(source: &str) -> Result<Value, String> {
-    let encoded = source.replace("::", NAMESPACE_SEPARATOR);
+fn parse_schema_value(source: &str) -> Result<Value, String> {
+    let mut marker = NAMESPACE_SEPARATOR.to_owned();
+    while source.contains(&marker) {
+        marker.push('_');
+    }
+    let encoded = source.replace("::", &marker);
     let parsed = quent_yaml::parse_from_str(encoded, Some("editor.yaml"))
         .map_err(|error| error.to_string())?;
     let mut schema = serde_json::to_value(parsed.schema).map_err(|error| error.to_string())?;
-    restore_namespaces(&mut schema);
+    restore_namespaces(&mut schema, &marker);

Also applies to: 20-20, 55-56

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@experimental/vibe/ui/yaml-wasm/src/lib.rs` at line 7, Replace the fixed
NAMESPACE_SEPARATOR scheme used by the parsing and rewriting logic with a
per-parse marker guaranteed not to occur in source, or limit encoding/decoding
to structured namespace fields. Update the relevant parse flow and line-56
replacement to use that marker consistently, and add a regression test covering
a scalar containing the current marker while preserving the scalar unchanged.

rapids-bot Bot pushed a commit that referenced this pull request Aug 7, 2026
# Description

Externalize Svelte from the schema-viewer bundle so the viewer and custom node components share one runtime in production. This fixes the null `nodes` crash on GitHub Pages.

Also suppress the unnecessary `/favicon.ico` request.

## Related Issues

Follow-up to #451.

## Testing

- Full experimental schema CI
- Pages-equivalent headless browser test: HTTP 200, graph rendered, no console or page errors
- License and whitespace checks

_Written by Codex._

Authors:
  - Johan Peltenburg (https://github.com/johanpel)

Approvers:
  - Pradeep Garigipati (https://github.com/9prady9)

URL: #546
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants