freesail 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +190 -5
  2. package/docs/A2UX_Protocol.md +183 -0
  3. package/docs/Agents.md +218 -0
  4. package/docs/Architecture.md +285 -0
  5. package/docs/CatalogReference.md +377 -0
  6. package/docs/GettingStarted.md +230 -0
  7. package/examples/demo/package.json +21 -0
  8. package/examples/demo/public/index.html +381 -0
  9. package/examples/demo/server.js +253 -0
  10. package/package.json +38 -5
  11. package/packages/core/package.json +48 -0
  12. package/packages/core/src/functions.ts +403 -0
  13. package/packages/core/src/index.ts +214 -0
  14. package/packages/core/src/parser.ts +270 -0
  15. package/packages/core/src/protocol.ts +254 -0
  16. package/packages/core/src/store.ts +452 -0
  17. package/packages/core/src/transport.ts +439 -0
  18. package/packages/core/src/types.ts +209 -0
  19. package/packages/core/tsconfig.json +10 -0
  20. package/packages/lit-ui/package.json +44 -0
  21. package/packages/lit-ui/src/catalogs/standard/catalog.json +405 -0
  22. package/packages/lit-ui/src/catalogs/standard/elements/Badge.ts +96 -0
  23. package/packages/lit-ui/src/catalogs/standard/elements/Button.ts +147 -0
  24. package/packages/lit-ui/src/catalogs/standard/elements/Card.ts +78 -0
  25. package/packages/lit-ui/src/catalogs/standard/elements/Checkbox.ts +94 -0
  26. package/packages/lit-ui/src/catalogs/standard/elements/Column.ts +66 -0
  27. package/packages/lit-ui/src/catalogs/standard/elements/Divider.ts +59 -0
  28. package/packages/lit-ui/src/catalogs/standard/elements/Image.ts +54 -0
  29. package/packages/lit-ui/src/catalogs/standard/elements/Input.ts +125 -0
  30. package/packages/lit-ui/src/catalogs/standard/elements/Progress.ts +79 -0
  31. package/packages/lit-ui/src/catalogs/standard/elements/Row.ts +68 -0
  32. package/packages/lit-ui/src/catalogs/standard/elements/Select.ts +110 -0
  33. package/packages/lit-ui/src/catalogs/standard/elements/Spacer.ts +37 -0
  34. package/packages/lit-ui/src/catalogs/standard/elements/Spinner.ts +76 -0
  35. package/packages/lit-ui/src/catalogs/standard/elements/Text.ts +86 -0
  36. package/packages/lit-ui/src/catalogs/standard/elements/index.ts +18 -0
  37. package/packages/lit-ui/src/catalogs/standard/index.ts +17 -0
  38. package/packages/lit-ui/src/index.ts +84 -0
  39. package/packages/lit-ui/src/renderer.ts +211 -0
  40. package/packages/lit-ui/src/types.ts +49 -0
  41. package/packages/lit-ui/src/utils/define-props.ts +157 -0
  42. package/packages/lit-ui/src/utils/index.ts +2 -0
  43. package/packages/lit-ui/src/utils/registry.ts +139 -0
  44. package/packages/lit-ui/tsconfig.json +11 -0
  45. package/packages/server/package.json +61 -0
  46. package/packages/server/src/adapters/index.ts +5 -0
  47. package/packages/server/src/adapters/langchain.ts +175 -0
  48. package/packages/server/src/adapters/openai.ts +209 -0
  49. package/packages/server/src/catalog-loader.ts +311 -0
  50. package/packages/server/src/index.ts +142 -0
  51. package/packages/server/src/stream.ts +329 -0
  52. package/packages/server/tsconfig.json +11 -0
  53. package/tsconfig.base.json +23 -0
  54. package/index.js +0 -3
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Select Component
3
+ *
4
+ * A dropdown select with options.
5
+ */
6
+
7
+ import { LitElement, html, css } from 'lit';
8
+ import { customElement, property } from 'lit/decorators.js';
9
+
10
+ interface SelectOption {
11
+ value: string;
12
+ label: string;
13
+ }
14
+
15
+ @customElement('fs-select')
16
+ export class FreesailSelect extends LitElement {
17
+ static override styles = css`
18
+ :host {
19
+ display: block;
20
+ }
21
+
22
+ .select-wrapper {
23
+ display: flex;
24
+ flex-direction: column;
25
+ gap: 4px;
26
+ }
27
+
28
+ label {
29
+ font-size: 0.875rem;
30
+ font-weight: 500;
31
+ color: #374151;
32
+ }
33
+
34
+ select {
35
+ font-family: inherit;
36
+ font-size: 1rem;
37
+ padding: 10px 12px;
38
+ border: 1px solid #d1d5db;
39
+ border-radius: 6px;
40
+ outline: none;
41
+ background: white;
42
+ cursor: pointer;
43
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
44
+ }
45
+
46
+ select:focus {
47
+ border-color: #2563eb;
48
+ box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
49
+ }
50
+
51
+ select:disabled {
52
+ background: #f3f4f6;
53
+ cursor: not-allowed;
54
+ }
55
+ `;
56
+
57
+ @property({ type: String }) value = '';
58
+ @property({ type: Array }) options: SelectOption[] = [];
59
+ @property({ type: String }) placeholder = 'Select an option';
60
+ @property({ type: String }) label = '';
61
+ @property({ type: Boolean }) disabled = false;
62
+ @property({ type: String }) bindPath = '';
63
+
64
+ private handleChange(e: Event) {
65
+ const select = e.target as HTMLSelectElement;
66
+ this.value = select.value;
67
+
68
+ this.dispatchEvent(new CustomEvent('fs-change', {
69
+ bubbles: true,
70
+ composed: true,
71
+ detail: {
72
+ value: this.value,
73
+ bindPath: this.bindPath,
74
+ componentId: this.id,
75
+ }
76
+ }));
77
+ }
78
+
79
+ override render() {
80
+ return html`
81
+ <div class="select-wrapper">
82
+ ${this.label ? html`<label>${this.label}</label>` : ''}
83
+
84
+ <select
85
+ .value=${this.value}
86
+ ?disabled=${this.disabled}
87
+ @change=${this.handleChange}
88
+ >
89
+ <option value="" disabled ?selected=${!this.value}>
90
+ ${this.placeholder}
91
+ </option>
92
+ ${this.options.map(opt => html`
93
+ <option
94
+ value=${opt.value}
95
+ ?selected=${opt.value === this.value}
96
+ >
97
+ ${opt.label}
98
+ </option>
99
+ `)}
100
+ </select>
101
+ </div>
102
+ `;
103
+ }
104
+ }
105
+
106
+ declare global {
107
+ interface HTMLElementTagNameMap {
108
+ 'fs-select': FreesailSelect;
109
+ }
110
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Spacer Component
3
+ *
4
+ * A flexible space component for layout.
5
+ */
6
+
7
+ import { LitElement, html, css } from 'lit';
8
+ import { customElement, property } from 'lit/decorators.js';
9
+
10
+ @customElement('fs-spacer')
11
+ export class FreesailSpacer extends LitElement {
12
+ static override styles = css`
13
+ :host {
14
+ display: block;
15
+ }
16
+
17
+ .spacer.flex {
18
+ flex: 1;
19
+ }
20
+ `;
21
+
22
+ @property({ type: String }) size = 'flex';
23
+
24
+ override render() {
25
+ if (this.size === 'flex') {
26
+ return html`<div class="spacer flex"></div>`;
27
+ }
28
+
29
+ return html`<div class="spacer" style="height: ${this.size}; width: ${this.size}"></div>`;
30
+ }
31
+ }
32
+
33
+ declare global {
34
+ interface HTMLElementTagNameMap {
35
+ 'fs-spacer': FreesailSpacer;
36
+ }
37
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Spinner Component
3
+ *
4
+ * A loading spinner indicator.
5
+ */
6
+
7
+ import { LitElement, html, css } from 'lit';
8
+ import { customElement, property } from 'lit/decorators.js';
9
+
10
+ type SpinnerSize = 'small' | 'medium' | 'large';
11
+
12
+ @customElement('fs-spinner')
13
+ export class FreesailSpinner extends LitElement {
14
+ static override styles = css`
15
+ :host {
16
+ display: inline-flex;
17
+ align-items: center;
18
+ justify-content: center;
19
+ }
20
+
21
+ .spinner {
22
+ border-radius: 50%;
23
+ border-style: solid;
24
+ border-color: currentColor;
25
+ border-top-color: transparent;
26
+ animation: spin 0.8s linear infinite;
27
+ }
28
+
29
+ .size-small {
30
+ width: 16px;
31
+ height: 16px;
32
+ border-width: 2px;
33
+ }
34
+
35
+ .size-medium {
36
+ width: 24px;
37
+ height: 24px;
38
+ border-width: 3px;
39
+ }
40
+
41
+ .size-large {
42
+ width: 36px;
43
+ height: 36px;
44
+ border-width: 4px;
45
+ }
46
+
47
+ @keyframes spin {
48
+ to {
49
+ transform: rotate(360deg);
50
+ }
51
+ }
52
+ `;
53
+
54
+ @property({ type: String }) size: SpinnerSize = 'medium';
55
+ @property({ type: String }) color = 'currentColor';
56
+
57
+ override render() {
58
+ const classes = `spinner size-${this.size}`;
59
+ const style = `color: ${this.color}`;
60
+
61
+ return html`
62
+ <div
63
+ class=${classes}
64
+ style=${style}
65
+ role="status"
66
+ aria-label="Loading"
67
+ ></div>
68
+ `;
69
+ }
70
+ }
71
+
72
+ declare global {
73
+ interface HTMLElementTagNameMap {
74
+ 'fs-spinner': FreesailSpinner;
75
+ }
76
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Text Component
3
+ *
4
+ * A text display component with variant styles.
5
+ */
6
+
7
+ import { LitElement, html, css } from 'lit';
8
+ import { customElement, property } from 'lit/decorators.js';
9
+
10
+ type TextVariant = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'body' | 'caption' | 'overline';
11
+ type FontWeight = 'light' | 'normal' | 'medium' | 'semibold' | 'bold';
12
+
13
+ @customElement('fs-text')
14
+ export class FreesailText extends LitElement {
15
+ static override styles = css`
16
+ :host {
17
+ display: block;
18
+ }
19
+
20
+ .text {
21
+ margin: 0;
22
+ font-family: inherit;
23
+ }
24
+
25
+ .h1 { font-size: 2.5rem; line-height: 1.2; }
26
+ .h2 { font-size: 2rem; line-height: 1.25; }
27
+ .h3 { font-size: 1.75rem; line-height: 1.3; }
28
+ .h4 { font-size: 1.5rem; line-height: 1.35; }
29
+ .h5 { font-size: 1.25rem; line-height: 1.4; }
30
+ .h6 { font-size: 1rem; line-height: 1.45; }
31
+ .body { font-size: 1rem; line-height: 1.5; }
32
+ .caption { font-size: 0.875rem; line-height: 1.4; }
33
+ .overline {
34
+ font-size: 0.75rem;
35
+ line-height: 1.4;
36
+ text-transform: uppercase;
37
+ letter-spacing: 0.1em;
38
+ }
39
+
40
+ .weight-light { font-weight: 300; }
41
+ .weight-normal { font-weight: 400; }
42
+ .weight-medium { font-weight: 500; }
43
+ .weight-semibold { font-weight: 600; }
44
+ .weight-bold { font-weight: 700; }
45
+
46
+ .align-left { text-align: left; }
47
+ .align-center { text-align: center; }
48
+ .align-right { text-align: right; }
49
+ .align-justify { text-align: justify; }
50
+ `;
51
+
52
+ @property({ type: String }) text = '';
53
+ @property({ type: String }) variant: TextVariant = 'body';
54
+ @property({ type: String }) color = 'inherit';
55
+ @property({ type: String }) weight: FontWeight = 'normal';
56
+ @property({ type: String }) align = 'left';
57
+
58
+ override render() {
59
+ const classes = `text ${this.variant} weight-${this.weight} align-${this.align}`;
60
+ const style = `color: ${this.color}`;
61
+
62
+ // Use appropriate semantic element
63
+ switch (this.variant) {
64
+ case 'h1':
65
+ return html`<h1 class=${classes} style=${style}>${this.text}</h1>`;
66
+ case 'h2':
67
+ return html`<h2 class=${classes} style=${style}>${this.text}</h2>`;
68
+ case 'h3':
69
+ return html`<h3 class=${classes} style=${style}>${this.text}</h3>`;
70
+ case 'h4':
71
+ return html`<h4 class=${classes} style=${style}>${this.text}</h4>`;
72
+ case 'h5':
73
+ return html`<h5 class=${classes} style=${style}>${this.text}</h5>`;
74
+ case 'h6':
75
+ return html`<h6 class=${classes} style=${style}>${this.text}</h6>`;
76
+ default:
77
+ return html`<p class=${classes} style=${style}>${this.text}</p>`;
78
+ }
79
+ }
80
+ }
81
+
82
+ declare global {
83
+ interface HTMLElementTagNameMap {
84
+ 'fs-text': FreesailText;
85
+ }
86
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Standard Catalog Elements Index
3
+ */
4
+
5
+ export { FreesailColumn } from './Column.js';
6
+ export { FreesailRow } from './Row.js';
7
+ export { FreesailText } from './Text.js';
8
+ export { FreesailButton } from './Button.js';
9
+ export { FreesailInput } from './Input.js';
10
+ export { FreesailCard } from './Card.js';
11
+ export { FreesailImage } from './Image.js';
12
+ export { FreesailDivider } from './Divider.js';
13
+ export { FreesailSpacer } from './Spacer.js';
14
+ export { FreesailBadge } from './Badge.js';
15
+ export { FreesailCheckbox } from './Checkbox.js';
16
+ export { FreesailSelect } from './Select.js';
17
+ export { FreesailSpinner } from './Spinner.js';
18
+ export { FreesailProgress } from './Progress.js';
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Standard Catalog
3
+ *
4
+ * Exports the standard A2UI catalog with all components.
5
+ */
6
+
7
+ import catalog from './catalog.json';
8
+ import type { CatalogDefinition } from '../../types.js';
9
+
10
+ // Import all elements to register them
11
+ export * from './elements/index.js';
12
+
13
+ // Export catalog definition
14
+ export const standardCatalog = catalog as unknown as CatalogDefinition;
15
+
16
+ // Export catalog ID for convenience
17
+ export const STANDARD_CATALOG_ID = 'standard_catalog_v1';
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Freesail Lit UI
3
+ *
4
+ * Web Components library for A2UX Protocol rendering.
5
+ */
6
+
7
+ // Types
8
+ export type { CatalogDefinition, ComponentDefinition, PropertyDefinition, RendererConfig } from './types.js';
9
+
10
+ // Utils
11
+ export { defineProps, getDefaults, validateProps } from './utils/define-props.js';
12
+ export { ComponentRegistry, registry } from './utils/registry.js';
13
+
14
+ // Renderer
15
+ export { SurfaceRenderer, createSurfaceRenderer } from './renderer.js';
16
+ export type { SurfaceRendererOptions } from './renderer.js';
17
+
18
+ // Standard Catalog
19
+ export {
20
+ standardCatalog,
21
+ STANDARD_CATALOG_ID,
22
+ FreesailColumn,
23
+ FreesailRow,
24
+ FreesailText,
25
+ FreesailButton,
26
+ FreesailInput,
27
+ FreesailCard,
28
+ FreesailImage,
29
+ FreesailDivider,
30
+ FreesailSpacer,
31
+ FreesailBadge,
32
+ FreesailCheckbox,
33
+ FreesailSelect,
34
+ FreesailSpinner,
35
+ FreesailProgress,
36
+ } from './catalogs/standard/index.js';
37
+
38
+ // =============================================================================
39
+ // Setup Helper
40
+ // =============================================================================
41
+
42
+ import { registry } from './utils/registry.js';
43
+ import { standardCatalog, STANDARD_CATALOG_ID } from './catalogs/standard/index.js';
44
+ import type { LitElement } from 'lit';
45
+
46
+ // Import element classes for registration
47
+ import { FreesailColumn } from './catalogs/standard/elements/Column.js';
48
+ import { FreesailRow } from './catalogs/standard/elements/Row.js';
49
+ import { FreesailText } from './catalogs/standard/elements/Text.js';
50
+ import { FreesailButton } from './catalogs/standard/elements/Button.js';
51
+ import { FreesailInput } from './catalogs/standard/elements/Input.js';
52
+ import { FreesailCard } from './catalogs/standard/elements/Card.js';
53
+ import { FreesailImage } from './catalogs/standard/elements/Image.js';
54
+ import { FreesailDivider } from './catalogs/standard/elements/Divider.js';
55
+ import { FreesailSpacer } from './catalogs/standard/elements/Spacer.js';
56
+ import { FreesailBadge } from './catalogs/standard/elements/Badge.js';
57
+ import { FreesailCheckbox } from './catalogs/standard/elements/Checkbox.js';
58
+ import { FreesailSelect } from './catalogs/standard/elements/Select.js';
59
+ import { FreesailSpinner } from './catalogs/standard/elements/Spinner.js';
60
+ import { FreesailProgress } from './catalogs/standard/elements/Progress.js';
61
+
62
+ /**
63
+ * Register the standard catalog with all components
64
+ */
65
+ export function registerStandardCatalog(): void {
66
+ const components = new Map<string, typeof LitElement>([
67
+ ['Column', FreesailColumn],
68
+ ['Row', FreesailRow],
69
+ ['Text', FreesailText],
70
+ ['Button', FreesailButton],
71
+ ['Input', FreesailInput],
72
+ ['Card', FreesailCard],
73
+ ['Image', FreesailImage],
74
+ ['Divider', FreesailDivider],
75
+ ['Spacer', FreesailSpacer],
76
+ ['Badge', FreesailBadge],
77
+ ['Checkbox', FreesailCheckbox],
78
+ ['Select', FreesailSelect],
79
+ ['Spinner', FreesailSpinner],
80
+ ['Progress', FreesailProgress],
81
+ ]);
82
+
83
+ registry.registerCatalog(STANDARD_CATALOG_ID, standardCatalog, components, 'fs');
84
+ }
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Surface Renderer
3
+ *
4
+ * Renders A2UX surfaces to the DOM using registered Web Components.
5
+ */
6
+
7
+ import { html, render, TemplateResult } from 'lit';
8
+ import type { A2UXComponent, SurfaceStore, Surface } from '@freesail/core';
9
+ import { registry } from './utils/registry.js';
10
+
11
+ export interface SurfaceRendererOptions {
12
+ /** Target container element or selector */
13
+ container: HTMLElement | string;
14
+ /** Surface ID to render */
15
+ surfaceId: string;
16
+ /** Store instance */
17
+ store: SurfaceStore;
18
+ /** Enable debug mode */
19
+ debug?: boolean;
20
+ }
21
+
22
+ /**
23
+ * SurfaceRenderer dynamically renders A2UX surfaces to the DOM.
24
+ * It subscribes to store changes and re-renders automatically.
25
+ */
26
+ export class SurfaceRenderer {
27
+ private container: HTMLElement;
28
+ private surfaceId: string;
29
+ private store: SurfaceStore;
30
+ private debug: boolean;
31
+ private unsubscribe: (() => void) | null = null;
32
+
33
+ constructor(options: SurfaceRendererOptions) {
34
+ // Resolve container
35
+ if (typeof options.container === 'string') {
36
+ const el = document.querySelector(options.container);
37
+ if (!el) {
38
+ throw new Error(`Container not found: ${options.container}`);
39
+ }
40
+ this.container = el as HTMLElement;
41
+ } else {
42
+ this.container = options.container;
43
+ }
44
+
45
+ this.surfaceId = options.surfaceId;
46
+ this.store = options.store;
47
+ this.debug = options.debug ?? false;
48
+
49
+ // Subscribe to surface changes
50
+ this.unsubscribe = this.store.subscribeSurface(this.surfaceId, () => {
51
+ this.render();
52
+ });
53
+
54
+ // Initial render
55
+ this.render();
56
+ }
57
+
58
+ /**
59
+ * Render the surface to the container
60
+ */
61
+ render(): void {
62
+ const surface = this.store.getSurface(this.surfaceId);
63
+
64
+ if (!surface) {
65
+ this.log(`Surface not found: ${this.surfaceId}`);
66
+ render(html``, this.container);
67
+ return;
68
+ }
69
+
70
+ const rootComponent = this.store.getRootComponent(this.surfaceId);
71
+
72
+ if (!rootComponent) {
73
+ this.log(`No root component for surface: ${this.surfaceId}`);
74
+ render(html``, this.container);
75
+ return;
76
+ }
77
+
78
+ const template = this.renderComponent(rootComponent, surface);
79
+ render(template, this.container);
80
+
81
+ this.log(`Rendered surface: ${this.surfaceId}`);
82
+ }
83
+
84
+ /**
85
+ * Render a single component and its children
86
+ */
87
+ private renderComponent(component: A2UXComponent, surface: Surface): TemplateResult {
88
+ const tagName = registry.getTagNameForCatalog(surface.catalogId, component.component);
89
+
90
+ if (!tagName) {
91
+ this.log(`Component not registered: ${component.component}`);
92
+ return html`<!-- Unknown component: ${component.component} -->`;
93
+ }
94
+
95
+ // Build children templates
96
+ let childTemplates: TemplateResult[] = [];
97
+
98
+ if (component.children && Array.isArray(component.children)) {
99
+ childTemplates = component.children.map(childId => {
100
+ const child = this.store.getComponent(this.surfaceId, childId as string);
101
+ if (child) {
102
+ return this.renderComponent(child, surface);
103
+ }
104
+ return html`<!-- Missing child: ${childId} -->`;
105
+ });
106
+ }
107
+
108
+ // Extract component properties (exclude id, component, children)
109
+ const { id: _id, component: _component, children: _children, ...props } = component;
110
+
111
+ // Apply data model bindings
112
+ this.applyDataBindings(props, surface);
113
+
114
+ // Create element dynamically
115
+ return this.createDynamicElement(tagName, props, childTemplates, component.id);
116
+ }
117
+
118
+ /**
119
+ * Apply data model bindings to properties
120
+ */
121
+ private applyDataBindings(props: Record<string, unknown>, surface: Surface): void {
122
+ for (const [key, value] of Object.entries(props)) {
123
+ if (typeof value === 'string' && value.startsWith('$data:')) {
124
+ const path = value.slice(6); // Remove '$data:' prefix
125
+ props[key] = this.store.getDataValue(surface.surfaceId, path);
126
+ }
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Create a dynamic element with properties
132
+ */
133
+ private createDynamicElement(
134
+ tagName: string,
135
+ props: Record<string, unknown>,
136
+ children: TemplateResult[],
137
+ id: string
138
+ ): TemplateResult {
139
+ // Convert props to attribute format for Lit
140
+ const propEntries = Object.entries(props);
141
+
142
+ // Build props string for logging
143
+ const propsStr = propEntries
144
+ .map(([k, v]) => `${k}=${JSON.stringify(v)}`)
145
+ .join(' ');
146
+
147
+ this.log(`Creating <${tagName} ${propsStr}>`);
148
+
149
+ // Use unsafeStatic for dynamic tag names requires lit-html/static
150
+ // For simplicity, we'll use document.createElement approach
151
+ const el = document.createElement(tagName);
152
+ el.id = id;
153
+
154
+ // Set properties
155
+ for (const [key, value] of propEntries) {
156
+ if (value !== undefined && value !== null) {
157
+ (el as unknown as Record<string, unknown>)[key] = value;
158
+ }
159
+ }
160
+
161
+ // Render children into slot
162
+ if (children.length > 0) {
163
+ const childContainer = document.createElement('div');
164
+ render(html`${children}`, childContainer);
165
+ while (childContainer.firstChild) {
166
+ el.appendChild(childContainer.firstChild);
167
+ }
168
+ }
169
+
170
+ // Return as template
171
+ return html`${el}`;
172
+ }
173
+
174
+ /**
175
+ * Update a specific component without full re-render
176
+ */
177
+ updateComponent(_componentId: string): void {
178
+ // For now, just do a full re-render
179
+ // Future optimization: targeted DOM updates
180
+ this.render();
181
+ }
182
+
183
+ /**
184
+ * Destroy the renderer and cleanup
185
+ */
186
+ destroy(): void {
187
+ if (this.unsubscribe) {
188
+ this.unsubscribe();
189
+ this.unsubscribe = null;
190
+ }
191
+
192
+ // Clear container
193
+ this.container.innerHTML = '';
194
+ }
195
+
196
+ /**
197
+ * Debug logging
198
+ */
199
+ private log(message: string): void {
200
+ if (this.debug) {
201
+ console.log(`[SurfaceRenderer:${this.surfaceId}] ${message}`);
202
+ }
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Create a surface renderer instance
208
+ */
209
+ export function createSurfaceRenderer(options: SurfaceRendererOptions): SurfaceRenderer {
210
+ return new SurfaceRenderer(options);
211
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Freesail Lit UI Types
3
+ */
4
+
5
+ /**
6
+ * Property definition in a catalog
7
+ */
8
+ export interface PropertyDefinition {
9
+ type: 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object';
10
+ description?: string;
11
+ default?: unknown;
12
+ enum?: string[];
13
+ reflect?: boolean;
14
+ required?: boolean;
15
+ }
16
+
17
+ /**
18
+ * Component definition in a catalog
19
+ */
20
+ export interface ComponentDefinition {
21
+ description?: string;
22
+ properties?: Record<string, PropertyDefinition>;
23
+ required?: string[];
24
+ slots?: Record<string, { description?: string }>;
25
+ events?: Record<string, { description?: string }>;
26
+ }
27
+
28
+ /**
29
+ * Catalog definition structure
30
+ */
31
+ export interface CatalogDefinition {
32
+ id: string;
33
+ version: string;
34
+ name?: string;
35
+ description?: string;
36
+ components: Record<string, ComponentDefinition>;
37
+ }
38
+
39
+ /**
40
+ * Surface renderer configuration
41
+ */
42
+ export interface RendererConfig {
43
+ /** Target container element */
44
+ container: HTMLElement;
45
+ /** Catalog ID to use */
46
+ catalogId: string;
47
+ /** Enable debug mode */
48
+ debug?: boolean;
49
+ }