commit 30946ce94eae9c10bbd284afdf37e17e40971067 Author: Juergen Kunz Date: Tue May 5 12:03:46 2026 +0000 Add catalog package diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4336e0e --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +node_modules/ +dist/ +dist_*/ +dist_ts/ +coverage/ +.nyc_output/ +.nogit/ +.playwright-mcp/ +*.log +.DS_Store +.env +.env.* +!.env.example diff --git a/.smartconfig.json b/.smartconfig.json new file mode 100644 index 0000000..3ba1a7f --- /dev/null +++ b/.smartconfig.json @@ -0,0 +1,43 @@ +{ + "@git.zone/cli": { + "projectType": "wcc", + "module": { + "githost": "code.foss.global", + "gitscope": "smarthome.exchange", + "gitrepo": "catalog", + "description": "Web component catalog for smarthome.exchange.", + "npmPackagename": "@smarthome.exchange/catalog", + "license": "MIT", + "projectDomain": "smarthome.exchange", + "keywords": [ + "smarthome.exchange", + "web components", + "home automation", + "agents", + "design system" + ] + }, + "release": { + "registries": [ + "https://verdaccio.lossless.digital", + "https://registry.npmjs.org" + ], + "accessLevel": "public" + } + }, + "@git.zone/tsbundle": { + "bundles": [ + { + "from": "./ts_web/index.ts", + "to": "./dist_bundle/bundle.js", + "outputMode": "bundle", + "bundler": "esbuild", + "production": true + } + ] + }, + "@git.zone/tswatch": { + "preset": "element" + }, + "@ship.zone/szci": {} +} diff --git a/html/index.html b/html/index.html new file mode 100644 index 0000000..f70ca09 --- /dev/null +++ b/html/index.html @@ -0,0 +1,11 @@ + + + + + + smarthome.exchange catalog + + + + + diff --git a/html/index.ts b/html/index.ts new file mode 100644 index 0000000..66c81c3 --- /dev/null +++ b/html/index.ts @@ -0,0 +1,29 @@ +import * as deesWccTools from '@design.estate/dees-wcctools'; +import * as deesDomTools from '@design.estate/dees-domtools'; + +import * as elements from '../ts_web/elements/index.js'; + +const fullPageElementNames = new Set([ + 'ShxLandingPage', + 'ShxConsoleShell', +]); + +deesWccTools.setupWccTools({ + sections: [ + { + name: 'Full Pages', + type: 'elements', + items: elements, + icon: 'web', + filter: (nameArg: string) => fullPageElementNames.has(nameArg), + }, + { + name: 'Primitives', + type: 'elements', + items: elements, + icon: 'category', + filter: (nameArg: string) => nameArg.startsWith('Shx') && !fullPageElementNames.has(nameArg), + }, + ], +}); +deesDomTools.elementBasic.setup(); diff --git a/package.json b/package.json new file mode 100644 index 0000000..53733c9 --- /dev/null +++ b/package.json @@ -0,0 +1,64 @@ +{ + "name": "@smarthome.exchange/catalog", + "version": "0.1.0", + "private": false, + "description": "Web component catalog for smarthome.exchange.", + "exports": { + ".": "./dist_ts_web/index.js" + }, + "main": "dist_ts_web/index.js", + "typings": "dist_ts_web/index.d.ts", + "type": "module", + "author": "Task Venture Capital GmbH", + "license": "MIT", + "scripts": { + "test": "pnpm run build", + "build": "tsbuild tsfolders --allowimplicitany && tsbundle", + "watch": "tswatch", + "buildDocs": "tsdoc" + }, + "dependencies": { + "@design.estate/dees-catalog": "^3.81.0", + "@design.estate/dees-domtools": "^2.5.6", + "@design.estate/dees-element": "^2.2.4", + "@design.estate/dees-wcctools": "^3.9.0", + "lucide": "^1.14.0" + }, + "devDependencies": { + "@git.zone/tsbuild": "^4.4.0", + "@git.zone/tsbundle": "^2.10.1", + "@git.zone/tswatch": "^3.3.3", + "@push.rocks/projectinfo": "^5.1.0", + "@types/node": "^25.6.0" + }, + "files": [ + "ts_web/**/*", + "dist/**/*", + "dist_*/**/*", + "dist_ts_web/**/*", + "assets/**/*", + "html/**/*", + "license", + "readme.md", + "changelog.md" + ], + "repository": { + "type": "git", + "url": "git+ssh://git@code.foss.global:29419/smarthome.exchange/catalog.git" + }, + "bugs": { + "url": "https://code.foss.global/smarthome.exchange/catalog/issues" + }, + "homepage": "https://code.foss.global/smarthome.exchange/catalog#readme", + "browserslist": [ + "last 1 chrome versions" + ], + "keywords": [ + "smarthome.exchange", + "catalog", + "web components", + "home automation", + "design system" + ], + "packageManager": "pnpm@10.28.2" +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..ecd4cb3 --- /dev/null +++ b/readme.md @@ -0,0 +1,3 @@ +# @smarthome.exchange/catalog + +Web component catalog for smarthome.exchange landing, console, dashboard, and primitive UI components. diff --git a/ts_web/demo/demo-data.ts b/ts_web/demo/demo-data.ts new file mode 100644 index 0000000..0d5e9f6 --- /dev/null +++ b/ts_web/demo/demo-data.ts @@ -0,0 +1,20 @@ +export const shxDemoAgents = [ + { id: 'comfort', name: 'Comfort', role: 'Climate, light, ambience', color: '#f59e0b', mode: 'auto', latest: 'Holding Office at 22.4C during Mira\'s call' }, + { id: 'sentinel', name: 'Sentinel', role: 'Locks, cameras, presence', color: '#ef4444', mode: 'ask', latest: 'Front door unlocked for Jonah' }, + { id: 'watt', name: 'Watt', role: 'Energy, solar, EV, appliances', color: '#22c55e', mode: 'ask', latest: 'Proposed EV charging during solar peak' }, + { id: 'dawn', name: 'Dawn', role: 'Wake, wind-down, daily ritual', color: '#a78bfa', mode: 'auto', latest: 'Prepared evening wind-down' }, + { id: 'echo', name: 'Echo', role: 'Audio, media, follow-me', color: '#60a5fa', mode: 'suggest', latest: 'Suggested dinner playlist' }, + { id: 'steward', name: 'Steward', role: 'Errands, deliveries, household ops', color: '#f87171', mode: 'ask', latest: 'Laundry pickup reminder queued' }, +]; + +export const shxDemoDevices = [ + { id: 'climate.office', name: 'Office Thermostat', room: 'Office', state: '22.4C hold', agent: 'comfort' }, + { id: 'lock.front', name: 'Front Lock', room: 'Hall', state: 'unlocked', agent: 'sentinel' }, + { id: 'energy.solar', name: 'Solar Inverter', room: 'Roof', state: '3.8 kW', agent: 'watt' }, + { id: 'light.living.floor', name: 'Living Floor Lamp', room: 'Living', state: 'on 65%', agent: 'comfort' }, +]; + +export const shxDemoApprovals = [ + { id: 'approval:ev', agent: 'watt', title: 'Charge EV to 80%', reason: 'Solar surplus and tariff dip between 15:00 and 17:00.', confidence: 0.86 }, + { id: 'approval:trash', agent: 'steward', title: 'Take out paper recycling', reason: 'Collection tomorrow at 06:00.', confidence: 0.71 }, +]; diff --git a/ts_web/elements/index.ts b/ts_web/elements/index.ts new file mode 100644 index 0000000..04f2a0b --- /dev/null +++ b/ts_web/elements/index.ts @@ -0,0 +1,7 @@ +export * from './tokens.js'; +export * from './shx-badge.js'; +export * from './shx-button.js'; +export * from './shx-card.js'; +export * from './shx-icon.js'; +export * from './shx-console-shell.js'; +export * from './shx-landing-page.js'; diff --git a/ts_web/elements/shx-badge.ts b/ts_web/elements/shx-badge.ts new file mode 100644 index 0000000..7111e21 --- /dev/null +++ b/ts_web/elements/shx-badge.ts @@ -0,0 +1,37 @@ +import { DeesElement, html, customElement, css } from '@design.estate/dees-element'; +import { shxElementStyles } from './tokens.js'; + +declare global { + interface HTMLElementTagNameMap { + 'shx-badge': ShxBadge; + } +} + +@customElement('shx-badge') +export class ShxBadge extends DeesElement { + public static demo = () => html`local-first`; + public static demoGroups = ['smarthome.exchange primitives']; + + public static styles = [ + ...shxElementStyles, + css` + :host { + display: inline-flex; + align-items: center; + min-height: 22px; + padding: 0 8px; + border: 1px solid color-mix(in srgb, var(--shx-accent), transparent 70%); + border-radius: 999px; + background: var(--shx-accent-soft); + color: var(--shx-accent); + font: 600 11px/1 var(--shx-mono); + letter-spacing: 0.04em; + text-transform: uppercase; + } + `, + ]; + + public render() { + return html``; + } +} diff --git a/ts_web/elements/shx-button.ts b/ts_web/elements/shx-button.ts new file mode 100644 index 0000000..fa8e126 --- /dev/null +++ b/ts_web/elements/shx-button.ts @@ -0,0 +1,44 @@ +import { DeesElement, html, customElement, css } from '@design.estate/dees-element'; +import { shxElementStyles } from './tokens.js'; + +declare global { + interface HTMLElementTagNameMap { + 'shx-button': ShxButton; + } +} + +@customElement('shx-button') +export class ShxButton extends DeesElement { + public static demo = () => html`Open console`; + public static demoGroups = ['smarthome.exchange primitives']; + + public static styles = [ + ...shxElementStyles, + css` + :host { + display: inline-flex; + } + button { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 34px; + padding: 0 14px; + border: 1px solid var(--shx-accent); + border-radius: 7px; + background: var(--shx-accent); + color: #fff; + font: 600 13px/1 var(--shx-font); + letter-spacing: -0.01em; + cursor: pointer; + } + button:hover { + filter: brightness(1.08); + } + `, + ]; + + public render() { + return html``; + } +} diff --git a/ts_web/elements/shx-card.ts b/ts_web/elements/shx-card.ts new file mode 100644 index 0000000..f213143 --- /dev/null +++ b/ts_web/elements/shx-card.ts @@ -0,0 +1,37 @@ +import { DeesElement, html, customElement, css } from '@design.estate/dees-element'; +import { shxElementStyles } from './tokens.js'; + +declare global { + interface HTMLElementTagNameMap { + 'shx-card': ShxCard; + } +} + +@customElement('shx-card') +export class ShxCard extends DeesElement { + public static demo = () => html`Card

Reusable panel frame.

`; + public static demoGroups = ['smarthome.exchange primitives']; + + public static styles = [ + ...shxElementStyles, + css` + :host { + display: block; + } + .card { + border: 1px solid var(--shx-border); + border-radius: var(--shx-radius); + background: var(--shx-card); + box-shadow: 0 16px 50px rgba(0,0,0,0.18); + overflow: hidden; + } + .body { + padding: 18px; + } + `, + ]; + + public render() { + return html`
`; + } +} diff --git a/ts_web/elements/shx-console-shell.ts b/ts_web/elements/shx-console-shell.ts new file mode 100644 index 0000000..0121f8d --- /dev/null +++ b/ts_web/elements/shx-console-shell.ts @@ -0,0 +1,179 @@ +import { DeesElement, html, customElement, css } from '@design.estate/dees-element'; +import { shxDemoAgents, shxDemoApprovals, shxDemoDevices } from '../demo/demo-data.js'; +import { shxElementStyles } from './tokens.js'; +import './shx-badge.js'; +import './shx-card.js'; +import './shx-icon.js'; + +declare global { + interface HTMLElementTagNameMap { + 'shx-console-shell': ShxConsoleShell; + } +} + +@customElement('shx-console-shell') +export class ShxConsoleShell extends DeesElement { + public static demo = () => html``; + public static demoGroups = ['smarthome.exchange full pages']; + + public static styles = [ + ...shxElementStyles, + css` + :host { + display: block; + min-height: 100vh; + background: var(--shx-bg); + color: var(--shx-fg); + } + .shell { + display: grid; + grid-template-columns: 230px 1fr; + min-height: 100vh; + } + aside { + border-right: 1px solid var(--shx-border-soft); + background: var(--shx-card); + padding: 16px; + } + .brand { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 20px; + font: 800 14px/1 var(--shx-display); + } + .nav { + display: grid; + gap: 4px; + } + .nav div { + padding: 8px 10px; + border-radius: 7px; + color: var(--shx-fg-3); + font-size: 13px; + } + .nav div:first-child { + background: var(--shx-accent-soft); + color: var(--shx-accent); + } + main { + padding: 18px; + } + .top { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; + } + h1 { + margin: 0; + font: 800 24px/1 var(--shx-display); + letter-spacing: -0.03em; + } + .grid { + display: grid; + grid-template-columns: 1.2fr 0.8fr; + gap: 14px; + } + .list { + display: grid; + gap: 8px; + } + .row { + display: grid; + grid-template-columns: 10px 1fr auto; + gap: 10px; + align-items: center; + padding: 10px; + border: 1px solid var(--shx-border-soft); + border-radius: 8px; + background: var(--shx-card-2); + } + .dot { + width: 8px; + height: 8px; + border-radius: 50%; + } + .name { + font-weight: 700; + } + .sub { + margin-top: 2px; + color: var(--shx-fg-3); + font-size: 12px; + } + @media (max-width: 860px) { + .shell, .grid { + grid-template-columns: 1fr; + } + aside { + border-right: none; + border-bottom: 1px solid var(--shx-border-soft); + } + } + `, + ]; + + public render() { + return html` +
+ +
+
+

Birch Lane

+ hub online · 12ms +
+
+ +

Agents

+
+ ${shxDemoAgents.map((agentArg) => html` +
+ +
${agentArg.name}
${agentArg.latest}
+ ${agentArg.mode} +
+ `)} +
+
+
+ +

Pending approvals

+
+ ${shxDemoApprovals.map((approvalArg) => html` +
+ +
${approvalArg.title}
${approvalArg.reason}
+ ${Math.round(approvalArg.confidence * 100)}% +
+ `)} +
+
+ +

Devices

+
+ ${shxDemoDevices.map((deviceArg) => html` +
+ +
${deviceArg.name}
${deviceArg.room} · ${deviceArg.state}
+ ${deviceArg.agent} +
+ `)} +
+
+
+
+
+
+ `; + } +} diff --git a/ts_web/elements/shx-icon.ts b/ts_web/elements/shx-icon.ts new file mode 100644 index 0000000..bdb0f68 --- /dev/null +++ b/ts_web/elements/shx-icon.ts @@ -0,0 +1,39 @@ +import { DeesElement, html, customElement, css } from '@design.estate/dees-element'; +import { shxElementStyles } from './tokens.js'; + +declare global { + interface HTMLElementTagNameMap { + 'shx-icon': ShxIcon; + } +} + +@customElement('shx-icon') +export class ShxIcon extends DeesElement { + public static demo = () => html``; + public static demoGroups = ['smarthome.exchange primitives']; + + public static styles = [ + ...shxElementStyles, + css` + :host { + display: inline-flex; + width: 24px; + height: 24px; + color: var(--shx-accent); + } + svg { + width: 100%; + height: 100%; + } + `, + ]; + + public render() { + return html` + + + + + `; + } +} diff --git a/ts_web/elements/shx-landing-page.ts b/ts_web/elements/shx-landing-page.ts new file mode 100644 index 0000000..bd7f106 --- /dev/null +++ b/ts_web/elements/shx-landing-page.ts @@ -0,0 +1,207 @@ +import { DeesElement, html, customElement, css } from '@design.estate/dees-element'; +import { shxElementStyles } from './tokens.js'; +import './shx-badge.js'; +import './shx-button.js'; +import './shx-icon.js'; + +declare global { + interface HTMLElementTagNameMap { + 'shx-landing-page': ShxLandingPage; + } +} + +@customElement('shx-landing-page') +export class ShxLandingPage extends DeesElement { + public static demo = () => html``; + public static demoGroups = ['smarthome.exchange full pages']; + + public static styles = [ + ...shxElementStyles, + css` + :host { + display: block; + min-height: 100vh; + background: radial-gradient(circle at 80% 10%, rgba(59,130,246,0.18), transparent 30%), var(--shx-bg); + color: var(--shx-fg); + } + .wrap { + max-width: 1180px; + margin: 0 auto; + padding: 0 28px; + } + nav { + display: flex; + align-items: center; + gap: 18px; + min-height: 64px; + border-bottom: 1px solid var(--shx-border-soft); + } + .brand { + display: inline-flex; + align-items: center; + gap: 10px; + font: 800 15px/1 var(--shx-display); + letter-spacing: -0.02em; + } + .links { + display: flex; + gap: 14px; + margin-left: auto; + color: var(--shx-fg-3); + font-size: 13px; + } + .hero { + display: grid; + grid-template-columns: 1.05fr 0.95fr; + gap: 44px; + align-items: center; + padding: 88px 0 72px; + } + h1 { + margin: 18px 0 18px; + max-width: 720px; + font: 800 clamp(44px, 7vw, 78px)/0.95 var(--shx-display); + letter-spacing: -0.06em; + } + h1 em, h2 em { + color: var(--shx-accent); + font-style: normal; + } + .lede { + max-width: 680px; + color: var(--shx-fg-2); + font-size: 18px; + line-height: 1.55; + } + .cta { + display: flex; + gap: 10px; + margin-top: 28px; + } + .panel { + border: 1px solid var(--shx-border); + border-radius: 16px; + background: color-mix(in srgb, var(--shx-card), transparent 4%); + box-shadow: 0 24px 70px rgba(0,0,0,0.34); + overflow: hidden; + } + .panel-head { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 14px; + border-bottom: 1px solid var(--shx-border-soft); + color: var(--shx-fg-3); + font: 500 11px/1 var(--shx-mono); + } + .panel-body { + display: grid; + gap: 10px; + padding: 16px; + } + .tool-row { + display: grid; + grid-template-columns: 12px 1fr auto; + gap: 10px; + align-items: center; + padding: 10px; + border: 1px solid var(--shx-border-soft); + border-radius: 9px; + background: var(--shx-card-2); + font-size: 13px; + } + .dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--shx-green); + box-shadow: 0 0 12px var(--shx-green); + } + section { + padding: 64px 0; + border-top: 1px solid var(--shx-border-soft); + } + h2 { + margin: 0 0 16px; + max-width: 760px; + font: 800 40px/1.05 var(--shx-display); + letter-spacing: -0.04em; + } + .grid3 { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 14px; + margin-top: 28px; + } + .mini { + min-height: 220px; + padding: 24px; + border: 1px solid var(--shx-border); + border-radius: 14px; + background: var(--shx-card); + } + .mini strong { + display: block; + margin-bottom: 10px; + font: 800 20px/1.1 var(--shx-display); + } + .mini p { + color: var(--shx-fg-3); + line-height: 1.55; + } + @media (max-width: 860px) { + .hero, .grid3 { + grid-template-columns: 1fr; + } + .links { + display: none; + } + } + `, + ]; + + public render() { + return html` +
+ +
+
+ local-first home OS +

