Skip to content

Commit d3ad826

Browse files
fix(examples): fix missing group buttons in contact form and require explicit CLI args
- Add ensureGroupButtons() to cb-contact-form that reads groups from the sidebar when the form is dynamically created (not SSR'd), fixing the empty group selector in Add/Edit Contact views - Remove hello-world fallback defaults from electron and node integrations; both now require explicit dist-dir and state path args, printing usage on missing args - Remove electron dependency from contact-book-manager; Electron is launched from integration/electron by passing app dist/state paths - Update READMEs to reflect required args and removed electron section Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 27686a2 commit d3ad826

8 files changed

Lines changed: 76 additions & 38 deletions

File tree

examples/app/contact-book-manager/README.md

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,6 @@ contact-book-manager/
136136
│ ├── cb-contact-list/ # Scrollable contact list
137137
│ ├── cb-header/ # Sticky top bar
138138
│ └── cb-sidebar/ # Left navigation panel
139-
└── electron/ # Optional Electron wrapper
140-
├── main.ts
141-
└── preload.ts
142139
```
143140

144141
## Component Catalog
@@ -193,15 +190,3 @@ Components with multiple action buttons (e.g., `<cb-contact-detail>`) use a sing
193190

194191
- **Desktop (≥768px):** Fixed sidebar (260px) + scrollable main content area
195192
- **Mobile (<768px):** Sidebar hidden, single-column layout, "Add Contact" button label hidden (icon only)
196-
197-
## Electron (Optional)
198-
199-
The `src/electron/` directory contains an optional desktop wrapper. It uses the `webui-node` native addon to render `protocol.bin` + `state.json` into HTML, then serves it via a custom `webui://` protocol scheme inside an Electron `BrowserWindow`.
200-
201-
To use it, build the native addon first:
202-
203-
```bash
204-
cargo build -p webui-node --release
205-
```
206-
207-
The Electron entry point is not part of the standard web build — it is an alternative deployment target that reuses the same SSR binary and client JS bundle.

examples/app/contact-book-manager/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"start:client": "esbuild src/index.ts --bundle --outfile=dist/index.js --format=esm --sourcemap --watch",
1111
"start:server": "cargo run -p webui-cli -- start ./src --state ./data/state.json --plugin=fast --servedir ./dist --port 3001",
1212
"start": "cargo xtask dev contact-book-manager"
13-
},
13+
},
1414
"devDependencies": {
1515
"@microsoft/fast-element": "catalog:",
1616
"@microsoft/fast-html": "catalog:",

examples/app/contact-book-manager/src/organisms/cb-contact-form/cb-contact-form.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ export class CbContactForm extends RenderableFASTElement(FASTElement) {
2525
this.addEventListener('click', (e: Event) => {
2626
this.onClick(e as MouseEvent);
2727
});
28+
29+
// When the form is dynamically created (not SSR'd), the <for> loop
30+
// for groups produces no buttons. Ensure they exist after first render.
31+
requestAnimationFrame(() => this.ensureGroupButtons());
2832
}
2933

3034
async prepare(): Promise<void> {
@@ -37,6 +41,47 @@ export class CbContactForm extends RenderableFASTElement(FASTElement) {
3741
this.selectedGroup = this.group || (groups.length > 0 ? groups[0] : '');
3842
}
3943

44+
private ensureGroupButtons(): void {
45+
const sr = this.shadowRoot;
46+
if (!sr) return;
47+
48+
const existing = sr.querySelectorAll('.group-option');
49+
if (existing.length > 0) return;
50+
51+
// Read groups from the sidebar (always SSR'd with group nav items)
52+
const hostRoot = this.getRootNode() as ShadowRoot;
53+
const app = hostRoot?.host;
54+
const sidebar = app?.shadowRoot?.querySelector('cb-sidebar');
55+
const navItems = sidebar?.shadowRoot?.querySelectorAll('.nav-item[data-nav]') || [];
56+
const groups: string[] = [];
57+
for (const el of navItems) {
58+
const label = (el as HTMLElement).getAttribute('data-nav') || '';
59+
if (!['Dashboard', 'All Contacts', 'Favorites'].includes(label) && label) {
60+
groups.push(label);
61+
}
62+
}
63+
64+
if (groups.length === 0) return;
65+
66+
const selector = sr.querySelector('.group-selector');
67+
if (!selector) return;
68+
69+
for (const g of groups) {
70+
const btn = document.createElement('button');
71+
btn.className = 'group-option';
72+
btn.setAttribute('data-group', g);
73+
btn.textContent = g;
74+
selector.appendChild(btn);
75+
}
76+
77+
formGroupsStore.set(this, groups);
78+
this.selectedGroup = this.group || groups[0] || '';
79+
80+
for (const btn of selector.querySelectorAll('.group-option')) {
81+
btn.classList.toggle('active', btn.getAttribute('data-group') === this.selectedGroup);
82+
}
83+
}
84+
4085
onClick(e: MouseEvent): void {
4186
const target = e.composedPath()[0] as HTMLElement;
4287
const action = target.closest('[data-action]')?.getAttribute('data-action');

examples/integration/electron/README.md

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,18 @@ esbuild ../../app/hello-world/src/index.ts --bundle --outfile=../../app/hello-wo
1919

2020
## Usage
2121

22-
Run with the default hello-world app:
23-
24-
```bash
25-
pnpm start
26-
```
27-
28-
Or point to any other WebUI app:
29-
3022
```bash
3123
# hello-world
32-
electron dist/main.js ../../app/hello-world/dist ../../app/hello-world/data/state.json --plugin=fast
24+
pnpm start ../../app/hello-world/dist ../../app/hello-world/data/state.json --plugin=fast
3325

3426
# contact-book-manager
35-
electron dist/main.js ../../app/contact-book-manager/dist ../../app/contact-book-manager/data/state.json --plugin=fast
27+
pnpm start ../../app/contact-book-manager/dist ../../app/contact-book-manager/data/state.json --plugin=fast
3628
```
3729

3830
## CLI Arguments
3931

40-
| Argument | Description | Default |
41-
|---|---|---|
42-
| `dist-dir` | Path to the app's `dist/` directory containing `protocol.bin` and CSS/JS assets | `../../app/hello-world/dist` |
43-
| `state.json` | Path to the state JSON file | `../../app/hello-world/data/state.json` |
44-
| `--plugin=fast` | Enable FAST hydration plugin | _(disabled)_ |
32+
| Argument | Description |
33+
|---|---|
34+
| `dist-dir` | **(required)** Path to the app's `dist/` directory containing `protocol.bin` and CSS/JS assets |
35+
| `state.json` | **(required)** Path to the state JSON file |
36+
| `--plugin=fast` | Enable FAST hydration plugin _(optional)_ |

examples/integration/electron/main.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,15 @@ const args = process.argv.slice(2).filter(a => !a.startsWith('--inspect'));
2525
const positional = args.filter(a => !a.startsWith('--'));
2626
const flags = args.filter(a => a.startsWith('--'));
2727

28-
const distDir = resolve(positional[0] || join(import.meta.dirname, '..', '..', 'app', 'hello-world', 'dist'));
29-
const statePath = resolve(positional[1] || join(import.meta.dirname, '..', '..', 'app', 'hello-world', 'data', 'state.json'));
28+
if (positional.length < 2) {
29+
console.error('Usage: electron dist/main.js <dist-dir> <state.json> [--plugin=fast]');
30+
console.error(' dist-dir Path to the app dist/ directory containing protocol.bin');
31+
console.error(' state.json Path to the JSON state file');
32+
process.exit(1);
33+
}
34+
35+
const distDir = resolve(positional[0]);
36+
const statePath = resolve(positional[1]);
3037

3138
const pluginArg = flags.find(a => a.startsWith('--plugin='));
3239
const pluginName = pluginArg ? pluginArg.split('=')[1] : undefined;
@@ -36,7 +43,7 @@ const pluginName = pluginArg ? pluginArg.split('=')[1] : undefined;
3643
// ---------------------------------------------------------------------------
3744

3845
function loadAddon() {
39-
const root = resolve(import.meta.dirname, '..', '..', '..');
46+
const root = resolve(import.meta.dirname, '..', '..', '..', '..');
4047
const ext = platform() === 'darwin' ? 'dylib' : platform() === 'win32' ? 'dll' : 'so';
4148
const prefix = platform() === 'win32' ? '' : 'lib';
4249
const filename = `${prefix}webui_node.${ext}`;

examples/integration/electron/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"type": "module",
66
"scripts": {
77
"build": "esbuild main.ts preload.ts --outdir=dist --platform=node --format=esm",
8-
"start": "npm run build && electron dist/main.js"
8+
"start": "npm run build && electron dist/main.js --"
99
},
1010
"devDependencies": {
1111
"electron": "catalog:",

examples/integration/node/index.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,15 @@ function loadAddon() {
4040
);
4141
}
4242

43-
const protocolPath =
44-
process.argv[2] || "../../app/hello-world/dist/protocol.bin";
45-
const statePath = process.argv[3] || "../../app/hello-world/data/state.json";
43+
if (!process.argv[2] || !process.argv[3]) {
44+
console.error("Usage: node index.js <protocol.bin> <state.json> [--plugin=fast]");
45+
console.error(" protocol.bin Path to the compiled protocol binary");
46+
console.error(" state.json Path to the JSON state file");
47+
process.exit(1);
48+
}
49+
50+
const protocolPath = process.argv[2];
51+
const statePath = process.argv[3];
4652

4753
// Check for --plugin=fast flag
4854
const pluginArg = process.argv.find((a) => a.startsWith("--plugin="));

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)