Selection → installation gap:syncSkills() in finalize.ts completely ignored state.selectedSkills. The multiselect UI was a no-op — ALL skills were synced regardless of what the user picked.
Silent failure: If the skills sync script exited non-zero, the error was swallowed in a bare catch {}. If the script was missing, nothing happened with no message to the user.
No per-skill granularity:mosaic-sync-skills was all-or-nothing rsync with no whitelist concept. There was no mechanism to link only a subset of skills.
IUV-02-03: Skill installer rework
syncSkills() now accepts selectedSkills[] and passes MOSAIC_INSTALL_SKILLS (colon-separated) to the bash script
mosaic-sync-skills filters the linking phase to only the whitelisted skills when MOSAIC_INSTALL_SKILLS is set (empty/unset = all skills, preserving legacy mosaic sync behavior)
Script not found → clear error message instead of silent no-op
Non-zero exit code → stderr captured and shown to the user
Post-install summary reports "N installed" or failure reason with recovery hint
IUV-02-04: Tests + gates
13 unit tests for deriveCorsOrigin (localhost, loopback, remote, https override)
## Summary
Closes #437
### IUV-02-01: Replace CORS origin prompt with FQDN/hostname input
**Problem:** The installer prompted users for a raw CORS origin string (`http://localhost:3000`). Users don't know what CORS is.
**Fix:**
- New `deriveCorsOrigin(hostname, webUiPort, useHttps?)` pure function
- Prompt now asks **"Web UI hostname"** (default: `localhost`)
- Auto-detects protocol: `localhost`/`127.0.0.1` always http; remote hosts default to https
- For remote hosts, a follow-up confirm asks "Is HTTPS enabled?" (defaults to yes)
- Headless: `MOSAIC_HOSTNAME` env var as a friendly alternative to `MOSAIC_CORS_ORIGIN`
- `GatewayState` gains optional `hostname` field to track the raw user input
- Fallback paths now read `GATEWAY_CORS_ORIGIN` from existing .env instead of hardcoding
### IUV-02-02: Skill installer failure modes diagnosed
1. **Selection → installation gap:** `syncSkills()` in `finalize.ts` completely ignored `state.selectedSkills`. The multiselect UI was a no-op — ALL skills were synced regardless of what the user picked.
2. **Silent failure:** If the skills sync script exited non-zero, the error was swallowed in a bare `catch {}`. If the script was missing, nothing happened with no message to the user.
3. **No per-skill granularity:** `mosaic-sync-skills` was all-or-nothing rsync with no whitelist concept. There was no mechanism to link only a subset of skills.
### IUV-02-03: Skill installer rework
- `syncSkills()` now accepts `selectedSkills[]` and passes `MOSAIC_INSTALL_SKILLS` (colon-separated) to the bash script
- `mosaic-sync-skills` filters the linking phase to only the whitelisted skills when `MOSAIC_INSTALL_SKILLS` is set (empty/unset = all skills, preserving legacy `mosaic sync` behavior)
- Script not found → clear error message instead of silent no-op
- Non-zero exit code → stderr captured and shown to the user
- Post-install summary reports "N installed" or failure reason with recovery hint
### IUV-02-04: Tests + gates
- 13 unit tests for `deriveCorsOrigin` (localhost, loopback, remote, https override)
- 5 integration tests for finalize skill installer (selection, skip, failure modes, missing script)
- 237 tests passing across 26 test files
- `pnpm typecheck && pnpm lint && pnpm format:check` all green
## Test plan
- [ ] Unit tests: `pnpm --filter @mosaicstack/mosaic test`
- [ ] Typecheck: `pnpm typecheck`
- [ ] Lint: `pnpm lint`
- [ ] Format: `pnpm format:check`
- [ ] CI green on Woodpecker
🤖 Generated with [Claude Code](https://claude.com/claude-code)
IUV-02-01: Replace raw CORS origin prompt with a friendly hostname input
- Add `deriveCorsOrigin(hostname, webUiPort, useHttps?)` pure function
- Prompt asks "Web UI hostname" (default: localhost) instead of "CORS origin"
- Auto-detects http vs https: localhost/127.0.0.1 always http, remote defaults to https
- For remote hosts, asks "Is HTTPS enabled?" (defaults to yes)
- Headless: MOSAIC_HOSTNAME env var as friendly alternative to MOSAIC_CORS_ORIGIN
- GatewayState gains optional `hostname` field to track the raw input
- Fallback paths now read GATEWAY_CORS_ORIGIN from .env instead of hardcoding
IUV-02-02: Diagnose skill installer failure modes (documented in PR body)
- Selection → installation gap: syncSkills() ignored state.selectedSkills entirely
- Silent failure: missing catalog directory had no user-visible error
- No per-skill granularity: all-or-nothing rsync with no whitelist concept
IUV-02-03: Rework skill installer end-to-end
- syncSkills() now accepts selectedSkills[] and passes MOSAIC_INSTALL_SKILLS
(colon-separated) to the bash script
- Script filters linking to only the whitelisted skills when MOSAIC_INSTALL_SKILLS is set
- Missing script surfaced clearly instead of silently swallowed
- Non-zero exit captured from stderr and shown to the user
- Post-install summary reports "N installed" or failure reason
IUV-02-04: Tests + gates
- 13 unit tests for deriveCorsOrigin covering localhost, remote, https override
- 5 integration tests for finalize skill installer (selection, skip, failure, missing script)
- pnpm typecheck + lint + format:check all green
- 237 tests passing (26 test files)
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Closes #437
IUV-02-01: Replace CORS origin prompt with FQDN/hostname input
Problem: The installer prompted users for a raw CORS origin string (
http://localhost:3000). Users don't know what CORS is.Fix:
deriveCorsOrigin(hostname, webUiPort, useHttps?)pure functionlocalhost)localhost/127.0.0.1always http; remote hosts default to httpsMOSAIC_HOSTNAMEenv var as a friendly alternative toMOSAIC_CORS_ORIGINGatewayStategains optionalhostnamefield to track the raw user inputGATEWAY_CORS_ORIGINfrom existing .env instead of hardcodingIUV-02-02: Skill installer failure modes diagnosed
syncSkills()infinalize.tscompletely ignoredstate.selectedSkills. The multiselect UI was a no-op — ALL skills were synced regardless of what the user picked.catch {}. If the script was missing, nothing happened with no message to the user.mosaic-sync-skillswas all-or-nothing rsync with no whitelist concept. There was no mechanism to link only a subset of skills.IUV-02-03: Skill installer rework
syncSkills()now acceptsselectedSkills[]and passesMOSAIC_INSTALL_SKILLS(colon-separated) to the bash scriptmosaic-sync-skillsfilters the linking phase to only the whitelisted skills whenMOSAIC_INSTALL_SKILLSis set (empty/unset = all skills, preserving legacymosaic syncbehavior)IUV-02-04: Tests + gates
deriveCorsOrigin(localhost, loopback, remote, https override)pnpm typecheck && pnpm lint && pnpm format:checkall greenTest plan
pnpm --filter @mosaicstack/mosaic testpnpm typecheckpnpm lintpnpm format:check🤖 Generated with Claude Code