An open home OS, spoken in the language of agents.

+

Every device, sensor, and routine becomes a typed tool: discoverable, permission-scoped, audit-logged, and callable by Claude, a local model, or your own TypeScript agent.

+
+ Open console + BYO LLM + MCP-native +
+
+
+
birch-lane.local/mcplive
+
+
devices.climate.office.holdauto
+
agents.watt.decideask
+
devices.lock.front.setapproval
+
audit.receipts.appendsigned
+
+
+
+
+

Three primitives. That is the whole platform.

+
+
Typed devices

Matter, Zigbee, Z-Wave, Thread, MQTT, HomeKit, ESPHome, and Home Assistant devices become callable tools.

+
Scoped agents

Agents receive revocable scopes and explicit modes: auto, ask, or suggest. No ambient access.

+
Audit receipts

Every effectful call creates a receipt with caller, scope, input summary, output summary, and undo intent.

+
+
+
+ `; + } +} diff --git a/ts_web/elements/tokens.ts b/ts_web/elements/tokens.ts new file mode 100644 index 0000000..51bd2c2 --- /dev/null +++ b/ts_web/elements/tokens.ts @@ -0,0 +1,56 @@ +import { css, cssManager } from '@design.estate/dees-element'; + +export const shxBaseStyles = css` + :host { + --shx-bg: ${cssManager.bdTheme('#fafafa', '#0a0a0a')}; + --shx-bg-2: ${cssManager.bdTheme('#f4f4f5', '#111111')}; + --shx-card: ${cssManager.bdTheme('#ffffff', '#121212')}; + --shx-card-2: ${cssManager.bdTheme('#f8fafc', '#161616')}; + --shx-fg: ${cssManager.bdTheme('#0a0a0a', '#fafafa')}; + --shx-fg-2: ${cssManager.bdTheme('#3f3f46', '#d4d4d8')}; + --shx-fg-3: ${cssManager.bdTheme('#71717a', '#a1a1aa')}; + --shx-fg-4: ${cssManager.bdTheme('#a1a1aa', '#525252')}; + --shx-border: ${cssManager.bdTheme('#e4e4e7', '#262626')}; + --shx-border-soft: ${cssManager.bdTheme('#f1f5f9', '#1c1c1c')}; + --shx-accent: ${cssManager.bdTheme('#0050b9', '#3b82f6')}; + --shx-accent-2: ${cssManager.bdTheme('#6e5be6', '#a78bfa')}; + --shx-accent-soft: ${cssManager.bdTheme('#e6effb', 'rgba(59,130,246,0.15)')}; + --shx-green: ${cssManager.bdTheme('#16a34a', '#4ade80')}; + --shx-amber: ${cssManager.bdTheme('#b45309', '#fbbf24')}; + --shx-red: ${cssManager.bdTheme('#dc2626', '#f87171')}; + --shx-radius: 10px; + --shx-font: 'Geist', ui-sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + --shx-display: 'Plus Jakarta Sans', 'Geist', ui-sans-serif, system-ui, sans-serif; + --shx-mono: 'Intel One Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + color: var(--shx-fg); + font-family: var(--shx-font); + font-feature-settings: 'cv11', 'tnum', 'cv05'; + -webkit-font-smoothing: antialiased; + } + + :host([theme="dark"]), + :host([dark]) { + --shx-bg: #0a0a0a; + --shx-bg-2: #111111; + --shx-card: #121212; + --shx-card-2: #161616; + --shx-fg: #fafafa; + --shx-fg-2: #d4d4d8; + --shx-fg-3: #a1a1aa; + --shx-fg-4: #525252; + --shx-border: #262626; + --shx-border-soft: #1c1c1c; + --shx-accent: #3b82f6; + --shx-accent-2: #a78bfa; + --shx-accent-soft: rgba(59,130,246,0.15); + --shx-green: #4ade80; + --shx-amber: #fbbf24; + --shx-red: #f87171; + } + + * { + box-sizing: border-box; + } +`; + +export const shxElementStyles = [cssManager.defaultStyles, shxBaseStyles]; diff --git a/ts_web/index.ts b/ts_web/index.ts new file mode 100644 index 0000000..59c9569 --- /dev/null +++ b/ts_web/index.ts @@ -0,0 +1,2 @@ +export * from './elements/index.js'; +export * from './demo/demo-data.js'; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..7862634 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "esModuleInterop": true, + "verbatimModuleSyntax": true, + "types": ["node"] + }, + "exclude": [ + "dist_*/**/*.d.ts" + ] +}