# Coralite Framework

## CRITICAL DIRECTIVES
When generating code or answering questions about Coralite, you MUST adhere to the following rules:
1. **NO VANILLA BOILERPLATE:** Do not write standard `customElements.define()` or `class extends HTMLElement` blocks for components. You MUST always use Coralite's `defineComponent` exported from `coralite`.
2. **AST SPLICING AWARENESS:** If a component is written declaratively in HTML (e.g., `<my-component>`), the server **preserves** the host tag to act as the Web Component container. It injects the component's template contents as children (Light DOM) and attaches deterministic tracking attributes (e.g., `data-cid="my-component-0"`) to the host tag itself.
3. **ZERO-CONFIG DEPENDENCIES:** Coralite uses AST static analysis. To import JavaScript on the client, use standard dynamic imports (`await import('pkg')`) inside the `client` block.
4. To mount a Web Component imperatively, use `document.createElement('my-component')` using a string literal containing a hyphen, which the bundler automatically detects.
---

## 1. The Serialization Boundary
**CRITICAL RULE:** You cannot use top-level imports or variables (declared outside `defineComponent`) directly inside the `client` block. The `client` block is serialized and sent to the browser. To bridge server-side data to the client, you must use the `server` block.

## 2. File Structure: Pages vs. Components
* **Pages**: Pages are standard `.html` files (e.g., `index.html`) that serve as route entry points. They act as the **consumers** of components by declaring custom element tags within their HTML body (e.g., `<my-navbar></my-navbar>`).
* **Components**: A Coralite component is strictly an `.html` file. It is not a `.js` or `.jsx` file. A component file consists of up to three top-level root tags:
  1. `<template id="component-name">` (**Required**): Defines the declarative HTML structure.
  2. `<style>` (**Optional**): Defines component-specific styles.
  3. `<script type="module">` (**Optional**): Defines the reactive logic using `defineComponent`.
* **The Relationship**: When a Page consumes a component tag, the server-side build process discovers the component, **preserves the custom element host tag**, and splices the component's inner `<template>` HTML into it. It then processes the `<style>`, and bundles the serialized `client` block into the client-side JavaScript chunk.

## 3. System Architecture
* **Rendering**: Strictly Server-Side Rendered (SSR) with deterministic ID generation (`my-component-0`). No heavy client-side AST rendering.
* **Architecture**: Coralite utilizes a hybrid model where components are either fully declarative (SSR-only) or true imperative Web Components. Imperative components lazily loaded via JavaScript use native HTML template stamping (`this.innerHTML = templateHTML`) with Light DOM projection, entirely eliminating the need for client-side HTML parsers.
* **API Paradigm**: Coralite uses a "Flat Options" API. All logic is defined within four strictly isolated blocks: `attributes`, `server`, `getters`, and `client`.
* **State Equation**: `Attributes + ServerState + Getters = State`. The template only sees this unified reactive proxy.
* **Scope Isolation**: Component state (like variables defined in the client script or internal `ref` IDs) is strictly isolated. It does **not** cascade down to child components. Only explicitly passed HTML attributes and globally injected plugin states cascade.

## 4. Static Components
Static components are purely HTML-based elements without scripting logic.
* **Definition**: Must be wrapped in a `<template id="component-name">` tag.
* **Usage**: The custom element tag name in the HTML page must exactly match the template's `id` (e.g., `<component-name></component-name>`).
* **Data Binding**: Data is passed via HTML attributes and rendered inside the template using double curly braces: `{{ attributeName }}`.

## 5. Dynamic Components (defineComponent)
Dynamic components are authored by appending a `<script type="module">` below the `<template>`, exporting the core `defineComponent` helper.

### 5.1 Anti-Patterns
When generating code for Coralite components, **DO NOT** use standard Web Component boilerplate.

**❌ BAD (Vanilla Web Component):**
```javascript
class MyComponent extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }
  connectedCallback() { ... }
}
customElements.define('my-component', MyComponent);
```

**✅ GOOD (Coralite defineComponent):**

```javascript
import { defineComponent } from 'coralite'
export default defineComponent({
  client: ({ state, signal, refs }) => { ... }
})
```

### 5.2 Component Structure: Isolated Blocks

A component definition consists of four primary blocks:

#### 1. `attributes` (Sync)

