jsgui3-server 0.0.149 → 0.0.151
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/Mobile Developer.agent.md +89 -0
- package/.github/instructions/copilot.instructions.md +1 -0
- package/AGENTS.md +6 -0
- package/README.md +185 -0
- package/admin-ui/client.js +73 -43
- package/admin-ui/v1/admin_auth_service.js +197 -0
- package/admin-ui/v1/admin_user_store.js +71 -0
- package/admin-ui/v1/client.js +17 -0
- package/admin-ui/v1/controls/admin_shell.js +1399 -0
- package/admin-ui/v1/controls/group_box.js +84 -0
- package/admin-ui/v1/controls/stat_card.js +125 -0
- package/admin-ui/v1/server.js +658 -0
- package/admin-ui/v1/utils/formatters.js +68 -0
- package/docs/admin-extension-guide.md +345 -0
- package/docs/api-reference.md +383 -303
- package/docs/books/adaptive-control-improvements/01-control-candidate-matrix.md +122 -0
- package/docs/books/adaptive-control-improvements/02-tier-1-layout-playbooks.md +207 -0
- package/docs/books/adaptive-control-improvements/03-tier-2-navigation-form-overlay.md +140 -0
- package/docs/books/adaptive-control-improvements/04-cross-cutting-platform-functionality.md +141 -0
- package/docs/books/adaptive-control-improvements/05-styling-theming-density-upgrades.md +114 -0
- package/docs/books/adaptive-control-improvements/06-testing-quality-gates.md +97 -0
- package/docs/books/adaptive-control-improvements/07-delivery-roadmap-and-ownership.md +137 -0
- package/docs/books/adaptive-control-improvements/08-appendix-tier1-acceptance-and-pr-templates.md +261 -0
- package/docs/books/adaptive-control-improvements/README.md +66 -0
- package/docs/books/admin-ui-authentication/01-threat-model-and-goals.md +124 -0
- package/docs/books/admin-ui-authentication/02-session-model-and-token-model.md +75 -0
- package/docs/books/admin-ui-authentication/03-auth-middleware-patterns.md +77 -0
- package/docs/books/admin-ui-authentication/README.md +25 -0
- package/docs/books/creating-a-new-admin-ui/01-introduction-and-vision.md +130 -0
- package/docs/books/creating-a-new-admin-ui/02-architecture-and-data-flow.md +298 -0
- package/docs/books/creating-a-new-admin-ui/03-server-introspection.md +381 -0
- package/docs/books/creating-a-new-admin-ui/04-admin-module-adapter-layer.md +592 -0
- package/docs/books/creating-a-new-admin-ui/05-domain-controls-stat-cards-and-gauges.md +513 -0
- package/docs/books/creating-a-new-admin-ui/06-domain-controls-process-manager.md +544 -0
- package/docs/books/creating-a-new-admin-ui/07-domain-controls-resource-pool-inspector.md +493 -0
- package/docs/books/creating-a-new-admin-ui/08-domain-controls-route-table-and-api-explorer.md +586 -0
- package/docs/books/creating-a-new-admin-ui/09-domain-controls-log-viewer-and-activity-feed.md +490 -0
- package/docs/books/creating-a-new-admin-ui/10-domain-controls-build-status-and-bundle-inspector.md +526 -0
- package/docs/books/creating-a-new-admin-ui/11-domain-controls-configuration-panel.md +808 -0
- package/docs/books/creating-a-new-admin-ui/12-admin-shell-layout-sidebar-navigation.md +210 -0
- package/docs/books/creating-a-new-admin-ui/13-telemetry-integration.md +556 -0
- package/docs/books/creating-a-new-admin-ui/14-realtime-sse-observable-integration.md +485 -0
- package/docs/books/creating-a-new-admin-ui/15-styling-theming-aero-design-system.md +521 -0
- package/docs/books/creating-a-new-admin-ui/16-testing-and-quality-assurance.md +147 -0
- package/docs/books/creating-a-new-admin-ui/17-next-steps-process-resource-roadmap.md +356 -0
- package/docs/books/creating-a-new-admin-ui/README.md +68 -0
- package/docs/books/device-adaptive-composition/01-platform-feature-audit.md +177 -0
- package/docs/books/device-adaptive-composition/02-responsive-composition-model.md +187 -0
- package/docs/books/device-adaptive-composition/03-data-model-vs-view-model.md +231 -0
- package/docs/books/device-adaptive-composition/04-styling-theme-breakpoints.md +234 -0
- package/docs/books/device-adaptive-composition/05-showcase-app-multi-device-assessment.md +193 -0
- package/docs/books/device-adaptive-composition/06-implementation-patterns-and-apis.md +346 -0
- package/docs/books/device-adaptive-composition/07-testing-harness-and-quality-gates.md +265 -0
- package/docs/books/device-adaptive-composition/08-roadmap-and-adoption-plan.md +250 -0
- package/docs/books/device-adaptive-composition/README.md +47 -0
- package/docs/comparison-report-express-plex-cpanel.md +549 -0
- package/docs/comprehensive-documentation.md +220 -220
- package/docs/configuration-reference.md +227 -204
- package/docs/designs/server-admin-interface-aero.svg +611 -0
- package/docs/middleware-guide.md +236 -0
- package/docs/system-architecture.md +24 -18
- package/docs/troubleshooting.md +84 -53
- package/middleware/compression.js +217 -0
- package/middleware/index.js +15 -0
- package/module.js +19 -11
- package/package.json +1 -1
- package/serve-factory.js +29 -0
- package/server.js +280 -20
- package/tests/README.md +5 -0
- package/tests/admin-ui-jsgui-controls.test.js +581 -0
- package/tests/test-runner.js +1 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-071799b982906680f5fd699d.js +0 -40
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-07352945ad5c92654fcb8b65.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-138a601fadb6191ea314c6fd.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-171f6c381c2cadf2e9fa7087.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-1d973388156b84a04373fac9.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-20e117bc8a10d2cd16234bbe.js +0 -40
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-2b028a82b0e5efddba42425f.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-4518556cd5c7e059e82b22b8.js +0 -40
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-5bac1aa0f213902f718ed74f.js +0 -40
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-5f9996ac7822caf777d92f56.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-60a92c702e65fd9cf748e3ec.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-6164c1f8f738995c541895d2.js +0 -44
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-6718a85eb9e5aa782dd47a05.js +0 -45
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-69e280f14e37aee76a1d4675.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7570d1b030d44b111ed59c4c.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7798c9bbd55e510d5039f936.js +0 -42
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-78cd511ea1ef18ecb03d1be5.js +0 -40
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7d482e0b95bcb5e3c543118b.js +0 -43
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-80e9476d1127c55b40fdb36f.js +0 -40
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-810ced55d5320a3088a05b13.js +0 -40
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-8423565f1a40e329afc8c6cf.js +0 -40
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-900bef783b8cee36506ec282.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-a1a37aff6416fdad74040ddf.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-ad48d5e8eda40f175b4df090.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-aec5a2d963015528c9099462.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-af9d34e0f1722fab9e28c269.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-b818e4015e2f1fe86280b5ab.js +0 -41
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-bcb2541adc70b7aba61768c5.js +0 -44
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-bfe89d2c78ed44f95ed7dd73.js +0 -40
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-c06f04806a1e688e1187110c.js +0 -40
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-c3f3adf904f585afc544b96a.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-d45acb873e1d8e32d5e60f2e.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-db06f132533706f4a0163b8c.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-f660f40d78b135fc8560a862.js +0 -39
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-f9dee4ec18a96e09bee06bae.js +0 -39
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Chapter 5 — Showcase App Multi-Device Assessment
|
|
2
|
+
|
|
3
|
+
## About the Showcase App
|
|
4
|
+
|
|
5
|
+
The showcase app (`dev-examples/binding/showcase_app/`) is a comprehensive demo of jsgui3 controls featuring:
|
|
6
|
+
|
|
7
|
+
- A **navigation sidebar** with section links
|
|
8
|
+
- A **Theme Studio** panel with preset switching, CSS variable editing, and export/import
|
|
9
|
+
- A **main content area** with sections: status bars, button variants, filter chips, tabbed panels, accordions, drawers, code editors, and console panels
|
|
10
|
+
- **localStorage persistence** for theme preferences
|
|
11
|
+
|
|
12
|
+
The current layout uses a three-column CSS Grid shell: `240px` (nav) + `260px` (theme studio) + `1fr` (content). This chapter assesses how this app would behave across device categories and identifies concrete adaptation points.
|
|
13
|
+
|
|
14
|
+
## Current Layout Analysis
|
|
15
|
+
|
|
16
|
+
### What Works Well Already
|
|
17
|
+
|
|
18
|
+
The app benefits from several naturally responsive behaviors:
|
|
19
|
+
|
|
20
|
+
- **CSS Grid with `1fr`** — the content area is flexible and fills available space
|
|
21
|
+
- **Token-driven styling** — control sizes, padding, and typography are all token-based, not hardcoded
|
|
22
|
+
- **Section-based content architecture** — each demo section is independent and scrollable
|
|
23
|
+
- **Interactive controls work** — tab switching, accordion expansion, drawer toggle, console append all function correctly regardless of viewport
|
|
24
|
+
|
|
25
|
+
### Where It Breaks
|
|
26
|
+
|
|
27
|
+
At narrower widths, the three-column shell creates problems:
|
|
28
|
+
|
|
29
|
+
- Below ~800px, the nav + theme studio consume most of the width, leaving the content area cramped
|
|
30
|
+
- Below ~600px, the three fixed columns overflow horizontally
|
|
31
|
+
- The theme studio's color pickers and text inputs become too narrow to use comfortably
|
|
32
|
+
- The navigation sidebar provides no value on phone screens where vertical scrolling is the natural navigation pattern
|
|
33
|
+
|
|
34
|
+
## Phone Assessment (320–480px)
|
|
35
|
+
|
|
36
|
+
### Target Devices
|
|
37
|
+
|
|
38
|
+
iPhone SE (375×667), iPhone 14 (390×844), Android small (360×800)
|
|
39
|
+
|
|
40
|
+
### Structural Adaptation Needed
|
|
41
|
+
|
|
42
|
+
The three-column layout must collapse to a **single-column stack** with drawer-based access to navigation and theme tools:
|
|
43
|
+
|
|
44
|
+
| Desktop Component | Phone Adaptation |
|
|
45
|
+
|-------------------|-----------------|
|
|
46
|
+
| Navigation sidebar (240px) | Hamburger menu → Drawer overlay |
|
|
47
|
+
| Theme Studio panel (260px) | Settings icon → Drawer overlay or bottom sheet |
|
|
48
|
+
| Content area (1fr) | Full width, single column |
|
|
49
|
+
| Section headers | Become scroll-to anchors, possibly sticky |
|
|
50
|
+
|
|
51
|
+
### Specific Concerns
|
|
52
|
+
|
|
53
|
+
1. **Touch targets** — Button groups (icon buttons, split buttons) need minimum 44×44px hit areas. The current icon buttons may be too small for comfortable thumb interaction.
|
|
54
|
+
|
|
55
|
+
2. **Filter chips** — The Cluster layout wraps naturally, which is good. But on narrow screens, a horizontally-scrollable strip might be better than wrapping to 3–4 rows.
|
|
56
|
+
|
|
57
|
+
3. **Tabbed panels** — Horizontal tabs work on phone if there are ≤4 tabs. More tabs should overflow-scroll horizontally rather than wrapping to multiple lines.
|
|
58
|
+
|
|
59
|
+
4. **Code editor** — Horizontal scrolling for code is acceptable on phone (developers expect it), but the container should be full-bleed to maximize line length.
|
|
60
|
+
|
|
61
|
+
5. **Theme Studio** — The full theme editor is a power-user feature that may not need to be accessible on phone. A simplified "preset only" mode (just the preset buttons) is more appropriate.
|
|
62
|
+
|
|
63
|
+
### Recommended Composition
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
Phone layout:
|
|
67
|
+
┌──────────────────┐
|
|
68
|
+
│ [≡] App Title [⚙]│ ← hamburger (nav) + gear (theme presets)
|
|
69
|
+
├──────────────────┤
|
|
70
|
+
│ │
|
|
71
|
+
│ Content area │ ← full width, vertical scroll
|
|
72
|
+
│ (all sections │
|
|
73
|
+
│ stacked) │
|
|
74
|
+
│ │
|
|
75
|
+
└──────────────────┘
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Tablet Portrait Assessment (768×1024)
|
|
79
|
+
|
|
80
|
+
### Target Devices
|
|
81
|
+
|
|
82
|
+
iPad (768×1024), iPad Air (820×1180), Surface Go (800×1280)
|
|
83
|
+
|
|
84
|
+
### Structural Adaptation Needed
|
|
85
|
+
|
|
86
|
+
Two columns work well at this width. The question is which two columns:
|
|
87
|
+
|
|
88
|
+
**Option A: Content + Theme Studio**
|
|
89
|
+
|
|
90
|
+
Drop the nav sidebar. Use a collapsible top bar or hamburger for navigation. Keep the theme studio visible because it's the interactive focus of a showcase app.
|
|
91
|
+
|
|
92
|
+
**Option B: Nav + Content**
|
|
93
|
+
|
|
94
|
+
Drop the theme studio to a slide-over panel. Keep the nav visible for section discovery. This prioritizes browse-ability.
|
|
95
|
+
|
|
96
|
+
For a showcase app, **Option A** is stronger — the theme studio is the unique value proposition. Section navigation can use a compact horizontal pill bar or a dropdown.
|
|
97
|
+
|
|
98
|
+
### Specific Concerns
|
|
99
|
+
|
|
100
|
+
1. **Three columns at 768px** — the current 240 + 260 + remaining = 268px for content. That's too cramped for code editors and data tables. Two columns are necessary.
|
|
101
|
+
|
|
102
|
+
2. **Spacing** — Desktop spacing tokens (`--j-space-5: 24px`) may feel too generous and waste tablet real estate. A `cozy` density mode with slightly tighter spacing would help.
|
|
103
|
+
|
|
104
|
+
3. **Split pane** — If both content and theme studio are visible, a Split_Pane with a drag handle would let users choose the balance. The existing Split_Pane primitive supports this.
|
|
105
|
+
|
|
106
|
+
### Recommended Composition
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
Tablet portrait layout:
|
|
110
|
+
┌─────────────────────────────┐
|
|
111
|
+
│ [≡] App Title [Nav pills]│ ← hamburger for full nav, pills for top sections
|
|
112
|
+
├──────────────┬──────────────┤
|
|
113
|
+
│ │ │
|
|
114
|
+
│ Content │ Theme │
|
|
115
|
+
│ area │ Studio │
|
|
116
|
+
│ │ │
|
|
117
|
+
│ │ │
|
|
118
|
+
└──────────────┴──────────────┘
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Tablet Landscape Assessment (1024×768)
|
|
122
|
+
|
|
123
|
+
### Target Devices
|
|
124
|
+
|
|
125
|
+
iPad landscape (1024×768), Surface Go landscape (1280×800)
|
|
126
|
+
|
|
127
|
+
### Structural Adaptation
|
|
128
|
+
|
|
129
|
+
At 1024px, the full three-column layout becomes viable, though with tighter proportions than desktop:
|
|
130
|
+
|
|
131
|
+
- Nav: 180px (narrower than desktop's 240px)
|
|
132
|
+
- Theme Studio: 220px (narrower than desktop's 260px)
|
|
133
|
+
- Content: 624px (adequate for most controls)
|
|
134
|
+
|
|
135
|
+
Alternatively, keep the two-column layout from tablet portrait and add the nav as a collapsible sidebar, deferring to the Drawer pattern when space gets tight.
|
|
136
|
+
|
|
137
|
+
### Specific Concerns
|
|
138
|
+
|
|
139
|
+
1. **Vertical space** — At 768px height, sticky panels should be limited. The theme studio shouldn't use `position: sticky` with a long panel because it would consume most of the viewport height.
|
|
140
|
+
|
|
141
|
+
2. **Keyboard navigation** — Tablet landscape is often used with a keyboard (Surface, iPad with Magic Keyboard). The full keyboard navigation paths should work, including tab-to-section and arrow-key-within-components.
|
|
142
|
+
|
|
143
|
+
## Desktop Assessment (1280–1920px+)
|
|
144
|
+
|
|
145
|
+
### What Works Well
|
|
146
|
+
|
|
147
|
+
The current layout is designed for desktop and works well:
|
|
148
|
+
|
|
149
|
+
- Three-column shell provides clear information hierarchy
|
|
150
|
+
- Generous spacing aids readability
|
|
151
|
+
- Theme studio is always accessible for experimentation
|
|
152
|
+
- Navigation sidebar gives quick section access
|
|
153
|
+
|
|
154
|
+
### Remaining Concerns
|
|
155
|
+
|
|
156
|
+
1. **Max content width** — On very wide screens (1920px+), text lines in the content area become too long for comfortable reading. Adding a `max-width: 900px` on text-heavy content or using the `Center` primitive would help.
|
|
157
|
+
|
|
158
|
+
2. **Pinnable panels** — On wide screens, users might want to unpin the theme studio or nav to gain more content space. A pin/unpin toggle on panel headers would add flexibility.
|
|
159
|
+
|
|
160
|
+
3. **Section subnavigation** — As the control catalog grows, flat scrolling through many sections becomes tedious. Section-level dropdowns or an accordion sidebar structure would improve discoverability.
|
|
161
|
+
|
|
162
|
+
## Cross-Device Summary Table
|
|
163
|
+
|
|
164
|
+
| Feature | Phone | Tablet Portrait | Tablet Landscape | Desktop |
|
|
165
|
+
|---------|-------|----------------|------------------|---------|
|
|
166
|
+
| Columns | 1 | 2 | 2–3 | 3 |
|
|
167
|
+
| Navigation | Drawer | Pills / dropdown | Sidebar or collapsible | Sidebar |
|
|
168
|
+
| Theme Studio | Presets only | Side panel | Side panel | Side panel |
|
|
169
|
+
| Density | Comfortable | Cozy | Cozy | Default |
|
|
170
|
+
| Touch targets | 44px+ | 44px+ | 36px+ | 32px+ |
|
|
171
|
+
| Content max-width | Full bleed | ~500px | ~600px | ~900px |
|
|
172
|
+
|
|
173
|
+
## Adaptation Points in the Current Code
|
|
174
|
+
|
|
175
|
+
Looking at the showcase app's `compose_ui()` method, the key adaptation opportunities are:
|
|
176
|
+
|
|
177
|
+
1. **Shell grid template** — currently hardcoded as `240px 260px 1fr`. Should be derived from `layout_mode`.
|
|
178
|
+
|
|
179
|
+
2. **Navigation panel** — currently composed inline. Should use `compose_adaptive` to switch between inline sidebar, pill bar, and drawer.
|
|
180
|
+
|
|
181
|
+
3. **Theme studio** — currently composed inline. Should collapse to presets-only on phone, slide-over on tablet.
|
|
182
|
+
|
|
183
|
+
4. **Section card widths** — currently use flexible styling. Could set `max-width` per layout mode to prevent overly wide content.
|
|
184
|
+
|
|
185
|
+
5. **Token density** — currently static. Should respond to `density_mode` attribute on root.
|
|
186
|
+
|
|
187
|
+
## Browser Window Variability
|
|
188
|
+
|
|
189
|
+
A final consideration: users frequently resize browser windows, especially when using split-screen on desktop. The layout should adapt fluidly between 600px and 1400px without sudden jumps or broken states.
|
|
190
|
+
|
|
191
|
+
The recommended approach uses CSS for fluid adaptation within a mode (e.g., flexible gaps and content wrapping) and JavaScript composition changes at mode boundaries (e.g., switching from two-column to drawer). This dual strategy avoids the flicker of constant recomposition while still handling structural changes cleanly.
|
|
192
|
+
|
|
193
|
+
**Next:** [Chapter 6](06-implementation-patterns-and-apis.md) provides the concrete patterns and API designs needed to implement these adaptations.
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
# Chapter 6 — Implementation Patterns and APIs
|
|
2
|
+
|
|
3
|
+
## Design Goal
|
|
4
|
+
|
|
5
|
+
Make high-level app code easy to write. A developer building a dashboard shouldn't need to understand viewport detection, mode resolution, or responsive token cascades. They should express adaptive intent in a few lines and get correct behavior across devices.
|
|
6
|
+
|
|
7
|
+
Complexity belongs in the mid-level platform code — the services, helpers, and mixins that resolve intent into concrete outcomes. And the low-level foundations in lang-tools and html-core handle eventing, model synchronization, and rendering.
|
|
8
|
+
|
|
9
|
+
This chapter defines the concrete patterns and APIs that make this work.
|
|
10
|
+
|
|
11
|
+
## Pattern 1: View Environment Service
|
|
12
|
+
|
|
13
|
+
### What It Does
|
|
14
|
+
|
|
15
|
+
A lightweight service that observes the runtime environment and publishes normalized state. Every control and view model can read from it. It runs client-side only (on the server, it returns safe defaults).
|
|
16
|
+
|
|
17
|
+
### API Design
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
const View_Environment = require('jsgui3-html/utils/view_environment');
|
|
21
|
+
|
|
22
|
+
// Created once per page context
|
|
23
|
+
const env = new View_Environment({
|
|
24
|
+
// Optional: override default breakpoints
|
|
25
|
+
breakpoints: {
|
|
26
|
+
phone_max: 599,
|
|
27
|
+
tablet_max: 1023
|
|
28
|
+
},
|
|
29
|
+
// Optional: default for SSR
|
|
30
|
+
defaults: {
|
|
31
|
+
layout_mode: 'desktop',
|
|
32
|
+
density_mode: 'cozy',
|
|
33
|
+
interaction_mode: 'pointer',
|
|
34
|
+
motion_mode: 'normal'
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Published State
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
env.state
|
|
43
|
+
// → {
|
|
44
|
+
// viewport: { width: 768, height: 1024, orientation: 'portrait' },
|
|
45
|
+
// layout_mode: 'tablet',
|
|
46
|
+
// density_mode: 'cozy',
|
|
47
|
+
// interaction_mode: 'touch',
|
|
48
|
+
// motion_mode: 'normal'
|
|
49
|
+
// }
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Change Events
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
env.on('change', (new_state, old_state) => {
|
|
56
|
+
// Fires when any property changes
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
env.on('change:layout_mode', (new_mode, old_mode) => {
|
|
60
|
+
// Fires only when layout_mode changes
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Integration with Page Context
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
// In the app's initialization:
|
|
68
|
+
const ctx = new jsgui.Page_Context();
|
|
69
|
+
ctx.view_environment = new View_Environment();
|
|
70
|
+
|
|
71
|
+
// In any control:
|
|
72
|
+
const mode = this.context.view_environment.state.layout_mode;
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Implementation Notes
|
|
76
|
+
|
|
77
|
+
The service should:
|
|
78
|
+
- Listen to `window.resize` (debounced) and `matchMedia` for reduced-motion and pointer queries
|
|
79
|
+
- Derive `layout_mode` from viewport width using configurable breakpoints
|
|
80
|
+
- Derive `interaction_mode` from `matchMedia('(pointer: coarse)')` — coarse = touch, fine = pointer, both = hybrid
|
|
81
|
+
- Derive `motion_mode` from `matchMedia('(prefers-reduced-motion: reduce)')`
|
|
82
|
+
- Set `data-layout-mode`, `data-density-mode`, and `data-interaction-mode` attributes on `document.documentElement`
|
|
83
|
+
- On the server (`typeof window === 'undefined'`), return static defaults
|
|
84
|
+
|
|
85
|
+
### Server-Side Behavior
|
|
86
|
+
|
|
87
|
+
```js
|
|
88
|
+
// On the server, the environment returns defaults:
|
|
89
|
+
const env = new View_Environment();
|
|
90
|
+
env.state.layout_mode // → 'desktop' (safe default)
|
|
91
|
+
env.state.viewport // → { width: 1280, height: 900, orientation: 'landscape' }
|
|
92
|
+
|
|
93
|
+
// Controls can use this for SSR composition:
|
|
94
|
+
if (env.state.layout_mode === 'phone') {
|
|
95
|
+
// Only reached if server received a mobile hint
|
|
96
|
+
this.compose_phone_shell();
|
|
97
|
+
} else {
|
|
98
|
+
this.compose_desktop_shell();
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Pattern 2: Adaptive Composition Helper
|
|
103
|
+
|
|
104
|
+
### The Problem It Solves
|
|
105
|
+
|
|
106
|
+
Without a helper, every adaptive control writes the same boilerplate:
|
|
107
|
+
|
|
108
|
+
```js
|
|
109
|
+
// Repeated in every control:
|
|
110
|
+
const mode = this.context.view_environment.state.layout_mode;
|
|
111
|
+
if (mode === 'phone') this.compose_phone();
|
|
112
|
+
else if (mode === 'tablet') this.compose_tablet();
|
|
113
|
+
else this.compose_desktop();
|
|
114
|
+
|
|
115
|
+
// And then separately, resize handling:
|
|
116
|
+
this.context.view_environment.on('change:layout_mode', (new_mode) => {
|
|
117
|
+
this.clear();
|
|
118
|
+
if (new_mode === 'phone') this.compose_phone();
|
|
119
|
+
else if (new_mode === 'tablet') this.compose_tablet();
|
|
120
|
+
else this.compose_desktop();
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### The Helper API
|
|
125
|
+
|
|
126
|
+
```js
|
|
127
|
+
const { compose_adaptive } = require('jsgui3-html/utils/adaptive');
|
|
128
|
+
|
|
129
|
+
class My_Dashboard extends Data_Model_View_Model_Control {
|
|
130
|
+
constructor(spec) {
|
|
131
|
+
super(spec);
|
|
132
|
+
|
|
133
|
+
compose_adaptive(this, {
|
|
134
|
+
phone: () => this.compose_phone_shell(),
|
|
135
|
+
tablet: () => this.compose_tablet_shell(),
|
|
136
|
+
desktop: () => this.compose_desktop_shell()
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
compose_phone_shell() {
|
|
141
|
+
const { context } = this;
|
|
142
|
+
this.header = new Stack({ context, direction: 'row', align: 'center' });
|
|
143
|
+
this.header.add(new Icon_Button({ context, icon: '≡', action: () => this.nav_drawer.open() }));
|
|
144
|
+
this.header.add(new Control({ context, text: 'Dashboard' }));
|
|
145
|
+
this.add(this.header);
|
|
146
|
+
|
|
147
|
+
this.nav_drawer = new Drawer({ context, position: 'left' });
|
|
148
|
+
this.add(this.nav_drawer);
|
|
149
|
+
|
|
150
|
+
this.content = new Stack({ context, direction: 'column' });
|
|
151
|
+
this.add(this.content);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
compose_tablet_shell() {
|
|
155
|
+
const { context } = this;
|
|
156
|
+
this.shell = new Grid_Gap({ context, columns: '1fr 260px', gap: 12 });
|
|
157
|
+
this.content = new Stack({ context, direction: 'column' });
|
|
158
|
+
this.tools = new Stack({ context, direction: 'column' });
|
|
159
|
+
this.shell.add(this.content);
|
|
160
|
+
this.shell.add(this.tools);
|
|
161
|
+
this.add(this.shell);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
compose_desktop_shell() {
|
|
165
|
+
const { context } = this;
|
|
166
|
+
this.shell = new Grid_Gap({ context, columns: '240px 1fr 260px', gap: 16 });
|
|
167
|
+
this.nav = new Stack({ context, direction: 'column' });
|
|
168
|
+
this.content = new Stack({ context, direction: 'column' });
|
|
169
|
+
this.tools = new Stack({ context, direction: 'column' });
|
|
170
|
+
this.shell.add(this.nav);
|
|
171
|
+
this.shell.add(this.content);
|
|
172
|
+
this.shell.add(this.tools);
|
|
173
|
+
this.add(this.shell);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### What `compose_adaptive` Does Internally
|
|
179
|
+
|
|
180
|
+
1. Reads `this.context.view_environment.state.layout_mode`
|
|
181
|
+
2. Calls the matching composition function
|
|
182
|
+
3. Registers a listener for `change:layout_mode`
|
|
183
|
+
4. On mode change: clears the control's children, calls the new composition function
|
|
184
|
+
5. Returns a cleanup function for the listener (used in `destroy()`)
|
|
185
|
+
|
|
186
|
+
### Fallback Behavior
|
|
187
|
+
|
|
188
|
+
If no exact match exists, the helper falls back intelligently:
|
|
189
|
+
|
|
190
|
+
```js
|
|
191
|
+
compose_adaptive(this, {
|
|
192
|
+
phone: () => this.compose_narrow(),
|
|
193
|
+
desktop: () => this.compose_wide()
|
|
194
|
+
// No explicit tablet branch
|
|
195
|
+
});
|
|
196
|
+
// tablet falls back to → phone (nearest smaller mode)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The fallback order is: exact match → next smaller mode → `desktop` default.
|
|
200
|
+
|
|
201
|
+
## Pattern 3: Responsive Param Resolver
|
|
202
|
+
|
|
203
|
+
### The Problem
|
|
204
|
+
|
|
205
|
+
Controls accept configuration parameters (`size`, `columns`, `density`). Currently, these are static — the same value is used regardless of screen size. Developers end up writing per-mode overrides manually.
|
|
206
|
+
|
|
207
|
+
### The Solution
|
|
208
|
+
|
|
209
|
+
Extend `theme_params.js` to accept mode-branched parameter objects:
|
|
210
|
+
|
|
211
|
+
```js
|
|
212
|
+
class My_Table extends Data_Model_View_Model_Control {
|
|
213
|
+
constructor(spec) {
|
|
214
|
+
super(spec);
|
|
215
|
+
|
|
216
|
+
// Responsive params — resolved automatically by layout_mode
|
|
217
|
+
this.resolve_responsive_params({
|
|
218
|
+
default: { columns: 8, row_height: 36, show_header: true },
|
|
219
|
+
phone: { columns: 2, row_height: 44, show_header: false },
|
|
220
|
+
tablet: { columns: 5, row_height: 40, show_header: true }
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// this.params.columns → 8 (on desktop), 2 (on phone), etc.
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
The resolver:
|
|
229
|
+
1. Reads `context.view_environment.state.layout_mode`
|
|
230
|
+
2. Merges the matching branch over the `default` branch
|
|
231
|
+
3. Exposes the merged result as `this.params`
|
|
232
|
+
4. Re-resolves and emits change events when mode changes
|
|
233
|
+
|
|
234
|
+
## Pattern 4: Container-Aware Adaptation
|
|
235
|
+
|
|
236
|
+
### Why Viewport Isn't Always Enough
|
|
237
|
+
|
|
238
|
+
A control embedded in a narrow sidebar behaves like a "phone" control even on a desktop viewport. Container queries (CSS `@container`) address this for styling, but composition decisions need JavaScript-level container awareness too.
|
|
239
|
+
|
|
240
|
+
### Proposed Utility
|
|
241
|
+
|
|
242
|
+
```js
|
|
243
|
+
const { container_mode } = require('jsgui3-html/utils/adaptive');
|
|
244
|
+
|
|
245
|
+
// In a control's activate():
|
|
246
|
+
activate() {
|
|
247
|
+
super.activate();
|
|
248
|
+
|
|
249
|
+
// Observe own container width, map to local mode
|
|
250
|
+
container_mode(this, {
|
|
251
|
+
narrow: { max_width: 400 },
|
|
252
|
+
medium: { max_width: 800 },
|
|
253
|
+
wide: { min_width: 801 }
|
|
254
|
+
}, (mode) => {
|
|
255
|
+
this.view.data.model.set('container_mode', mode);
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
This uses `ResizeObserver` on the control's parent element to track available width, independent of viewport size.
|
|
261
|
+
|
|
262
|
+
### When to Use Container vs Viewport Mode
|
|
263
|
+
|
|
264
|
+
| Concern | Use Viewport | Use Container |
|
|
265
|
+
|---------|-------------|---------------|
|
|
266
|
+
| App shell structure | ✅ | |
|
|
267
|
+
| Navigation morphing | ✅ | |
|
|
268
|
+
| Control internal layout | | ✅ |
|
|
269
|
+
| Embedded widget adaptation | | ✅ |
|
|
270
|
+
| Touch target sizing | ✅ | |
|
|
271
|
+
| Column count in a table | | ✅ |
|
|
272
|
+
|
|
273
|
+
## Pattern 5: Declarative Region Morphing
|
|
274
|
+
|
|
275
|
+
### The Concept
|
|
276
|
+
|
|
277
|
+
Some adaptive changes follow repeatable patterns: a sidebar becomes a drawer, a multi-panel becomes tabs, a toolbar becomes an overflow menu. Rather than writing this logic from scratch each time, the platform can provide declarative region morphing:
|
|
278
|
+
|
|
279
|
+
```js
|
|
280
|
+
// Declarative: specify what morphs into what
|
|
281
|
+
this.nav_region = adaptive_region(this, 'nav', {
|
|
282
|
+
desktop: { as: Stack, options: { direction: 'column', gap: 8 } },
|
|
283
|
+
tablet: { as: Drawer, options: { position: 'left', breakpoint: 768 } },
|
|
284
|
+
phone: { as: Drawer, options: { position: 'left', overlay_mode: true } }
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// The region control changes type based on layout_mode
|
|
288
|
+
// Content added to the region transfers across morphs
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
This is a more advanced pattern that builds on `compose_adaptive`. It's best suited for standard layout regions (nav, tools, inspector) where the morph pattern is well-defined.
|
|
292
|
+
|
|
293
|
+
## Pattern 6: Composition-Safe Persistence
|
|
294
|
+
|
|
295
|
+
### What to Persist
|
|
296
|
+
|
|
297
|
+
Not all state should survive across sessions. A clear rule:
|
|
298
|
+
|
|
299
|
+
```
|
|
300
|
+
Persist:
|
|
301
|
+
✅ User's chosen theme name ('vs-dark')
|
|
302
|
+
✅ User's density preference ('compact')
|
|
303
|
+
✅ Panel pinning preference (pinned/unpinned)
|
|
304
|
+
✅ Custom token overrides ({'--admin-font-size': '12px'})
|
|
305
|
+
|
|
306
|
+
Don't persist:
|
|
307
|
+
❌ Current layout_mode (should always reflect current viewport)
|
|
308
|
+
❌ Viewport dimensions
|
|
309
|
+
❌ Container widths
|
|
310
|
+
❌ Transient interaction state (hover, focus, scroll position)
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Persistence Integration
|
|
314
|
+
|
|
315
|
+
```js
|
|
316
|
+
// Save: extract only stable preferences
|
|
317
|
+
const prefs = {
|
|
318
|
+
theme: this.data.model.get('theme_name'),
|
|
319
|
+
density: this.data.model.get('density_preference'),
|
|
320
|
+
overrides: this.data.model.get('token_overrides')
|
|
321
|
+
};
|
|
322
|
+
localStorage.setItem('app_prefs', JSON.stringify(prefs));
|
|
323
|
+
|
|
324
|
+
// Restore: apply preferences, let environment service handle the rest
|
|
325
|
+
const prefs = JSON.parse(localStorage.getItem('app_prefs'));
|
|
326
|
+
this.data.model.set('theme_name', prefs.theme);
|
|
327
|
+
this.data.model.set('density_preference', prefs.density);
|
|
328
|
+
// layout_mode is NOT restored — it's derived from the current viewport
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## Complexity Budget
|
|
332
|
+
|
|
333
|
+
Each pattern has a different cost for app developers vs platform maintainers:
|
|
334
|
+
|
|
335
|
+
| Pattern | App Developer Effort | Platform Effort | Priority |
|
|
336
|
+
|---------|---------------------|-----------------|----------|
|
|
337
|
+
| View Environment Service | Near zero (read from context) | Medium | P0 |
|
|
338
|
+
| compose_adaptive() | Low (declare branches) | Medium | P0 |
|
|
339
|
+
| Responsive params | Low (declare param branches) | Medium | P1 |
|
|
340
|
+
| Container-aware mode | Medium (choose thresholds) | High | P2 |
|
|
341
|
+
| Region morphing | Low (declare morph map) | High | P2 |
|
|
342
|
+
| Persistence integration | Low (use conventions) | Low | P1 |
|
|
343
|
+
|
|
344
|
+
P0 patterns should ship first — they provide the most value with reasonable implementation cost.
|
|
345
|
+
|
|
346
|
+
**Next:** [Chapter 7](07-testing-harness-and-quality-gates.md) explains how to test responsive behavior systematically across viewport sizes.
|