Skip to content

Commit 4824400

Browse files
committed
add documentation (WIP)
1 parent ef5d7a0 commit 4824400

12 files changed

Lines changed: 1199 additions & 8 deletions

packages/shadow-objects/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ All notable changes to [@spearwolf/shadow-objects](https://github.com/spearwolf/
55
The format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## unreleased
9+
10+
- sharpen the `EntityApi` type definitions
11+
812
## [0.26.4] - 2026-01-15
913

1014
- fix return type definitions for `provideContext()` and `provideGlobalContext()`

packages/shadow-objects/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ In your HTML or View layer, you use the provided Web Components to create the En
140140

141141
```html
142142
<!-- 1. Initialize the Environment -->
143-
<shae-worker-env src="./my-module.js"></shae-worker-env>
143+
<shae-worker src="./my-module.js"></shae-worker>
144144

145145
<!-- 2. Create Entities -->
146146
<shae-ent token="my-entity">
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# The Mental Model
2+
3+
The **Shadow Objects Framework** is built upon a strict separation of concerns. To help you visualize how this works, let's start with a simple analogy.
4+
5+
## Analogy: The Shadow Theater
6+
7+
Imagine a **Shadow Theater** (Wayang Kulit).
8+
9+
* **The Screen (The View):** The audience watches the screen. They see moving shapes and stories unfolding. This is your UI (HTML/CSS).
10+
* **The Puppets (The Entities):** Behind the screen are the puppets. They are the actual objects with structure and form.
11+
* **The Puppeteer (The Logic/Shadow Object):** The puppeteer manipulates the puppets, deciding how they move and react based on the script.
12+
13+
In Shadow Objects:
14+
* You don't script the screen directly (no `document.querySelector` to update text).
15+
* You script the **Puppeteer** (Shadow Object).
16+
* The framework projects the state of the puppets onto the screen automatically.
17+
18+
## The Two Worlds
19+
20+
This analogy maps to two distinct realms in your application:
21+
22+
### 1. The Light World (View)
23+
This is what the user sees and interacts with. It consists of the DOM, Web Components, and the rendering layer.
24+
* **Role:** Pure projection and user input.
25+
* **State:** Minimal / Transient. Ideally, the view should not hold business logic state.
26+
* **Environment:** The Main Thread (Browser UI).
27+
28+
### 2. The Shadow World (Logic)
29+
This is where your application actually "lives". It contains the business logic, state management, and side effects.
30+
* **Role:** Processing logic, managing state, handling data.
31+
* **State:** The source of truth.
32+
* **Environment:** Typically a **Web Worker** (the "Dark"), but can also run on the Main Thread for simple setups.
33+
34+
## Core Concepts
35+
36+
To bridge these two worlds, we use four fundamental concepts:
37+
38+
### 1. Entity (The Puppet)
39+
An **Entity** is the abstract representation of a component. It exists in the Shadow World but mirrors a node in the View hierarchy.
40+
* Forms a tree structure (Parent/Child).
41+
* Holds **Properties** (data syncing from View).
42+
* Participates in the **Context** system.
43+
44+
### 2. Shadow Object (The Brain)
45+
A **Shadow Object** is a functional unit of logic attached to an Entity.
46+
* It is the "code" that runs for a specific component.
47+
* It is reactive: it listens to property changes and triggers effects.
48+
* It can talk to other Shadow Objects via Context.
49+
50+
### 3. Token (The Name)
51+
A **Token** is a simple string identifier (e.g., `"my-button"`, `"user-profile"`) that links the View to the Logic.
52+
* In the View: `<shae-ent token="my-button">`
53+
* In the Registry: `"my-button"` maps to `MyButtonShadowObject`.
54+
55+
> [!NOTE]
56+
> **Token vs. ID:** A `Token` describes *what* the object is (like a class name). The framework assigns unique IDs internally to distinguish specific instances.
57+
58+
### 4. Events (The Signals)
59+
While Properties flow down (from View to Logic), **Events** allow for dynamic communication in both directions.
60+
61+
* **Inbound (View → Logic):** Entities receive events from the View (like `click`, `submit`, or custom DOM events). Your Shadow Object can listen to these events to trigger actions.
62+
* **Outbound (Logic → View):** Shadow Objects can emit their own events. These travel back to the View, allowing the UI layer to react to logic decisions (e.g., playing an animation, showing a toast, or navigating).
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Architecture
2+
3+
This section details how the Shadow Objects framework orchestrates the communication between the View and the Logic, and how its internal systems operate.
4+
5+
## System Overview
6+
7+
The architecture revolves around a central **Kernel** that manages the lifecycle of the Shadow World and mediates communication.
8+
9+
A key architectural decision is that the **View Layer is abstract**. While the framework provides ready-to-use Web Components, they are built on top of a lower-level JavaScript API that you can use directly.
10+
11+
```mermaid
12+
graph TD
13+
subgraph "Light World (Main Thread)"
14+
UserCode[Your App / Framework]
15+
16+
subgraph "View Layer Abstraction"
17+
WC[Web Components <shae-ent>]
18+
React[React / Vue Adapter]
19+
Vanilla[Vanilla JS]
20+
21+
API[ViewComponent API]
22+
end
23+
24+
UserCode --> WC
25+
UserCode --> React
26+
UserCode --> Vanilla
27+
28+
WC -.->|Uses| API
29+
React -.->|Uses| API
30+
Vanilla -.->|Uses| API
31+
end
32+
33+
subgraph "Communication Bridge"
34+
Msg[Message Dispatch]
35+
end
36+
37+
subgraph "Shadow World (Worker Thread)"
38+
Kernel[The Kernel]
39+
Registry[Registry]
40+
41+
EntityTree[Entity Tree]
42+
Entity[Entity]
43+
SO[Shadow Object]
44+
45+
Kernel -->|Manages| EntityTree
46+
Kernel -->|Consults| Registry
47+
48+
EntityTree --> Entity
49+
Entity -->|Has| SO
50+
end
51+
52+
API -->|Messages| Msg
53+
Msg -->|Events| Kernel
54+
Kernel -->|State Updates| Msg
55+
Msg -->|Render Updates| API
56+
```
57+
58+
## Core Components
59+
60+
### 1. The Kernel
61+
The **Kernel** is the engine of the Shadow World.
62+
* **Entity Management:** It maintains the `EntityTree`, handling the creation, movement, and destruction of Entities mirroring the DOM structure.
63+
* **Orchestration:** When an Entity is created, the Kernel asks the **Registry** "What logic belongs to this Token?" and instantiates the corresponding Shadow Objects.
64+
* **Scheduling:** It manages the reactive update cycle, ensuring changes propagate efficiently.
65+
66+
### 2. The Registry
67+
The **Registry** is the configuration lookup table.
68+
* **Mapping:** It maps **Tokens** (strings) to **Shadow Object Constructors**.
69+
* **Routing:** It defines complex composition rules. A single Token in the view might trigger multiple Shadow Objects in the logic (e.g., a "button" token might load the `ButtonLogic` *and* a `AnalyticsMixin`).
70+
* **Conditional Routing:** Routes can be dynamic, loading logic only if certain properties are present on the Entity.
71+
72+
### 3. Message Dispatch
73+
Because the Light World and Shadow World often run in different threads (Main vs. Worker), they cannot share memory directly. They communicate via **Messages**.
74+
* **View -> Shadow:** Property changes, DOM events, and lifecycle hooks are sent as messages to the Kernel.
75+
* **Shadow -> View:** State changes in Shadow Objects are batched and sent back to update the View.
76+
77+
## The View Layer & ViewComponent API
78+
79+
The framework interacts with the "Light World" through the **ViewComponent API**. This is a JavaScript interface that handles the message passing and synchronization with the Kernel.
80+
81+
### Web Components (The Default Implementation)
82+
The standard way to use Shadow Objects is via the provided Web Components (`<shae-worker>`, `<shae-ent>` and `<shae-prop>`).
83+
* **Role:** These are simply a convenience wrapper.
84+
* **Function:** They automatically handle the lifecycle (mount/unmount) and property syncing by calling the ViewComponent API internally.
85+
86+
### Custom Integrations
87+
Because the architecture relies on the ViewComponent API, you are not forced to use Web Components. You can integrate Shadow Objects into any environment:
88+
* **React/Vue/Svelte:** You could write a wrapper that syncs a React component's lifecycle to Shadow Objects.
89+
* **Vanilla JS:** You can instantiate `ViewComponent` classes directly in plain JavaScript code if you don't want to use DOM elements.
90+
* **Canvas/WebGL:** You could even bind Shadow Objects to non-DOM objects, like sprites in a game engine.
91+
92+
## Communication Patterns
93+
94+
### Property Synchronization (Downstream)
95+
Data flows primarily **down** from the View to the Shadow World.
96+
1. **View Update:** The View (e.g., via attribute change on `<shae-ent>` or direct API call) updates a property.
97+
2. **Message:** The ViewComponent API sends a message to the Kernel.
98+
3. **Kernel Update:** Kernel updates `Entity.properties`.
99+
4. **Reaction:** Shadow Object's `useProperty` signal updates, triggering Effects.
100+
101+
### Events (Upstream & Lateral)
102+
Shadow Objects can emit events or react to them.
103+
* **DOM Events:** Forwarded from View to Entity.
104+
* **Internal Events:** Entities can emit events to communicate with parents or children without direct references.
105+
106+
### Context (Dependency Injection)
107+
The framework implements a hierarchical **Context** system similar to React Context or Angular Services.
108+
* **Provider:** An Entity provides a value (or signal).
109+
* **Consumer:** Any descendant Entity can consume that value.
110+
* **Reactive:** If the provider updates the value, all consumers update automatically.
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Lifecycle
2+
3+
Understanding the lifecycle of an Entity and its Shadow Objects is crucial for managing resources, side effects, and subscriptions correctly.
4+
5+
## The Entity Lifecycle
6+
7+
An Entity's life is controlled by the **ViewComponent API**.
8+
9+
### View Components & Component Context
10+
11+
The underlying machinery involves two key classes:
12+
1. **`ViewComponent`**: Represents a single node in the View Layer.
13+
2. **`ComponentContext`**: Orchestrates a group of ViewComponents, manages their hierarchy, and batches changes (Change Trails) to be sent to the Kernel.
14+
15+
While most developers use the provided Web Components, it's helpful to understand how they map to this API:
16+
17+
* **`<shae-ent>` (The Entity):** When this Web Component is connected to the DOM, it internally creates a new `ViewComponent` instance and mounts it.
18+
* **`<shae-worker>` (The Environment):** This Web Component (or similar environment wrappers) manages the `ComponentContext`. It ensures that all `<shae-ent>` children are registered within the same context, allowing them to form a cohesive tree structure that is then synced to the Shadow World.
19+
20+
### Lifecycle Phases
21+
22+
1. **Mount (View Layer):** The View Component is initialized and registered with its Context.
23+
* *Web Components:* `<shae-ent>` connects, finds its nearest `ComponentContext` (provided by parent `<shae-ent>` or root `<shae-worker>`), and registers itself.
24+
* *Manual:* You create a `ViewComponent` and call `context.addComponent(component)`.
25+
2. **Creation (Shadow):** The `ComponentContext` sends a message to the Kernel describing the new component. The Kernel creates an `Entity` node.
26+
3. **Instantiation:** The Kernel resolves the Token and creates the associated Shadow Object(s).
27+
4. **Active:** The Shadow Object runs its logic, sets up signals, and reacts to changes.
28+
5. **Unmount (View Layer):** The View Component is disposed.
29+
* *Web Components:* `<shae-ent>` disconnects and calls `component.destroy()`.
30+
* *Manual:* You call `component.destroy()` or `context.destroyComponent(component)`.
31+
6. **Destruction (Shadow):** The Kernel destroys the `Entity` and triggers the cleanup of all associated Shadow Objects.
32+
33+
## The Shadow Object Lifecycle
34+
35+
Shadow Objects are functional units. Their lifecycle is simple: **Setup** and **Teardown**.
36+
37+
### 1. Setup Phase (The Function Body)
38+
The code inside your function (or constructor) runs **once** when the object is instantiated. This is where you connect your logic to the framework.
39+
40+
* **Goal:** Define your reactive graph.
41+
* **Actions (The ShadowObjectCreationAPI):**
42+
43+
* **Inputs (Properties):**
44+
* `useProperty(name)`: Create a reactive signal for a single property.
45+
* `useProperties(map)`: Create signals for multiple properties at once.
46+
* **Context (Dependency Injection):**
47+
* `useContext(name)`: Consume a context value from the nearest ancestor provider.
48+
* `useParentContext(name)`: Consume a context value starting from the parent (skipping self).
49+
* `provideContext(name, value)`: Provide a value to descendants.
50+
* `provideGlobalContext(name, value)`: Provide a value globally to all entities.
51+
* **Reactivity:**
52+
* `createSignal(initial)`: Create a local state signal.
53+
* `createEffect(fn)`: Create a side effect that runs when dependencies change.
54+
* `createMemo(fn)`: Create a derived signal (computed value).
55+
* `createResource(factory, cleanup)`: Manage an external resource (like a 3D object) with lifecycle management.
56+
* **Events:**
57+
* `on(target, event, callback)`: Listen for events (from View or other objects).
58+
* `once(target, event, callback)`: Listen for an event exactly once.
59+
* **Lifecycle:**
60+
* `onDestroy(callback)`: Register a cleanup function to run when the object is destroyed.
61+
* **Access:**
62+
* `entity`: Direct access to the underlying `EntityApi` (advanced usage).
63+
64+
```typescript
65+
export function MyLogic({
66+
useProperty,
67+
createEffect,
68+
createSignal,
69+
on,
70+
entity, // Access to the entity instance
71+
onDestroy
72+
}) {
73+
// SETUP: Runs once
74+
const title = useProperty('title');
75+
const [count, setCount] = createSignal(0);
76+
77+
createEffect(() => {
78+
// RUNTIME: Runs whenever 'title' or 'count' changes
79+
console.log(`Title: ${title()}, Count: ${count()}`);
80+
});
81+
82+
// SETUP: Listen for View events
83+
// Events dispatched from the View are received on the entity instance as 'onViewEvent'.
84+
// The first argument is the type (name) of the event, the second is the data.
85+
on(entity, 'onViewEvent', (type, data) => {
86+
if (type === 'click') {
87+
setCount(c => c + 1);
88+
console.log('View was clicked!', data);
89+
}
90+
});
91+
92+
onDestroy(() => {
93+
// TEARDOWN: Runs once when destroyed
94+
console.log('Cleaning up...');
95+
});
96+
}
97+
```
98+
99+
### 2. Runtime Phase (Reactivity & Events)
100+
After setup, the Shadow Object is "alive". It doesn't re-run the main function. Instead, it reacts to changes in the environment.
101+
102+
#### A. Reactive Updates
103+
Effects and Computed values (Memos) re-run whenever their dependencies change.
104+
* **Drivers:** Property updates (from View), Context updates (from Parent), or internal Signal changes.
105+
106+
#### B. Event Flow
107+
The Runtime phase is also driven by **Events**, which can flow in multiple directions:
108+
109+
1. **View → Entity:** The View Layer triggers standard DOM events (like `click`, `input`) or custom events. These are sent to the Entity as events.
110+
* **Mechanism:** The `ViewComponent` captures the DOM event and sends a message to the Shadow World.
111+
* **Reaction:** The Shadow Object listens to these events on the `entity` instance using `on(entity, 'onViewEvent', (type, data) => ... )`. The specific event name (e.g., 'click') is passed as the first argument.
112+
2. **Entity → View:** The Shadow Object can emit events. The View Layer receives these messages and can trigger UI updates (e.g., navigation, playing sound).
113+
3. **Entity Tree (Inter-Object):** Shadow Objects can communicate with each other via events. Because Entities form a tree, events can be dispatched through the hierarchy, allowing decoupled communication between logic units (e.g. a child item signaling a selection to a parent list).
114+
115+
### 3. Teardown Phase (Cleanup)
116+
When the Entity is destroyed (or if the specific route that created this object is disabled), the Teardown phase begins.
117+
118+
* **Automatic:** All framework-managed signals, effects, and event listeners (`createEffect`, `on`) are automatically disposed of.
119+
* **Manual:** Use `onDestroy` to clean up *external* resources, such as:
120+
* `setInterval` / `setTimeout`
121+
* Global event listeners (e.g. on `window`)
122+
* WebSockets or network connections
123+
124+
> [!WARNING]
125+
> **Memory Leaks:** Always ensure you clean up non-framework resources in `onDestroy`. Failing to clear a `setInterval` will keep the closure alive indefinitely!

0 commit comments

Comments
 (0)