* **Purpose**: Coerces HTML attributes into JavaScript primitives.
* **Rule**: Strictly limited to primitives (`String`, `Number`, `Boolean`). `Object` and `Array` types are strictly blocked to avoid JSON-in-HTML parsing errors.
* **Reactivity**: Changes to HTML attributes (via `setAttribute`) automatically trigger updates to the unified `state`.

#### 2. `server({ state, page, root, module, id, session, app, noHydration, ...plugins })` (Async/Server)

* **Purpose**: Fetches initial state on the server during the build process. Formerly known as `data`.
* **Arguments** (The Context Object):
  * `state`: Contains the component's resolved attributes (e.g., `state.category`). **Always use this to read HTML attribute values passed to the component.**
  * `page`: Contains global metadata about the consuming page/route (e.g., URL, file info).
  * `root`: The root AST node of the component.
  * `module`: The component module definition.
  * `id`: The unique instance ID.
  * `session`: The current rendering session.
  * `app`: The Coralite instance.
  * `noHydration`: Boolean indicating if hydration is disabled for this instance.
  * `...plugins`: Namespaced plugin contexts from `server.context`.

* **Serialization**: This block is fully stripped from the client bundle by the build tool. The returned object is serialized and merged into the unified client-side `state`.

#### 3. `getters` (Sync)

* **Purpose**: Pure functions for derived state (isomorphic).
* **Rule**: These receive a **Read-Only Proxy** of the state. They must never mutate state and cannot be `async`.
* **Reactivity**: Automatically re-evaluated when any underlying state dependency changes.

#### 4. `client({ state, signal, refs, ...plugins })` (Client)

* **Purpose**: The client-side controller for the component. Formerly known as `script`.
* **Arguments** (Injected via Client Context):
  * `state`: A **Read/Write Mutable Proxy**. Mutations here automatically trigger getter evaluations and precise DOM updates.
  * `signal`: An `AbortSignal` mapped to the component's lifecycle. Used for native cleanup (e.g., aborting fetch requests, removing global listeners) when disconnected.
  * `refs`: A dictionary of dynamically generated, globally unique IDs (e.g., `my-comp-0__btn`) for elements marked with the `ref` attribute (integrated into the Core framework).
  * `[pluginName | client.name]`: **Namespaced Plugin Context.** Injected context helpers from plugins are isolated under their namespace key (either the plugin `name` or `client.name`). For example, if a plugin has `client.name: 'event'` and defines `context: { bus: ... }`, the component's client block will receive it as `event.bus` (e.g., `client: ({ event }) => { event.bus.emit(...) }`).


* **Usage**: All DOM events, imperative mutations, third-party library initializations, and side-effects live here.

**⚠️ WARNING:** When dynamically adding Coralite components/custom elements to the DOM, you MUST use `document.createElement('tag-name')`. Using `element.innerHTML = '<tag-name></tag-name>'` will prevent the framework from detecting, upgrading, and initializing the component.

### 5.3 The "Smart State, Dumb Template" Paradigm

HTML templates in Coralite are strictly declarative and "dumb". They do not support JavaScript logic or dot-notation. The template can *only* render flat keys from the unified `state`.

### 5.4 Native Web APIs

* **Communication**: Use standard `CustomEvent` for component-to-component communication.
* **Cleanup**: Always use the provided `AbortSignal` (`signal`) to clean up event listeners and abort pending network requests when the component unmounts.

### 5.5 Slots Projection & Transformation (`slots`)

You define transformations in the `slots` object. Each key is a slot name (use `'default'` for unnamed slots).

* **Signature**: `(slotNodes, state) => HTMLString | NodeArray | undefined | null`
* **Void = Bypass Paradigm**:
  * **`undefined`**: Signals the framework to **bypass** processing. It preserves the existing content (AST on server, DOM on client). This is crucial for performance and prevents flushing content during hydration if it's already rendered.
  * **`null` | `""` | `[]`**: Signals the framework to **clear** the slot content explicitly.
* **Scope**: Slotted content is evaluated within the parent component's local scope.
* **Preservation**: Coralite maps original Light DOM elements automatically, projecting them synchronously upon connected callbacks for dynamic imperative components.

### 5.6 CSS Scoping (PostCSS)

