Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions v2/lib/src/components/Input/core/_Input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,11 @@ export class AgInput extends FaceMixin(LitElement) implements InputProps {
if (this.onChange) {
this.onChange(e);
}

// Native `change` is composed:false so it stops at the shadow root.
// Re-dispatch a composed change from the host so Vue @change listeners
// and other external DOM listeners receive it.
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
}

/**
Expand Down
3 changes: 3 additions & 0 deletions v2/lib/src/components/Input/vue/VueInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@
:type="type"
:placeholder="placeholder"
:label="label"
:labelPosition="labelPosition"
:ariaLabel="ariaLabel"
:helpText="helpText"
:errorMessage="errorMessage"
:size="size"
:rows="rows"
:cols="cols"
:min="min"
:max="max"
@click="handleClick"
@input="handleInput"
@change="handleChange"
Expand Down
155 changes: 155 additions & 0 deletions v2/sdui/DEV-WORKFLOW.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# SDUI Demo Development Workflow

This guide explains the local dependency chain, what to rebuild after upstream changes, and
how to avoid stale-cache pitfalls.

## Dependency chain

```
v2/lib (agnosticui-core — web components, Vue/React wrappers)
└── v2/sdui/schema (@agnosticui/schema — AgNode type definitions)
└── v2/sdui/renderers/react (@agnosticui/render-react)
└── v2/sdui/renderers/vue (@agnosticui/render-vue)
└── v2/sdui/renderers/lit (@agnosticui/render-lit)
└── v2/sdui/demo (React demo app)
└── v2/sdui/demo-vue (Vue demo app)
└── v2/sdui/demo-lit (Lit demo app)
```

Each arrow is a `file:` dependency in `package.json`. npm installs these as **symlinks** in
`node_modules`, so demos always resolve to the local source. However, all packages under
`v2/lib` and `v2/sdui/renderers/*` are **TypeScript projects that must be compiled to `dist/`
before their consumers can see changes.**

## When to rebuild what

| You changed... | Rebuild command | Then... |
|---|---|---|
| `v2/lib/src/**` (core components, Vue/React wrappers) | `cd v2/lib && npm run build` | Restart the affected demo dev server |
| `v2/sdui/schema/src/**` | `cd v2/sdui/schema && npm run build` | Rebuild any affected renderer (see below) |
| `v2/sdui/renderers/vue/src/**` | `cd v2/sdui/renderers/vue && npm run build` | Restart demo-vue dev server |
| `v2/sdui/renderers/lit/src/**` | `cd v2/sdui/renderers/lit && npm run build` | Restart demo-lit dev server |
| `v2/sdui/renderers/react/src/**` | `cd v2/sdui/renderers/react && npm run build` | Restart demo (React) dev server |
| Fixture files in `v2/sdui/demo/src/fixtures/**` | None — imported as raw TypeScript by all three demos | Just save; Vite HMR picks it up |

### Rebuilding everything from scratch

```bash
# From the repo root (v2/)
cd lib && npm run build && cd ..
cd sdui/schema && npm run build && cd ../..
cd sdui/renderers/react && npm run build && cd ../../..
cd sdui/renderers/vue && npm run build && cd ../../..
cd sdui/renderers/lit && npm run build && cd ../../..
```

## First-time setup for a demo app

If a demo's `node_modules` folder does not exist yet (e.g. after a fresh clone or after
deleting it), run `npm install` inside the demo directory. npm resolves all `file:` deps as
symlinks automatically.

```bash
# Example for demo-lit (which ships without node_modules committed)
cd v2/sdui/demo-lit && npm install && npm run dev
```

## The Vite pre-bundle cache (`.vite/deps`)

Vite pre-bundles certain dependencies into `.vite/deps/` for faster startup. The demos use
`optimizeDeps.noDiscovery: true` plus an explicit `include` list, so **`agnosticui-core` and
the SDUI renderers are NOT pre-bundled** — they are served directly from the symlinked `dist/`
folders on disk.

The only package currently pre-bundled in demo-vue is `vue` itself. You should not need to
clear the Vite cache after a normal upstream rebuild.

**However**, if you ever see a demo serving unmistakably stale code after a rebuild and dev
server restart, clear the cache:

```bash
# Stop the dev server first, then:
rm -rf v2/sdui/demo-vue/node_modules/.vite
rm -rf v2/sdui/demo-lit/node_modules/.vite
rm -rf v2/sdui/demo/node_modules/.vite
# Then restart: npm run dev
```

## Convenience scripts (add to each demo's `package.json`)

You can add the following scripts to each demo app to make the rebuild loop faster. These are
intentionally not committed by default because they encode absolute paths; adjust for your
machine.

```jsonc
// v2/sdui/demo-vue/package.json (example)
"scripts": {
"dev": "vite",
"build": "vue-tsc -b && vite build",
"preview": "vite preview",
"rebuild:lib": "npm run build --prefix ../../lib",
"rebuild:renderer": "npm run build --prefix ../renderers/vue",
"rebuild:all": "npm run rebuild:lib && npm run rebuild:renderer",
"fresh": "npm run rebuild:all && rm -rf node_modules/.vite && npm run dev"
}
```

Run `npm run fresh` after any upstream change: it rebuilds both lib and the renderer, clears
the Vite pre-bundle cache, then starts the dev server.

## Common pitfalls

### Symptom: a prop (e.g. `min`, `max`) is passed in the fixture but has no effect in the UI

**Cause:** The renderer dist is stale — the prop is in the renderer's TypeScript source but
was added after the last `npm run build`.

**Fix:** Rebuild the affected renderer.

```bash
cd v2/sdui/renderers/vue && npm run build
# or
cd v2/sdui/renderers/lit && npm run build
```

### Symptom: an event (e.g. `@change`) fires in React but not in Vue or Lit

**Cause:** Native DOM events like `change` are `composed: false` by default, so they stop at
the shadow root of a LitElement and never reach outside listeners. Only `input` and `click`
are `composed: true` natively.

**Fix:** In the web component's event handler, re-dispatch a composed `CustomEvent` (or a
plain `Event` with `composed: true`) from the host element so external listeners receive it.
All AgnosticUI form components should do this; if one is missing it, add it to the
component's `_handle*` method in `v2/lib/src/`.

### Symptom: Vue `@change` on `ag-input` never fires

Same root cause as above. The fix is in `v2/lib/src/components/Input/core/_Input.ts`:
`_handleChange` must dispatch `new Event('change', { bubbles: true, composed: true })` from
`this` (the host element) after handling the native event.

### Symptom: a Vue wrapper prop (e.g. `min`, `labelPosition`) is declared but silently ignored

**Cause:** Vue 3 `defineProps` removes declared props from `$attrs`. If a prop is declared in
`withDefaults(defineProps<...>(), {...})` but is NOT explicitly bound in the template, Vue
will not forward it to the underlying web component.

**Fix:** Add the binding to the wrapper template. Example in `VueInput.vue`:

```html
:min="min"
:max="max"
:labelPosition="labelPosition"
```

React is not affected (all props are forwarded automatically via `@lit/react` `createComponent`).
Lit is not affected (direct property binding in the renderer template).

## Summary checklist after an upstream change

- [ ] `cd v2/lib && npm run build` if any core component or Vue/React wrapper changed
- [ ] `cd v2/sdui/renderers/[vue|lit|react] && npm run build` if the corresponding renderer changed
- [ ] `cd v2/sdui/schema && npm run build` if the schema types changed
- [ ] Restart the demo dev server
- [ ] If something still looks stale: `rm -rf node_modules/.vite` in the demo, then restart
6 changes: 3 additions & 3 deletions v2/sdui/demo-lit/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion v2/sdui/demo-lit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview"
"preview": "vite preview",
"rebuild:lib": "npm run build --prefix ../../lib",
"rebuild:renderer": "npm run build --prefix ../renderers/lit",
"rebuild:all": "npm run rebuild:lib && npm run rebuild:renderer",
"fresh": "npm run rebuild:all && rm -rf node_modules/.vite && npm run dev"
},
"dependencies": {
"@agnosticui/render-lit": "file:../renderers/lit",
Expand Down
38 changes: 32 additions & 6 deletions v2/sdui/demo-lit/src/AgSduiDemo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@ import { state } from 'lit/decorators.js';
import 'agnosticui-core/header';
import './components/WorkflowPicker';
import './components/StreamingOutput';
import './components/AdaptiveOutput';
import './SkinSwitcher';

const ADAPTIVE_WORKFLOW = 'adaptive-questionnaire';

export class AgSduiDemo extends LitElement {
static styles = css`
:host {
display: block;
}

.demo-layout {
max-width: 800px;
max-width: 900px;
margin: 0 auto;
margin-block-start: var(--ag-space-8, 2rem);
padding: 0 1.5rem 2rem;
Expand Down Expand Up @@ -55,11 +58,32 @@ export class AgSduiDemo extends LitElement {
color: var(--ag-text-muted, #666);
}

.demo-picker-section label {
display: block;
font-size: 0.85rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--ag-text-muted, #666);
margin-bottom: 0.75rem;
}

.demo-output-body {
display: flex;
flex-direction: column;
gap: var(--ag-space-4, 1rem);
min-height: 120px;
}

`;

@state() private workflow = 'contact-form';
@state() private seed = 0;

private get isAdaptive() {
return this.workflow === ADAPTIVE_WORKFLOW;
}

private handleSelect(e: Event) {
this.workflow = (e as CustomEvent<string>).detail;
this.seed = 0;
Expand Down Expand Up @@ -90,14 +114,16 @@ export class AgSduiDemo extends LitElement {
<div class="demo-output-header">
<span class="demo-output-label">Generated output</span>
<ag-button shape="rounded" @click=${this.handleRegenerate}>
Regenerate
${this.isAdaptive ? 'Restart' : 'Regenerate'}
</ag-button>
</div>
<div class="demo-output-body">
<ag-streaming-output
.workflow=${this.workflow}
.seed=${this.seed}
></ag-streaming-output>
${this.isAdaptive
? html`<ag-adaptive-output></ag-adaptive-output>`
: html`<ag-streaming-output
.workflow=${this.workflow}
.seed=${this.seed}
></ag-streaming-output>`}
</div>
</section>
</div>
Expand Down
Loading
Loading