jsgui3-server 0.0.140 → 0.0.141
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.
- package/.github/agents/jsgui3-server.agent.md +699 -0
- package/.github/instructions/copilot.instructions.md +180 -0
- package/.playwright-mcp/page-2025-11-29T23-39-18-629Z.png +0 -0
- package/.playwright-mcp/page-2025-11-29T23-39-31-903Z.png +0 -0
- package/.playwright-mcp/page-2025-11-30T00-33-56-265Z.png +0 -0
- package/.playwright-mcp/page-2025-11-30T00-34-06-619Z.png +0 -0
- package/docs/agent-development-guide.md +108 -4
- package/docs/api-reference.md +116 -0
- package/docs/controls-development.md +127 -0
- package/docs/css/luxuryObsidianCss.js +1203 -0
- package/docs/css/obsidian-scrollbars.css +370 -0
- package/docs/diagrams/jsgui3-stack.svg +568 -0
- package/docs/guides/JSGUI3_UI_ARCHITECTURE_GUIDE.md +2527 -0
- package/docs/guides/OBSIDIAN_LUXURY_DESIGN_GUIDE.md +847 -0
- package/docs/jsgui3-vs-express-comparison.svg +542 -0
- package/docs/jsgui3-vs-nestjs-comparison.svg +550 -0
- package/docs/publishers-guide.md +76 -0
- package/docs/troubleshooting.md +51 -0
- package/examples/controls/15) window, observable SSE/README.md +125 -0
- package/examples/controls/15) window, observable SSE/check.js +144 -0
- package/examples/controls/15) window, observable SSE/client.js +395 -0
- package/examples/controls/15) window, observable SSE/server.js +111 -0
- package/http/responders/static/Static_Route_HTTP_Responder.js +16 -16
- package/module.js +7 -0
- package/package.json +7 -6
- package/port-utils.js +112 -0
- package/serve-factory.js +27 -5
- package/tests/README.md +40 -26
- package/tests/examples-controls.e2e.test.js +164 -0
- package/tests/observable-sse.test.js +363 -0
- package/tests/port-utils.test.js +114 -0
- package/tests/test-runner.js +13 -12
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "GitHub Copilot instructions for jsgui3-server development"
|
|
3
|
+
applyTo: "**"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# GitHub Copilot — jsgui3-server Playbook
|
|
7
|
+
|
|
8
|
+
## Primary References
|
|
9
|
+
|
|
10
|
+
- **`AGENTS.md`** — Root agent guidance with documentation index
|
|
11
|
+
- **`.github/agents/jsgui3-server.agent.md`** — Detailed patterns and anti-patterns
|
|
12
|
+
- **`docs/agent-development-guide.md`** — Broken functionality tracker (CHECK FIRST)
|
|
13
|
+
|
|
14
|
+
## Core Conventions
|
|
15
|
+
|
|
16
|
+
### Naming (Non-Negotiable)
|
|
17
|
+
```javascript
|
|
18
|
+
// Variables, functions → snake_case
|
|
19
|
+
const my_variable = 'value';
|
|
20
|
+
function process_data() {}
|
|
21
|
+
|
|
22
|
+
// Classes → PascalCase
|
|
23
|
+
class My_Control extends Active_HTML_Document {}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Control Lifecycle (The Canonical Pattern)
|
|
27
|
+
```javascript
|
|
28
|
+
class My_Control extends Active_HTML_Document {
|
|
29
|
+
constructor(spec = {}) {
|
|
30
|
+
spec.__type_name = spec.__type_name || 'my_control';
|
|
31
|
+
super(spec);
|
|
32
|
+
const { context } = this;
|
|
33
|
+
|
|
34
|
+
const compose = () => {
|
|
35
|
+
// Build UI here
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
if (!spec.el) { compose(); } // Conditional compose!
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
activate() {
|
|
42
|
+
if (!this.__active) { // Guard against double activation!
|
|
43
|
+
super.activate();
|
|
44
|
+
// Event bindings here
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
My_Control.css = `/* styles */`; // CSS as static property!
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Quick Commands
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Run example
|
|
56
|
+
cd examples/controls/001_demo_page && node server.js
|
|
57
|
+
|
|
58
|
+
# Tests
|
|
59
|
+
npm test # All
|
|
60
|
+
npm run test:bundlers # Bundler tests
|
|
61
|
+
npm run test:publishers # Publisher tests
|
|
62
|
+
|
|
63
|
+
# Debug mode
|
|
64
|
+
JSGUI_DEBUG=1 node server.js
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Before You Code
|
|
68
|
+
|
|
69
|
+
1. **Check known issues**: `docs/agent-development-guide.md` has the broken functionality tracker
|
|
70
|
+
2. **Find patterns**: Look at `examples/controls/` for reference implementations
|
|
71
|
+
3. **Update docs**: If you find something broken, document it immediately
|
|
72
|
+
|
|
73
|
+
## Anti-Patterns to Avoid
|
|
74
|
+
|
|
75
|
+
| ❌ Wrong | ✅ Right |
|
|
76
|
+
|----------|----------|
|
|
77
|
+
| `document.getElementById()` | `this.body.add.div({ id: 'x' })` |
|
|
78
|
+
| Always compose in constructor | `if (!spec.el) { compose(); }` |
|
|
79
|
+
| `activate()` without guard | `if (!this.__active) { super.activate(); }` |
|
|
80
|
+
| `server.start()` immediately | `server.on('ready', () => server.start())` |
|
|
81
|
+
| `new controls.h2({ text: 'Hi' })` | `h2.add('Hi')` — Use `.add()` for text! |
|
|
82
|
+
| `controls.button` (lowercase) | `controls.Button` (PascalCase for composites) |
|
|
83
|
+
|
|
84
|
+
## ⚠️ Critical: Text Content in Controls
|
|
85
|
+
|
|
86
|
+
**HTML elements** (div, span, h2) use `.add()` for text:
|
|
87
|
+
```javascript
|
|
88
|
+
const title = new controls.h2({ context });
|
|
89
|
+
title.add('My Title'); // ✅ Correct
|
|
90
|
+
|
|
91
|
+
// ❌ WRONG: text property won't render for HTML elements
|
|
92
|
+
const title = new controls.h2({ context, text: 'My Title' }); // Empty!
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Composite controls** (Button, Checkbox) DO support `text` property:
|
|
96
|
+
```javascript
|
|
97
|
+
const button = new controls.Button({ context, text: 'Click' }); // ✅ Works
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Control naming:**
|
|
101
|
+
- HTML elements: lowercase (`controls.div`, `controls.h2`)
|
|
102
|
+
- Composites: PascalCase (`controls.Button`, `controls.Window`)
|
|
103
|
+
|
|
104
|
+
**Set IDs via:** `control.dom.attributes.id = 'my-id'`
|
|
105
|
+
|
|
106
|
+
## Key Architectural Facts
|
|
107
|
+
|
|
108
|
+
- **Isomorphic**: Controls render server-side, activate client-side
|
|
109
|
+
- **ESBuild**: JavaScript bundling handled by ESBuild
|
|
110
|
+
- **CSS extraction**: CSS comes from static `.css` property on control classes
|
|
111
|
+
- **Publishers**: Different content types (webpage, function, css) have dedicated publishers
|
|
112
|
+
- **Context**: Central runtime object - extract with `const { context } = this;`
|
|
113
|
+
|
|
114
|
+
## When Creating Controls
|
|
115
|
+
|
|
116
|
+
1. Set `__type_name` before `super()`
|
|
117
|
+
2. Extract `context` after `super()`
|
|
118
|
+
3. Define `compose()` function for UI building
|
|
119
|
+
4. Use `if (!spec.el) { compose(); }` for conditional composition
|
|
120
|
+
5. Guard `activate()` with `if (!this.__active)`
|
|
121
|
+
6. Put CSS on static `.css` property
|
|
122
|
+
|
|
123
|
+
## Server Startup
|
|
124
|
+
|
|
125
|
+
```javascript
|
|
126
|
+
// Recommended: Server.serve() API
|
|
127
|
+
const Server = require('jsgui3-server');
|
|
128
|
+
Server.serve({
|
|
129
|
+
Ctrl: My_Control,
|
|
130
|
+
src_path_client_js: __dirname + '/client.js',
|
|
131
|
+
port: 8080
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Auto-port selection (avoids conflicts)
|
|
135
|
+
Server.serve({
|
|
136
|
+
Ctrl: My_Control,
|
|
137
|
+
src_path_client_js,
|
|
138
|
+
port: 'auto' // or port: 0
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Manual: Wait for 'ready' event
|
|
142
|
+
const server = new Server({ Ctrl, src_path_client_js });
|
|
143
|
+
server.on('ready', () => server.start(8080));
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Port Utilities
|
|
147
|
+
|
|
148
|
+
```javascript
|
|
149
|
+
const { get_free_port, is_port_available } = require('jsgui3-server');
|
|
150
|
+
|
|
151
|
+
const port = await get_free_port(); // Find any free port
|
|
152
|
+
const available = await is_port_available(8080); // Check specific port
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Data Binding
|
|
156
|
+
|
|
157
|
+
```javascript
|
|
158
|
+
const { Data_Object, field } = require('obext');
|
|
159
|
+
|
|
160
|
+
const model = new Data_Object({
|
|
161
|
+
count: field(0)
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
model.on('change.count', (e) => {
|
|
165
|
+
// React to changes
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
model.count++; // Triggers change event
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Documentation When Stuck
|
|
172
|
+
|
|
173
|
+
| Topic | File |
|
|
174
|
+
|-------|------|
|
|
175
|
+
| Architecture overview | `README.md` |
|
|
176
|
+
| Comprehensive API | `docs/comprehensive-documentation.md` |
|
|
177
|
+
| Control development | `docs/controls-development.md` |
|
|
178
|
+
| Publisher system | `docs/publishers-guide.md` |
|
|
179
|
+
| Troubleshooting | `docs/troubleshooting.md` |
|
|
180
|
+
| **Broken stuff** | `docs/agent-development-guide.md` |
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -106,6 +106,20 @@ jsgui3-server/
|
|
|
106
106
|
- [x] Data binding with observable models
|
|
107
107
|
- [x] CSS as static properties
|
|
108
108
|
|
|
109
|
+
#### Port Utilities (NEW)
|
|
110
|
+
- [x] `get_free_port()` - Find available port
|
|
111
|
+
- [x] `is_port_available()` - Check if port is free
|
|
112
|
+
- [x] `get_free_ports()` - Find multiple ports
|
|
113
|
+
- [x] `get_port_or_free()` - Prefer port or find free
|
|
114
|
+
- [x] `port: 'auto'` option in Server.serve()
|
|
115
|
+
- [x] Documented in api-reference.md
|
|
116
|
+
|
|
117
|
+
#### Observable Publisher (Documented)
|
|
118
|
+
- [x] HTTP_Observable_Publisher for SSE streaming
|
|
119
|
+
- [x] Integration with `fnl` observables
|
|
120
|
+
- [x] Working example in `examples/controls/15) window, observable SSE/`
|
|
121
|
+
- [x] Documented in publishers-guide.md
|
|
122
|
+
|
|
109
123
|
### 🚧 In Progress / Partially Complete
|
|
110
124
|
|
|
111
125
|
#### Website Publisher
|
|
@@ -192,6 +206,7 @@ jsgui3-server/
|
|
|
192
206
|
- Emit single clear "Server ready" message
|
|
193
207
|
- Ensure all publishers are ready before signaling
|
|
194
208
|
- Update CLI to wait for proper ready signal
|
|
209
|
+
- **Note**: Currently emits "ready" twice, causing port conflicts in tests
|
|
195
210
|
|
|
196
211
|
4. **Default Holding Page**
|
|
197
212
|
- Serve simple HTML when no content configured
|
|
@@ -199,22 +214,28 @@ jsgui3-server/
|
|
|
199
214
|
- Allow configuration override
|
|
200
215
|
|
|
201
216
|
### Medium Priority
|
|
202
|
-
5. **
|
|
217
|
+
5. **Observable API Integration**
|
|
218
|
+
- Detect observable returns in HTTP_Function_Publisher
|
|
219
|
+
- Auto-switch to SSE transport for observable endpoints
|
|
220
|
+
- Add client-side `context.subscribe()` for consuming streams
|
|
221
|
+
- Document observable publishing patterns
|
|
222
|
+
|
|
223
|
+
6. **File Manager Interface**
|
|
203
224
|
- Create admin UI for browsing/serving directories
|
|
204
225
|
- Integrate with file system resource
|
|
205
226
|
- Add upload/download capabilities
|
|
206
227
|
|
|
207
|
-
|
|
228
|
+
7. **CSS Bundling Cleanup**
|
|
208
229
|
- Remove legacy bundle paths
|
|
209
230
|
- Consolidate CSS extraction logic
|
|
210
231
|
- Ensure reliable CSS bundling
|
|
211
232
|
|
|
212
|
-
|
|
233
|
+
8. **Configuration File Support**
|
|
213
234
|
- Add `jsgui.config.js` support
|
|
214
235
|
- Implement `--config` CLI option
|
|
215
236
|
- Document configuration patterns
|
|
216
237
|
|
|
217
|
-
|
|
238
|
+
9. **Graceful Shutdown**
|
|
218
239
|
- Handle SIGINT/SIGTERM signals
|
|
219
240
|
- Clean up resources properly
|
|
220
241
|
- Print shutdown confirmation
|
|
@@ -232,6 +253,45 @@ jsgui3-server/
|
|
|
232
253
|
|
|
233
254
|
## Development Patterns
|
|
234
255
|
|
|
256
|
+
### Observable Pattern (Core to jsgui3-server)
|
|
257
|
+
|
|
258
|
+
The `fnl` module provides observables used throughout for async operations with intermediate results:
|
|
259
|
+
|
|
260
|
+
```javascript
|
|
261
|
+
const {obs} = require('fnl');
|
|
262
|
+
|
|
263
|
+
// Creating an observable
|
|
264
|
+
const my_async_operation = obs((next, complete, error) => {
|
|
265
|
+
// Emit progress/intermediate values
|
|
266
|
+
next({ stage: 'parsing', progress: 25 });
|
|
267
|
+
next({ stage: 'bundling', progress: 75 });
|
|
268
|
+
|
|
269
|
+
// Complete with final result (acts like promise resolution)
|
|
270
|
+
complete({ bundle: compiled_code });
|
|
271
|
+
|
|
272
|
+
// Or error (acts like promise rejection)
|
|
273
|
+
// error(new Error('compilation failed'));
|
|
274
|
+
|
|
275
|
+
// Return cleanup functions (optional)
|
|
276
|
+
return [() => cleanup_resources()];
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// Consuming an observable
|
|
280
|
+
my_async_operation.on('next', data => console.log('Progress:', data));
|
|
281
|
+
my_async_operation.on('complete', result => console.log('Done:', result));
|
|
282
|
+
my_async_operation.on('error', err => console.error('Failed:', err));
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
**Key locations using observables:**
|
|
286
|
+
- `publishers/http-observable-publisher.js` — SSE streaming to clients
|
|
287
|
+
- `resources/processors/bundlers/*.js` — All bundling operations
|
|
288
|
+
- `publishers/http-website-publisher.js` — Website build pipeline
|
|
289
|
+
|
|
290
|
+
**Observable vs Promise:**
|
|
291
|
+
- Use **observable** when operation has meaningful intermediate states (bundling progress, streaming data)
|
|
292
|
+
- Use **promise** for simple async one-shot operations
|
|
293
|
+
- The `http-function-publisher.js` already detects promises; extend to detect observables
|
|
294
|
+
|
|
235
295
|
### Control Creation Pattern
|
|
236
296
|
```javascript
|
|
237
297
|
class MyControl extends Active_HTML_Document {
|
|
@@ -264,6 +324,41 @@ class MyControl extends Active_HTML_Document {
|
|
|
264
324
|
MyControl.css = `/* CSS styles */`;
|
|
265
325
|
```
|
|
266
326
|
|
|
327
|
+
### ⚠️ Critical Pattern: Text Content in Controls
|
|
328
|
+
|
|
329
|
+
**This is a common pitfall!** HTML element controls and composite controls handle text differently:
|
|
330
|
+
|
|
331
|
+
```javascript
|
|
332
|
+
// ❌ WRONG: text property/assignment doesn't work for HTML elements
|
|
333
|
+
const title = new controls.h2({ context, text: 'My Title' }); // Won't render!
|
|
334
|
+
const div = new controls.div({ context });
|
|
335
|
+
div.text = 'Content'; // Won't render!
|
|
336
|
+
|
|
337
|
+
// ✅ CORRECT: Use .add() for HTML elements (div, span, h2, etc.)
|
|
338
|
+
const title = new controls.h2({ context });
|
|
339
|
+
title.add('My Title'); // ✅ Renders correctly
|
|
340
|
+
|
|
341
|
+
const div = new controls.div({ context });
|
|
342
|
+
div.add('Content'); // ✅ Renders correctly
|
|
343
|
+
|
|
344
|
+
// ✅ Composite controls (Button, Checkbox) DO support text property
|
|
345
|
+
const button = new controls.Button({ context, text: 'Click Me' }); // ✅ Works
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
**Why?** HTML elements are thin wrappers where text is a child node. Composite controls like `Button` have internal `compose_button()` that calls `this.add(this.text)`.
|
|
349
|
+
|
|
350
|
+
**Control Naming:**
|
|
351
|
+
| Type | Naming | Examples |
|
|
352
|
+
|------|--------|----------|
|
|
353
|
+
| HTML elements | lowercase | `controls.div`, `controls.span`, `controls.h2` |
|
|
354
|
+
| Composite controls | PascalCase | `controls.Button`, `controls.Window`, `controls.Checkbox` |
|
|
355
|
+
|
|
356
|
+
**Setting Element IDs:**
|
|
357
|
+
```javascript
|
|
358
|
+
const div = new controls.div({ context });
|
|
359
|
+
div.dom.attributes.id = 'my-id'; // ✅ Correct way to set ID
|
|
360
|
+
```
|
|
361
|
+
|
|
267
362
|
### Publisher Creation Pattern
|
|
268
363
|
```javascript
|
|
269
364
|
class CustomPublisher extends HTTP_Publisher {
|
|
@@ -321,9 +416,18 @@ class CustomResource extends Resource {
|
|
|
321
416
|
|
|
322
417
|
### Architecture Insights
|
|
323
418
|
- **Event-Driven Design**: Heavy use of Evented_Class and observable patterns
|
|
419
|
+
- **Observable-First Async**: `fnl` observables used for all multi-stage operations (bundling, compilation)
|
|
324
420
|
- **Resource Pool Pattern**: Centralized resource management
|
|
325
421
|
- **Publisher Abstraction**: Clean separation of content types
|
|
326
422
|
- **Context System**: Runtime environment management
|
|
423
|
+
- **SSE Infrastructure**: `HTTP_Observable_Publisher` already implements Server-Sent Events for streaming
|
|
424
|
+
|
|
425
|
+
### Observable Pattern (Strategic Differentiator)
|
|
426
|
+
The `obs()` factory from `fnl` is used throughout for operations with intermediate results:
|
|
427
|
+
- Bundlers emit progress updates during compilation
|
|
428
|
+
- Website publisher streams build status
|
|
429
|
+
- Can be exposed at API level for real-time client updates
|
|
430
|
+
- `HTTP_Observable_Publisher` already handles SSE transport
|
|
327
431
|
|
|
328
432
|
### Complexity Areas
|
|
329
433
|
- **Bundling System**: Multi-stage CSS/JS processing
|
package/docs/api-reference.md
CHANGED
|
@@ -99,6 +99,122 @@ server.use(middleware: Function): void
|
|
|
99
99
|
**Parameters:**
|
|
100
100
|
- `middleware` (Function): Express-style middleware function
|
|
101
101
|
|
|
102
|
+
## Port Utilities
|
|
103
|
+
|
|
104
|
+
The `port-utils` module provides automatic port selection to avoid conflicts.
|
|
105
|
+
|
|
106
|
+
### get_free_port(options)
|
|
107
|
+
|
|
108
|
+
Find a free port on the system.
|
|
109
|
+
|
|
110
|
+
**Signature:**
|
|
111
|
+
```javascript
|
|
112
|
+
get_free_port(options?: PortOptions): Promise<number>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Parameters:**
|
|
116
|
+
- `options.host` (string, optional): Host to check (default: '127.0.0.1')
|
|
117
|
+
- `options.startPort` (number, optional): Starting port to try (default: 0 = OS chooses)
|
|
118
|
+
- `options.endPort` (number, optional): Maximum port to try (default: 65535)
|
|
119
|
+
|
|
120
|
+
**Returns:** Promise resolving to an available port number
|
|
121
|
+
|
|
122
|
+
**Example:**
|
|
123
|
+
```javascript
|
|
124
|
+
const { get_free_port } = require('jsgui3-server');
|
|
125
|
+
|
|
126
|
+
const port = await get_free_port();
|
|
127
|
+
console.log(`Using port: ${port}`);
|
|
128
|
+
|
|
129
|
+
// Or with options
|
|
130
|
+
const port = await get_free_port({ startPort: 8080 });
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### is_port_available(port, host)
|
|
134
|
+
|
|
135
|
+
Check if a specific port is available.
|
|
136
|
+
|
|
137
|
+
**Signature:**
|
|
138
|
+
```javascript
|
|
139
|
+
is_port_available(port: number, host?: string): Promise<boolean>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Parameters:**
|
|
143
|
+
- `port` (number): Port to check
|
|
144
|
+
- `host` (string, optional): Host to check (default: '127.0.0.1')
|
|
145
|
+
|
|
146
|
+
**Returns:** Promise resolving to `true` if available, `false` if in use
|
|
147
|
+
|
|
148
|
+
**Example:**
|
|
149
|
+
```javascript
|
|
150
|
+
const { is_port_available } = require('jsgui3-server');
|
|
151
|
+
|
|
152
|
+
if (await is_port_available(8080)) {
|
|
153
|
+
console.log('Port 8080 is free');
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### get_free_ports(count, options)
|
|
158
|
+
|
|
159
|
+
Find multiple free ports.
|
|
160
|
+
|
|
161
|
+
**Signature:**
|
|
162
|
+
```javascript
|
|
163
|
+
get_free_ports(count: number, options?: PortOptions): Promise<number[]>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Parameters:**
|
|
167
|
+
- `count` (number): Number of ports to find
|
|
168
|
+
- `options` (PortOptions, optional): Same as `get_free_port`
|
|
169
|
+
|
|
170
|
+
**Returns:** Promise resolving to array of available port numbers
|
|
171
|
+
|
|
172
|
+
### get_port_or_free(preferred_port, host)
|
|
173
|
+
|
|
174
|
+
Get a specific port if available, otherwise find a free one.
|
|
175
|
+
|
|
176
|
+
**Signature:**
|
|
177
|
+
```javascript
|
|
178
|
+
get_port_or_free(preferred_port: number, host?: string): Promise<number>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Parameters:**
|
|
182
|
+
- `preferred_port` (number): Preferred port (0 = auto-select)
|
|
183
|
+
- `host` (string, optional): Host to check (default: '127.0.0.1')
|
|
184
|
+
|
|
185
|
+
**Returns:** Promise resolving to the preferred port if available, or a free port
|
|
186
|
+
|
|
187
|
+
**Example:**
|
|
188
|
+
```javascript
|
|
189
|
+
const { get_port_or_free } = require('jsgui3-server');
|
|
190
|
+
|
|
191
|
+
// Try 8080, fall back to any free port
|
|
192
|
+
const port = await get_port_or_free(8080);
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Auto-Port in Server.serve()
|
|
196
|
+
|
|
197
|
+
The `Server.serve()` API supports automatic port selection:
|
|
198
|
+
|
|
199
|
+
```javascript
|
|
200
|
+
const Server = require('jsgui3-server');
|
|
201
|
+
|
|
202
|
+
// Auto-select a free port
|
|
203
|
+
Server.serve({
|
|
204
|
+
Ctrl: MyControl,
|
|
205
|
+
port: 'auto' // or port: 0
|
|
206
|
+
}).then(server => {
|
|
207
|
+
console.log(`Running on port ${server.port}`);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Or use autoPort option
|
|
211
|
+
Server.serve({
|
|
212
|
+
Ctrl: MyControl,
|
|
213
|
+
autoPort: true,
|
|
214
|
+
port: 8080 // Will fall back to free port if 8080 is in use
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
102
218
|
## Control Classes
|
|
103
219
|
|
|
104
220
|
### Active_HTML_Document
|
|
@@ -155,6 +155,24 @@ const button = new controls.Button({
|
|
|
155
155
|
});
|
|
156
156
|
```
|
|
157
157
|
|
|
158
|
+
> **⚠️ Important: Text Content Patterns**
|
|
159
|
+
>
|
|
160
|
+
> Different controls handle text content differently:
|
|
161
|
+
>
|
|
162
|
+
> **Composite controls** (Button, Checkbox, etc.) accept `text` in spec:
|
|
163
|
+
> ```javascript
|
|
164
|
+
> const button = new controls.Button({ context, text: 'Click Me' }); // ✅ Works
|
|
165
|
+
> ```
|
|
166
|
+
>
|
|
167
|
+
> **HTML element controls** (div, span, h2, etc.) require `.add()` method:
|
|
168
|
+
> ```javascript
|
|
169
|
+
> const title = new controls.h2({ context });
|
|
170
|
+
> title.add('My Heading'); // ✅ Correct
|
|
171
|
+
> // title.text = 'My Heading'; // ❌ Won't render
|
|
172
|
+
> ```
|
|
173
|
+
>
|
|
174
|
+
> See [Text Content](#text-content-in-controls) section for details.
|
|
175
|
+
|
|
158
176
|
#### Text_Input
|
|
159
177
|
|
|
160
178
|
**Purpose:** Text input field control.
|
|
@@ -408,6 +426,115 @@ if (this.body.has_class('active')) {
|
|
|
408
426
|
}
|
|
409
427
|
```
|
|
410
428
|
|
|
429
|
+
## Text Content in Controls
|
|
430
|
+
|
|
431
|
+
### The `.add()` Method (For HTML Elements)
|
|
432
|
+
|
|
433
|
+
For basic HTML element controls (`div`, `span`, `h2`, `p`, etc.), use the `.add()` method to add text content:
|
|
434
|
+
|
|
435
|
+
```javascript
|
|
436
|
+
// ✅ Correct: Use .add() for HTML elements
|
|
437
|
+
const title = new controls.h2({ context });
|
|
438
|
+
title.add('My Page Title');
|
|
439
|
+
container.add(title);
|
|
440
|
+
|
|
441
|
+
const paragraph = new controls.div({ context, 'class': 'content' });
|
|
442
|
+
paragraph.add('This is the text content.');
|
|
443
|
+
container.add(paragraph);
|
|
444
|
+
|
|
445
|
+
const label = new controls.span({ context });
|
|
446
|
+
label.add('Label: ');
|
|
447
|
+
container.add(label);
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### The `text` Property (For Composite Controls)
|
|
451
|
+
|
|
452
|
+
Some composite controls like `Button`, `Checkbox`, and custom controls explicitly handle the `text` property:
|
|
453
|
+
|
|
454
|
+
```javascript
|
|
455
|
+
// ✅ Correct: Button handles text property
|
|
456
|
+
const button = new controls.Button({
|
|
457
|
+
context,
|
|
458
|
+
text: 'Submit Form'
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// ✅ Checkbox uses label.text
|
|
462
|
+
const checkbox = new controls.Checkbox({
|
|
463
|
+
context,
|
|
464
|
+
label: { text: 'Accept terms' }
|
|
465
|
+
});
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### Common Mistake: Text Not Rendering
|
|
469
|
+
|
|
470
|
+
```javascript
|
|
471
|
+
// ❌ WRONG: text property won't render for div/h2/span
|
|
472
|
+
const title = new controls.h2({
|
|
473
|
+
context,
|
|
474
|
+
text: 'This will NOT appear' // Won't render!
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
// ❌ WRONG: .text assignment won't render
|
|
478
|
+
const div = new controls.div({ context });
|
|
479
|
+
div.text = 'This also won\'t appear'; // Won't render!
|
|
480
|
+
|
|
481
|
+
// ✅ CORRECT: Use .add() method
|
|
482
|
+
const title = new controls.h2({ context });
|
|
483
|
+
title.add('This WILL appear'); // ✅ Renders correctly
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
### Why This Pattern Exists
|
|
487
|
+
|
|
488
|
+
JSGUI3 follows the DOM model where text is a child node, not a property. The `.add()` method creates a `Text_Node` child that renders properly both server-side and client-side.
|
|
489
|
+
|
|
490
|
+
Composite controls like `Button` have a `compose_button()` method that internally calls `this.add(this.text)`, which is why they work differently.
|
|
491
|
+
|
|
492
|
+
### Setting Element IDs
|
|
493
|
+
|
|
494
|
+
To set an element's ID attribute for client-side access:
|
|
495
|
+
|
|
496
|
+
```javascript
|
|
497
|
+
// ✅ Correct: Use dom.attributes.id
|
|
498
|
+
const status_div = new controls.div({ context });
|
|
499
|
+
status_div.dom.attributes.id = 'status-display';
|
|
500
|
+
status_div.add('Ready');
|
|
501
|
+
container.add(status_div);
|
|
502
|
+
|
|
503
|
+
// In activate(), access via getElementById:
|
|
504
|
+
activate() {
|
|
505
|
+
if (!this.__active) {
|
|
506
|
+
super.activate();
|
|
507
|
+
const status = document.getElementById('status-display');
|
|
508
|
+
status.textContent = 'Active'; // Update via DOM
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### Control Naming Conventions
|
|
514
|
+
|
|
515
|
+
JSGUI3 controls follow specific naming patterns:
|
|
516
|
+
|
|
517
|
+
| Type | Naming | Examples |
|
|
518
|
+
|------|--------|----------|
|
|
519
|
+
| HTML elements | lowercase | `controls.div`, `controls.span`, `controls.h2`, `controls.button` (native) |
|
|
520
|
+
| Composite controls | PascalCase | `controls.Button`, `controls.Window`, `controls.Checkbox`, `controls.Panel` |
|
|
521
|
+
| Custom controls | PascalCase | `controls.My_Custom_Control`, `controls.Dashboard` |
|
|
522
|
+
|
|
523
|
+
```javascript
|
|
524
|
+
// HTML elements (lowercase)
|
|
525
|
+
const div = new controls.div({ context });
|
|
526
|
+
const span = new controls.span({ context });
|
|
527
|
+
const h1 = new controls.h1({ context });
|
|
528
|
+
|
|
529
|
+
// Composite controls (PascalCase)
|
|
530
|
+
const button = new controls.Button({ context, text: 'Click' });
|
|
531
|
+
const window = new controls.Window({ context, title: 'My Window' });
|
|
532
|
+
const checkbox = new controls.Checkbox({ context });
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
> **Note:** `controls.button` (lowercase) creates a native `<button>` element without Button class features.
|
|
536
|
+
> `controls.Button` (PascalCase) creates the full Button control with text handling and styling.
|
|
537
|
+
|
|
411
538
|
### CSS Extraction and Bundling
|
|
412
539
|
|
|
413
540
|
CSS is automatically extracted from control classes and bundled by the server:
|