Coralite components are strictly scoped by default, despite rendering in the global Light DOM. The compiler injects a unique instance identifier and PostCSS nests all CSS rules securely beneath that attribute selector.

---

## 6. Framework Extensibility (definePlugin)

Plugins use a strictly isomorphic API boundary, divided clearly into `server` (Node.js/Build) and `client` (Browser/Runtime) objects.

### 6.1 The Plugin API Schema

The `definePlugin` function enforces the following schema. A plugin consists of a server-side build lifecycle layer (`server`) and a client-side browser runtime lifecycle layer (`client`).

```javascript
import { definePlugin } from 'coralite'

export default definePlugin({
  name: 'my-plugin', // Required. Acts as the namespace key on the evaluation context (e.g. context['my-plugin']).
  
  // SERVER: Strictly Node.js / Build-Time Execution
  server: {
    config: { ... }, // Optional plugin-specific configuration settings
    context: (pluginContext) => (instanceContext) => { ... }, // Symmetrical context function available in defineComponent server block
    components: [ ... ], // Array of string paths to plugin-provided .html components

    // Server Build Hooks
    onBeforeBuild: async ({ app, buildId, options, config }) => {
      // Runs before the build process starts. Can return a partial options object to override build options.
    },
    onPageSet: async ({ elements, state, page, data, app, config }) => {
      // Called when a new page is added to the collection.
    },
    onPageUpdate: async ({ elements, page, newValue, oldValue, app, config }) => {
      // Called when a page document is updated.
    },
    onPageDelete: async ({ data, app, config }) => {
      // Called when a page document is deleted.
    },
    onComponentSet: async ({ component, app, config }) => {
      // Called when a component is added to the collection.
    },
    onComponentUpdate: async ({ component, app, config }) => {
      // Called when a component document is updated.
    },
    onComponentDelete: async ({ component, app, config }) => {
      // Called when a component is deleted.
    },
    onBeforePageRender: async ({ component, state, page, session, app, config }) => {
      // Called before a page's AST tree is compiled.
    },
    onBeforeComponentRender: async ({ state, componentId, instanceId, refs, textNodes, attributes, page, element, session, app, config }) => {
      // Called before a component is rendered. Can return a partial state object to merge.
    },
    onAfterComponentRender: async ({ result, state, componentId, instanceId, refs, textNodes, attributes, page, element, session, app, config }) => {
      // Called after a component is rendered. Can return a partial AST node/array patch.
    },
    onAfterPageRender: async ({ result, session, app, config }) => {
      // Called after a page is rendered, but before it is written to the filesystem.
    },
    onAfterBuild: async ({ results, error, duration, app, config }) => {
      // Called when the build completes (succeeds or fails).
    }
  },

  // CLIENT: Strictly Browser / Run-Time Execution
  client: {
    config: { ... }, // Optional plugin-specific client-side configuration
    context: (pluginContext) => (instanceContext) => { ... }, // Client-side context injections (Two-Phase Resolver)
    
    // Client Runtime Hooks
    onBeforeComponentRender: ({ state, instanceId, componentId, refs, element, options }) => {
      // Called before the component state is proxied and rendered.
    },
    onAfterComponentRender: ({ state, instanceId, componentId, element, options }) => {
      // Called after the component DOM is updated and stable.
    },
    onDisconnected: ({ state, instanceId, componentId, element, options }) => {
      // Called when the component is removed from the DOM.
    }
  }
})
```

### 6.2 The `server.context` System

The `server.context` property defines methods and data available to components **during the server-side build process** via the first argument of the `server` block in `defineComponent`. It uses a **Two-Phase Resolver** pattern.

* **Phase 1 (Global Initialization)**: `(pluginContext) => ...` Runs exactly once during framework setup.
    * **`pluginContext`**: A Proxy containing the global `app` instance and the plugin's `config`.
    * **Mutation**: Any property added to `pluginContext` during this phase (e.g., `pluginContext.db = ...`) is persisted to the global server context and becomes visible to all subsequent plugins registered in the system.
* **Phase 2 (Instance Resolver)**: `(instanceContext) => ...` The function returned by Phase 1. It runs per component instance rendering.
    * **`instanceContext`**: A namespaced Proxy that allows this plugin to lazily access other plugins' Phase 2 APIs (e.g., `instanceContext['other-plugin'].getData()`). It also contains the `app` instance.
    * **Result**: Returns the final object exposed to the component's `server` block under the plugin's name.

