diff --git a/README.md b/README.md index d4958493c..613182a2d 100755 --- a/README.md +++ b/README.md @@ -3,5 +3,5 @@ To start a new RSpade project: ```bash -git clone --recurse-submodules ssh://git@privategit.hanson.xyz:3322/brianhansonxyz/rspade_project.git your-project-name +git clone --recurse-submodules ssh://git@git.internal.hanson.xyz/brianhansonxyz/rspade_project.git your-project-name ``` diff --git a/app/RSpade/man/jqhtml.txt b/app/RSpade/man/jqhtml.txt index fc71b64be..d18c0b778 100755 --- a/app/RSpade/man/jqhtml.txt +++ b/app/RSpade/man/jqhtml.txt @@ -14,8 +14,7 @@ SYNOPSIS class User_Card extends Jqhtml_Component { async on_load() { // Load async data - ONLY modify this.data - this.data = await fetch('/api/user/' + this.args.id) - .then(r => r.json()); + this.data = await User_Controller.get({id: this.args.id}); } on_ready() { @@ -43,6 +42,81 @@ DESCRIPTION - Double-render pattern for loading states - No build configuration required (auto-discovered) +COMPONENT COMPLEXITY TIERS + Three tiers guide which patterns to use for a new component: + + Static Components (template-only, no JS file): + Pure display from this.args. No lifecycle hooks needed. + Use for: badges, labels, static cards, layout wrappers. + + + + <%= this.args.label %> + + + + Simple Components (on_load + reload): + Fetch data in on_load(), re-fetch with reload() when this.args change. + Most common pattern. Framework handles caching and re-render. + + class User_List extends Jqhtml_Component { + on_create() { + this.data.users = []; + } + async on_load() { + this.data = await User_Controller.list({ + filter: this.args.filter + }); + } + on_ready() { + this.$sid('filter_btn').on('click', () => { + this.args.filter = 'active'; + this.reload(); // Re-fetches via on_load() + }); + } + } + + Complex Components (on_load + jQuery DOM manipulation): + Use on_load() for initial/cached data, then jQuery for incremental + mutations after initialization. Avoids unnecessary network round-trips + and preserves user state (selections, scroll position, input). + + class Chat_Widget extends Jqhtml_Component { + async on_load() { + this.data = await Chat_Controller.get_messages({ + channel: this.args.channel + }); + } + on_ready() { + // Incremental updates via DOM, not reload() + this.socket = new WebSocket(this.args.ws_url); + this.socket.onmessage = (e) => { + const msg = JSON.parse(e.data); + this.$sid('messages').append( + '
' + Rsx.escape(msg.text) + '
' + ); + }; + } + on_stop() { + this.socket.disconnect(); + } + } + + this.state Convention: + this.state is not a framework property - it has no special behavior. + Use it as convention for mutable UI state that changes after + initialization, distinct from this.data (immutable after on_load). + + on_create() { + this.state = { selected_ids: [], is_expanded: false }; + } + on_ready() { + this.$sid('toggle').on('click', () => { + this.state.is_expanded = !this.state.is_expanded; + this.$sid('panel').toggle(this.state.is_expanded); + }); + } + SEMANTIC-FIRST DESIGN JQHTML is designed for mechanical thinkers who think structurally rather than visually. Components represent logical concepts, not @@ -553,8 +627,7 @@ DOUBLE-RENDER PATTERN } async on_load() { - this.data.products = await fetch('/api/products') - .then(r => r.json()); + this.data.products = await Product_Controller.list(); // Automatic re-render happens after this completes } @@ -581,7 +654,7 @@ LOADING STATE PATTERN (CRITICAL) this.data.state = {loading: true}; this.render(); // WRONG: Manual render call - const response = await $.ajax({...}); // WRONG: $.ajax() + const response = await $.ajax({url: '...', ...}); // WRONG: $.ajax() if (response.success) { this.data = { @@ -684,8 +757,7 @@ JAVASCRIPT COMPONENT CLASS async on_load() { // Fetch data - NO DOM manipulation allowed! // ONLY update this.data - this.data.products = await fetch('/api/products') - .then(r => r.json()); + this.data.products = await Product_Controller.list(); // Template re-renders automatically with new data } @@ -1360,9 +1432,9 @@ EXAMPLES class Product_Card extends Jqhtml_Component { async on_load() { // Load product data - const id = this.args.product_id; - this.data = await fetch(`/api/products/${id}`) - .then(r => r.json()); + this.data = await Product_Controller.get({ + id: this.args.product_id + }); // Template re-renders automatically with data } @@ -1399,8 +1471,7 @@ EXAMPLES class Todo_List extends Jqhtml_Component { async on_load() { - this.data.todos = await fetch('/api/todos') - .then(r => r.json()); + this.data = await Todo_Controller.list(); } on_ready() { @@ -1438,15 +1509,9 @@ EXAMPLES } async submit() { - const data = { + await Contact_Controller.send({ email: this.$sid('email').val(), message: this.$sid('message').val(), - }; - - await fetch('/contact', { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify(data) }); this.$.html('

Thank you!

'); diff --git a/bin/framework-pull-upstream.sh b/bin/framework-pull-upstream.sh index e1a7daf1e..6b02a0afa 100755 --- a/bin/framework-pull-upstream.sh +++ b/bin/framework-pull-upstream.sh @@ -86,7 +86,7 @@ if ! git remote get-url rspade_upstream >/dev/null 2>&1; then # Auto-configure remote for project mode (when system/ is a submodule) if [ "$IS_PROJECT_MODE" = false ] && [ -f "../.gitmodules" ] && grep -q "path = system" ../.gitmodules 2>/dev/null; then echo "→ Configuring rspade_upstream remote (first-time setup)..." - UPSTREAM_URL="ssh://git@privategit.hanson.xyz:3322/brianhansonxyz/rspade_system.git" + UPSTREAM_URL="ssh://git@git.internal.hanson.xyz/brianhansonxyz/rspade_system.git" if git remote add rspade_upstream "$UPSTREAM_URL" 2>&1; then echo " ✓ Remote configured: $UPSTREAM_URL" echo "" diff --git a/docs/CLAUDE.dist.md b/docs/CLAUDE.dist.md index 17d5d8b1d..22d1ba6bd 100644 --- a/docs/CLAUDE.dist.md +++ b/docs/CLAUDE.dist.md @@ -595,12 +595,19 @@ class Toggle_Button extends Component { ### Lifecycle 1. **on_create()** → Setup defaults (sync) - `this.data.rows = []; this.data.loading = true;` -2. **render** → Template executes -3. **on_render()** → Hide uninitialized UI (sync) -4. **on_load()** → Fetch data into `this.data` (async) -5. **on_ready()** → DOM manipulation safe (async) +2. **render** → Template executes (top-down: parent before children) +3. **on_render()** → Fires after render, BEFORE children ready (top-down, sync) +4. **on_load()** → Fetch data into `this.data` (bottom-up, parallel siblings, async) +5. **on_ready()** → All children guaranteed ready (bottom-up, async) +6. **on_stop()** → Teardown when destroyed (sync) -If `on_load()` modifies `this.data`, component renders twice (defaults → populated). +If `on_load()` modifies `this.data`, component renders twice (defaults → populated). on_ready() fires once after final render. + +### Component Complexity Tiers + +- **Static**: Template-only, no JS file. Pure display from this.args. +- **Simple**: on_load() fetches this.data, `reload()` to re-fetch when this.args change. +- **Complex**: on_load() for initial/cached data, then jQuery DOM manipulation for incremental mutations post-initialization. ### Component API @@ -612,11 +619,12 @@ If `on_load()` modifies `this.data`, component renders twice (defaults → popul | `this.$sid('name')` | jQuery | Child with `$sid="name"` | | `this.sid('name')` | Component/null | Child component instance | -**reload() vs render():** -``` -reload() = on_load() → render() → on_ready() ← ALWAYS USE THIS -render() = template only (no on_ready) ← NEVER USE -``` +**Lifecycle Methods:** +- `reload()` - Reset this.data to on_create() defaults → on_load() → render() → on_ready(). Use when this.args changed. +- `render()` / `redraw()` - Re-execute template → wait for children → on_ready(). Does NOT re-run on_load(). UI-only updates. +- `stop()` - Destroy component and all children. Calls on_stop() if defined. + +**render() destroys child DOM**: All child elements and child components are recreated. DOM event handlers on children are lost and must be re-registered. After mutations, call `this.reload()` - the server round-trip is intentional: ```javascript @@ -626,11 +634,15 @@ async add_item() { } ``` -**Event handlers** go in `on_ready()` - they auto-reattach after reload. **WRONG:** Event delegation like `this.$.on('click', '[data-sid="btn"]', handler)` to "survive" render calls - use `reload()` instead. - **this.data rules (enforced):** Writable only in `on_create()` (defaults) and `on_load()` (fetched data). Read-only elsewhere. -**on_render():** Ignore - use `on_ready()` for post-render work. +### Event Handler Placement + +| What | Where | Why | +|------|-------|-----| +| `this.on('event', ...)` | `on_create()` | Persists across renders; on_ready() risks infinite loops from event replay | +| `this.sid('child').on('event')` | `on_ready()` | Child component events | +| `this.$sid('elem').on('click')` | `on_render()` or `on_ready()` | Child DOM recreated on render, must re-attach | ### Loading Pattern diff --git a/docs/QUICKSTART.md b/docs/QUICKSTART.md index 8e890532c..9ccd87c79 100755 --- a/docs/QUICKSTART.md +++ b/docs/QUICKSTART.md @@ -7,14 +7,14 @@ Clone the RSpade project with the framework submodule: ```bash -git clone --recurse-submodules ssh://git@privategit.hanson.xyz:3322/brianhansonxyz/rspade_project.git /path/to/project +git clone --recurse-submodules ssh://git@git.internal.hanson.xyz/brianhansonxyz/rspade_project.git /path/to/project cd /path/to/project ``` **Alternative method** (clone then initialize submodules): ```bash -git clone ssh://git@privategit.hanson.xyz:3322/brianhansonxyz/rspade_project.git /path/to/project +git clone ssh://git@git.internal.hanson.xyz/brianhansonxyz/rspade_project.git /path/to/project cd /path/to/project git submodule update --init --recursive ``` diff --git a/docs/framework_upstream.md b/docs/framework_upstream.md index 14b0503a1..66c8a2a4f 100755 --- a/docs/framework_upstream.md +++ b/docs/framework_upstream.md @@ -20,7 +20,7 @@ When starting a new project from this template: bin/framework-upstream setup ``` -This configures `ssh://git@192.168.0.3:3322/brianhansonxyz/rspade-publish.git` as the `framework-upstream` remote. +This configures `ssh://git@git.internal.hanson.xyz/brianhansonxyz/rspade-publish.git` as the `framework-upstream` remote. ## Workflow @@ -171,7 +171,7 @@ The `bin/framework-upstream` script automates these Git commands: ```bash # Add upstream remote manually -git remote add framework-upstream ssh://git@192.168.0.3:3322/brianhansonxyz/rspade-publish.git +git remote add framework-upstream ssh://git@git.internal.hanson.xyz/brianhansonxyz/rspade-publish.git # Fetch updates git fetch framework-upstream