kopular 0.9.0 → 0.10.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.
package/LLM.md CHANGED
@@ -124,6 +124,22 @@ w.Bump(); // re-render
124
124
  reconciliation. Compose independent components into stable slots (see `Router`'s own
125
125
  pattern of keeping page instances alive) to avoid this rather than nesting components
126
126
  that both re-render.
127
+ - `RenderError(string message)`: `virtual`, called by `Mount()`/`Update()` (via a private
128
+ `SafeRender()` wrapper) when `Render()` throws, instead of letting the exception
129
+ propagate uncaught and crash whatever triggered the render (a click handler, a `Router`
130
+ navigation). The base implementation just `throw`s `message` again — **purely additive,
131
+ opt-in error recovery**; a `Component` that never overrides `RenderError` behaves
132
+ exactly as before this existed. Override it to show a fallback UI instead:
133
+ ```ks
134
+ protected override Element RenderError(string message) {
135
+ Element el = document.createElement("div");
136
+ el.textContent = "Something went wrong: " + message;
137
+ return el;
138
+ }
139
+ ```
140
+ A later successful `Update()` (e.g. from a "Retry" button in that fallback calling back
141
+ into the component) renders normally again — there's no separate "broken" state to
142
+ reset, `SafeRender()` just tries `Render()` again like any other `Update()`.
127
143
 
128
144
  ## `Router` (`router.ks`)
129
145
 
package/README.md CHANGED
@@ -15,7 +15,9 @@ Generating Kopular code with an AI coding assistant? Point it at **[`LLM.md`](./
15
15
  - **`Component`**: a base class with `virtual Render()` (builds a fresh DOM subtree from
16
16
  current state) and `Update()` (swaps the old subtree for the new one). No template
17
17
  language, no diffing — components build/update the DOM imperatively against plain DOM
18
- bindings, the way you'd write careful vanilla-JS UI code.
18
+ bindings, the way you'd write careful vanilla-JS UI code. An optional `virtual
19
+ RenderError(message)` renders a fallback instead of a hard crash if `Render()` throws —
20
+ purely additive; not overriding it keeps today's exact (uncaught) behavior.
19
21
  - **Reactive state, no RxJS**: components hold `state<number>`/`state<string>`/... (a
20
22
  KopScript language feature — see the [Kop](https://dev.azure.com/koppinator/Koppindependence/_git/Kop)
21
23
  repo) and subscribe once, in their constructor, to call `Update()` on change. No
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kopular",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Kopular: a small component framework for KopScript — components, reactive state, constructor-injected services, routing, structural directives, and HTTP, with no template DSL and no DI container",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/component.js CHANGED
@@ -5,14 +5,26 @@ export class Component {
5
5
  return document.createElement("div");
6
6
  }
7
7
 
8
+ RenderError(message) {
9
+ throw message;
10
+ }
11
+
12
+ SafeRender() {
13
+ try {
14
+ return this.Render();
15
+ } catch (message) {
16
+ return this.RenderError(message);
17
+ }
18
+ }
19
+
8
20
  Mount(parent) {
9
21
  (this.ParentElement = parent);
10
- (this.Root = this.Render());
22
+ (this.Root = this.SafeRender());
11
23
  parent.appendChild(this.Root);
12
24
  }
13
25
 
14
26
  Update() {
15
- let newRoot = this.Render();
27
+ let newRoot = this.SafeRender();
16
28
  this.ParentElement.replaceChild(newRoot, this.Root);
17
29
  (this.Root = newRoot);
18
30
  }
package/src/component.ks CHANGED
@@ -21,14 +21,34 @@ class Component {
21
21
  return document.createElement("div");
22
22
  }
23
23
 
24
+ // Overridden to render a fallback UI when Render() throws — a page bug,
25
+ // an unhandled rejected Http call, anything — instead of leaving
26
+ // Mount()/Update() to propagate the exception uncaught, which would
27
+ // otherwise crash whatever triggered the render (a click handler, a
28
+ // Router navigation) with nothing shown to the user at all. Default just
29
+ // re-throws, so anything that doesn't override this keeps today's exact
30
+ // behavior — this is purely additive, opt-in error recovery, not a
31
+ // behavior change for existing components.
32
+ protected virtual Element RenderError(string message) {
33
+ throw message;
34
+ }
35
+
36
+ private Element SafeRender() {
37
+ try {
38
+ return this.Render();
39
+ } catch (string message) {
40
+ return this.RenderError(message);
41
+ }
42
+ }
43
+
24
44
  public void Mount(Element parent) {
25
45
  this.ParentElement = parent;
26
- this.Root = this.Render();
46
+ this.Root = this.SafeRender();
27
47
  parent.appendChild(this.Root);
28
48
  }
29
49
 
30
50
  protected void Update() {
31
- Element newRoot = this.Render();
51
+ Element newRoot = this.SafeRender();
32
52
  this.ParentElement.replaceChild(newRoot, this.Root);
33
53
  this.Root = newRoot;
34
54
  }