* **Usage**: `async server(context) { const data = await context['my-plugin'].getData(); return { data }; }`

### 6.3 Isomorphic Component Lifecycle Hooks

Plugins can intercept specific components during AST generation (Server) and DOM hydration (Client):

* `onBeforeComponentRender`: Runs before tokens are replaced/proxied. Perfect for injecting variables into the local `state`.
  * **Server Payload**: AST Pointers (`refs`, `textNodes`, `attributes`).
  * **Client Payload**: Mutable State, Physical Element Reference.

* `onAfterComponentRender`: Runs after the component is rendered.
  * **Server Payload**: The completed AST array (`result`).
  * **Client Payload**: Executes after the browser's microtask queue flushes the DOM.

### 6.4 Client Context Injection (Two-Phase Resolver)

Client injections mapped in `client.context` use a Two-Phase Resolver setup:

* **Phase 1 (Global Setup)**: `(pluginContext) => ...` Runs exactly once when the plugin registers. Use this for shared plugin state.
* **Phase 2 (Local Instance)**: `(instanceContext) => ...` Runs when a component mounts. Returns the actual utility/context mapped to the plugin's namespace on the component's client context.

**Namespacing**: Injected context helpers are namespaced on the client context object using either the plugin's `name` or `client.name` property. For example, if a plugin defines `name: 'store'` and has a context helper `fetch`, the component `client` block accesses it as `context.store.fetch(...)` rather than a flat global export.

### 6.5 The Plugin Serialization Boundary
**CRITICAL RULE:** Variables defined in the top-level scope of the plugin file or inside the plugin factory function (e.g., `export default function myPlugin(options) { ... }`) are NOT accessible in the `client` block. The framework serializes the `client` functions for the browser.
*   **Phase 1 (`pluginContext`)**: Use this scope to define shared state (e.g., caches) that must persist across all component instances in the browser. Mutations here are shared globally across the plugin system.
*   **Phase 2 (`instanceContext`)**: Use this scope for logic specific to a single component instance. Use the `instanceContext` Proxy to lazily access other plugins' APIs.

## 7. Configuration (defineConfig)

Coralite projects are configured using `defineConfig` exported from `coralite`. This helper validates the configuration object structure and options at startup.

### 7.1 Required Properties
*   `output` (string): The path to the output directory where built files will be placed.
*   `components` (string): The path to the directory containing Coralite components.
*   `pages` (string): The path to the directory containing pages that will be rendered.

These strings must be non-empty.

### 7.2 Optional Properties
*   `plugins` (array): Array of plugin instances. Each plugin must be an object with a valid non-empty `name` string.
*   `baseURL` (string): Base URL for asset paths (defaults to `/`).
*   `assets` (array): Static assets to copy during the build.
*   `externalStyles` (array of strings): Global stylesheets to inject into every page.
*   `mode` (string): Build mode, either `'production'` or `'development'`.
*   `projectRoot` (string): The root directory of the project.
*   `ignoreByAttribute` (array): Attributes to ignore during parse.
*   `skipRenderByAttribute` (array): Attributes to skip rendering.
*   `onError` (function): Custom callback for handling errors and warnings.

### 7.3 Schema Example
```javascript
import { defineConfig } from 'coralite';

export default defineConfig({
  output: './dist',
  components: './src/components',
  pages: './src/pages',
  mode: 'production',
  assets: [
    { src: './src/assets/images', dest: 'assets/images' }
  ]
});
```

## 8. Asset Management

The `assets` array in the configuration allows you to copy files or directories to the build output. **Prefer this over manually copying vendor files into the `public` directory.**

### 8.1 Local File Resolution
If the asset object contains a `src` property, it copies from your local file system.
```javascript
export default defineConfig({
  assets: [
    { src: './src/docs/pdf', dest: 'assets/pdf' }
  ]
});
```

### 8.2 NPM Package Resolution
If `src` is missing, use `pkg` and `path` to resolve assets from `node_modules`.
```javascript
export default defineConfig({
  assets: [
    { 
      pkg: 'flag-icons', 
      path: 'flags/4x3/es.svg', 
      dest: 'assets/flags/4x3/es.svg' 
    }
  ]
});
```

## 9. Built-In Plugins

* **Metadata Plugin**: Extracts SEO data during the build phase.
* **Static Asset Plugin**: Copies static assets to the build output folder.
* **Testing Plugin**: Automates E2E testing preparation by duplicating all `ref` attributes to `data-testid` deterministically on both server and client.

## 10. E2E Testing

* **Readiness**: Await `window.__coralite_ready__` after navigation.
* **Selectors**: Rely on `data-testid` generated by the **Testing Plugin** (e.g., `[data-testid="my-component-0__submit-btn"]`) for robust selectors.

## 11. Form Integration and Interaction Patterns

### 11.1 Toggleable Components (Checkbox/Radio)
For components that act as filters or toggles, prefer native browser behaviors over manual JavaScript state toggling.
*   **The Pattern**: Wrap the visual component in a `<label>` and link it to a hidden `<input>` using unique IDs.
*   **Unique IDs**: Use Coralite's `instanceId` or `ref` system to generate unique IDs for `id` and `for` attributes.
*   **Example**:
    ```html
    <template id="filter-button">
      <label for="{{ ref_checkbox }}"> ... </label>
      <input id="{{ ref_checkbox }}" ref="checkbox" type="checkbox" name="{{ name }}" checked="{{ isChecked }}">
    </template>
    ```

### 11.2 Data Collection with FormData
When multiple components form a filter set or a form, wrap them in a `<form>` element and use the `FormData` API.
*   **Reactivity**: Listen for the `change` event on the form to detect state updates from any child component.
*   **Efficiency**: `FormData` automatically collects all values from named inputs, avoiding manual DOM traversal and individual attribute checking.
*   **Implementation**:
    ```javascript
    client: ({ refs }) => {
      const form = refs('filterForm');
      form.addEventListener('change', () => {
        const data = new FormData(form);
        const categories = data.getAll('categories');
        // Update global state or trigger fetch...
      });
    }
    ```

### 11.3 State Synchronization
If CSS styling depends on the host element's attributes (e.g., `filter-button[checked]`), manually sync the internal input state back to the host attribute during the `change` event.
```javascript
form.addEventListener('change', () => {
  const btn = event.target.closest('filter-button');
  const input = btn.querySelector('input');
  if (input.checked) btn.setAttribute('checked', '');
  else btn.removeAttribute('checked');
});
```

## 12. Collections (CoraliteCollection)

Coralite tracks pages and components in structured, queryable collections accessible on the Coralite application instance as `app.pages` and `app.components`.

### 12.1 Collection Properties
Each collection maintains three internal views:
*   `list` (array of `CoraliteCollectionItem`): A flat list of all items in the collection.
*   `listByPath` (object): Groupings of collection items by their folder path.
*   `collection` (object): Direct map of unique identifiers (pathname or custom ID) to items.

### 12.2 Callback Hooks
During initialization, a collection registers callback hooks to handle document lifecycle events:
*   `onSet(value)`: Triggers when a new document is added. Can return an object containing `{ value, id, state, type }` to customize mapping or abort insertion by returning a falsy value.
*   `onUpdate(newValue, oldValue)`: Triggers when an existing document is updated. Can customize state or abort update by returning a falsy value.
*   `onDelete(value)`: Triggers when a document is deleted.

### 12.3 Key Methods
*   `setItem(value)`: Adds or updates a page or component document. Supports string filepath or `HTMLData` objects.
*   `deleteItem(value)`: Removes a document from the collection.
*   `getItem(id)`: Retrieves a document by its ID or pathname.
*   `getListByPath(dirname)`: Retrieves documents under a specific directory.

## 13. Error Handling (CoraliteError)

Coralite implements `CoraliteError` to encapsulate context about errors encountered during AST compilation, page rendering, or plugin execution.

### 13.1 CoraliteError Context Properties
Every `CoraliteError` exposes detailed diagnostic metadata:
*   `message`: Description of the error.
*   `componentId` (string): The custom tag name of the component where the error occurred.
*   `filePath` (string): The path to the file containing the error.
*   `instanceId` (string): The unique instance identifier of the component.
*   `pagePath` (string): The path to the page currently being built.
*   `line` (number): The line number in the source file.
*   `column` (number): The column number in the source file.
*   `stackFile` (string): The file name extracted from the stack trace.
*   Cause (Error): The original error instance (if wrapped).
