docs: add progressive template enhancement implementation plan
This commit is contained in:
@@ -0,0 +1,627 @@
|
||||
# Progressive Browser Script Template Enhancement Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Enhance the `browser_script_with_business_logic` template in Rust to generate complete, runnable browser scripts with proper HTTP handling, status determination, and error handling.
|
||||
|
||||
**Architecture:** Modify `src/generated_scene/generator.rs` to replace the current incomplete JavaScript template with an enhanced version that includes: direct URL usage (fixing the URL construction bug), jQuery + fetch dual HTTP client support, complete status determination (blocked/error/partial/empty/ok), and enhanced entrypoint with page context validation.
|
||||
|
||||
**Tech Stack:** Rust, JavaScript (browser script), serde_json
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
| File | Action | Purpose |
|
||||
|------|--------|---------|
|
||||
| `src/generated_scene/generator.rs` | Modify | Replace `browser_script_with_business_logic` function with enhanced template |
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Fix URL Building in buildRequest()
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/generated_scene/generator.rs:308-321` (current `buildRequest` function in template)
|
||||
|
||||
**Current bug:** The template uses `new URL(endpoint.url, window.location.origin)` which incorrectly constructs URLs based on the current page's origin instead of using the complete endpoint URL directly.
|
||||
|
||||
**Goal:** Replace the buggy URL construction with direct URL usage.
|
||||
|
||||
- [ ] **Step 1: Write the failing test**
|
||||
|
||||
Create a test file to verify URL construction behavior:
|
||||
|
||||
```javascript
|
||||
// Test that URL is used directly without window.location.origin
|
||||
const assert = require('assert');
|
||||
|
||||
// Mock a complete URL in endpoint
|
||||
const endpoint = { url: 'http://20.76.57.61:18080/gsllys/api/test', method: 'POST' };
|
||||
|
||||
// Expected: buildRequest should return the URL directly
|
||||
// NOT: new URL(endpoint.url, 'http://different-origin.com')
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Implement the fix**
|
||||
|
||||
Replace the `buildRequest` function in `browser_script_with_business_logic` (lines 308-321 in the generated template):
|
||||
|
||||
**Current (buggy):**
|
||||
```javascript
|
||||
function buildRequest(args, endpoint) {
|
||||
const url = new URL(endpoint.url, window.location.origin);
|
||||
const params = { ...STATIC_PARAMS, ...args };
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (value !== undefined && value !== null) {
|
||||
url.searchParams.set(key, String(value));
|
||||
}
|
||||
}
|
||||
return {
|
||||
url: url.toString(),
|
||||
method: endpoint.method || 'GET',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
**Fixed:**
|
||||
```javascript
|
||||
function buildRequest(args, endpoint) {
|
||||
// Use endpoint.url directly - it's already a complete URL
|
||||
const url = endpoint.url;
|
||||
const method = endpoint.method || 'POST';
|
||||
const headers = { 'Content-Type': 'application/json' };
|
||||
const body = JSON.stringify({ ...STATIC_PARAMS, ...args });
|
||||
return { url, method, headers, body };
|
||||
}
|
||||
```
|
||||
|
||||
Locate this in `src/generated_scene/generator.rs` within the `browser_script_with_business_logic` function (around line 308 in the format! string). Replace the entire `buildRequest` function definition.
|
||||
|
||||
- [ ] **Step 3: Verify the change**
|
||||
|
||||
Run `cargo build` to verify the Rust code compiles:
|
||||
|
||||
```bash
|
||||
cargo build
|
||||
```
|
||||
|
||||
Expected: Build succeeds without errors.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add src/generated_scene/generator.rs
|
||||
git commit -m "fix(generator): use endpoint.url directly in buildRequest to fix URL construction bug"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Add jQuery + fetch Dual HTTP Client Support
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/generated_scene/generator.rs:355-368` (current `defaultDeps` object in template)
|
||||
|
||||
**Goal:** Add jQuery `$.ajax` as primary HTTP client with fetch as fallback for environments without jQuery.
|
||||
|
||||
- [ ] **Step 1: Replace defaultDeps with enhanced version**
|
||||
|
||||
Replace the current `defaultDeps` object in the template with enhanced jQuery + fetch support:
|
||||
|
||||
**Current:**
|
||||
```javascript
|
||||
const defaultDeps = {
|
||||
validatePageContext: async () => true,
|
||||
queryData: async (args) => {
|
||||
const endpoint = API_ENDPOINTS[0];
|
||||
if (!endpoint) throw new Error('No API endpoint configured');
|
||||
const request = buildRequest(args, endpoint);
|
||||
const response = await fetch(request.url, {
|
||||
method: request.method,
|
||||
headers: request.headers
|
||||
});
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
return response.json();
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**Enhanced:**
|
||||
```javascript
|
||||
const defaultDeps = {
|
||||
validatePageContext(args) {
|
||||
const host = (globalThis.location?.hostname || '').trim();
|
||||
const expected = (args.expected_domain || '').trim();
|
||||
if (!host) return { ok: false, reason: 'page_context_unavailable' };
|
||||
if (host !== expected) return { ok: false, reason: 'page_context_mismatch' };
|
||||
return { ok: true };
|
||||
},
|
||||
|
||||
async queryData(args) {
|
||||
const endpoint = API_ENDPOINTS[0];
|
||||
if (!endpoint) throw new Error('No API endpoint configured');
|
||||
const request = buildRequest(args, endpoint);
|
||||
|
||||
// Prefer jQuery (internal pages typically have it)
|
||||
if (typeof $ !== 'undefined' && typeof $.ajax === 'function') {
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
url: request.url,
|
||||
type: request.method,
|
||||
data: request.body,
|
||||
contentType: 'application/json',
|
||||
dataType: 'json',
|
||||
success: resolve,
|
||||
error: (xhr, status, err) => reject(new Error(
|
||||
`API failed (${xhr.status}): ${err} | body=${(xhr.responseText || '').substring(0, 200)}`
|
||||
))
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Fallback: fetch API
|
||||
if (typeof fetch === 'function') {
|
||||
const response = await fetch(request.url, {
|
||||
method: request.method,
|
||||
headers: request.headers,
|
||||
body: request.method !== 'GET' ? request.body : undefined
|
||||
});
|
||||
if (!response.ok) {
|
||||
const text = await response.text().catch(() => '');
|
||||
throw new Error(`HTTP ${response.status}: ${text.substring(0, 200)}`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
throw new Error('No HTTP client available (need jQuery or fetch)');
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
This code goes into the format! string in `browser_script_with_business_logic` function in `src/generated_scene/generator.rs`.
|
||||
|
||||
- [ ] **Step 2: Verify the change**
|
||||
|
||||
Run `cargo build` to verify:
|
||||
|
||||
```bash
|
||||
cargo build
|
||||
```
|
||||
|
||||
Expected: Build succeeds.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add src/generated_scene/generator.rs
|
||||
git commit -m "feat(generator): add jQuery + fetch dual HTTP client support in template"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Add determineArtifactStatus Function
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/generated_scene/generator.rs` (add new function to template before `buildArtifact`)
|
||||
|
||||
**Goal:** Add complete status determination logic supporting blocked/error/partial/empty/ok statuses.
|
||||
|
||||
- [ ] **Step 1: Add determineArtifactStatus function to template**
|
||||
|
||||
Insert the following function into the template, before the `buildArtifact` function:
|
||||
|
||||
```javascript
|
||||
function determineArtifactStatus({ blockedReason = '', fatalError = '', reasons = [], rows = [] }) {
|
||||
if (blockedReason) return 'blocked';
|
||||
if (fatalError) return 'error';
|
||||
if (reasons.length > 0) return 'partial';
|
||||
if (!rows.length) return 'empty';
|
||||
return 'ok';
|
||||
}
|
||||
```
|
||||
|
||||
This should be placed in the template string between `normalizeRows` and `buildArtifact` functions.
|
||||
|
||||
- [ ] **Step 2: Verify the change**
|
||||
|
||||
```bash
|
||||
cargo build
|
||||
```
|
||||
|
||||
Expected: Build succeeds.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add src/generated_scene/generator.rs
|
||||
git commit -m "feat(generator): add determineArtifactStatus for complete status determination"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Enhance buildArtifact Function
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/generated_scene/generator.rs:334-353` (current `buildArtifact` function in template)
|
||||
|
||||
**Goal:** Enhance `buildArtifact` to use `determineArtifactStatus` and accept additional parameters.
|
||||
|
||||
- [ ] **Step 1: Replace buildArtifact function**
|
||||
|
||||
Replace the current `buildArtifact` function with enhanced version:
|
||||
|
||||
**Current:**
|
||||
```javascript
|
||||
function buildArtifact(args, rows) {
|
||||
return {
|
||||
type: 'report-artifact',
|
||||
report_name: '{scene_id}',
|
||||
status: rows.length > 0 ? 'ok' : 'empty',
|
||||
period: {
|
||||
mode: args.period_mode,
|
||||
mode_code: args.period_mode_code,
|
||||
value: args.period_value,
|
||||
payload: normalizePayload(args.period_payload)
|
||||
},
|
||||
org: { label: args.org_label, code: args.org_code },
|
||||
column_defs: COLUMN_DEFS,
|
||||
columns: {columns_json},
|
||||
rows,
|
||||
counts: { detail_rows: rows.length },
|
||||
partial_reasons: [],
|
||||
reasons: []
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
**Enhanced:**
|
||||
```javascript
|
||||
function buildArtifact({ status, blockedReason = '', fatalError = '', reasons = [], rows = [], args }) {
|
||||
return {
|
||||
type: 'report-artifact',
|
||||
report_name: REPORT_NAME,
|
||||
status: status || determineArtifactStatus({ blockedReason, fatalError, reasons, rows }),
|
||||
period: {
|
||||
mode: args.period_mode,
|
||||
mode_code: args.period_mode_code,
|
||||
value: args.period_value,
|
||||
payload: normalizePayload(args.period_payload)
|
||||
},
|
||||
org: { label: args.org_label, code: args.org_code },
|
||||
column_defs: COLUMN_DEFS,
|
||||
columns: COLUMNS,
|
||||
rows,
|
||||
counts: { detail_rows: rows.length },
|
||||
partial_reasons: reasons.filter(r => r && !r.startsWith('api_') && !r.startsWith('validation_')),
|
||||
reasons: Array.from(new Set(reasons.filter(Boolean)))
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify the change**
|
||||
|
||||
```bash
|
||||
cargo build
|
||||
```
|
||||
|
||||
Expected: Build succeeds.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add src/generated_scene/generator.rs
|
||||
git commit -m "feat(generator): enhance buildArtifact with determineArtifactStatus integration"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Enhance buildBrowserEntrypointResult Function
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/generated_scene/generator.rs:370-405` (current `buildBrowserEntrypointResult` function in template)
|
||||
|
||||
**Goal:** Complete rewrite of entrypoint with proper validation flow, page context check, and error handling.
|
||||
|
||||
- [ ] **Step 1: Replace buildBrowserEntrypointResult function**
|
||||
|
||||
Replace the entire `buildBrowserEntrypointResult` function:
|
||||
|
||||
**Current:**
|
||||
```javascript
|
||||
async function buildBrowserEntrypointResult(args, deps = defaultDeps) {
|
||||
const validation = validateArgs(args);
|
||||
if (!validation.valid) {
|
||||
return {
|
||||
type: 'report-artifact',
|
||||
report_name: '{scene_id}',
|
||||
status: 'error',
|
||||
error: 'Validation failed: ' + validation.errors.join(', '),
|
||||
column_defs: COLUMN_DEFS,
|
||||
columns: {columns_json},
|
||||
rows: [],
|
||||
counts: { detail_rows: 0 },
|
||||
partial_reasons: [],
|
||||
reasons: validation.errors
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const rawData = await (deps.queryData ? deps.queryData(args) : Promise.resolve([]));
|
||||
const rows = normalizeRows(rawData);
|
||||
return buildArtifact(args, rows);
|
||||
} catch (error) {
|
||||
return {
|
||||
type: 'report-artifact',
|
||||
report_name: '{scene_id}',
|
||||
status: 'error',
|
||||
error: error.message,
|
||||
column_defs: COLUMN_DEFS,
|
||||
columns: {columns_json},
|
||||
rows: [],
|
||||
counts: { detail_rows: 0 },
|
||||
partial_reasons: [],
|
||||
reasons: [error.message]
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Enhanced:**
|
||||
```javascript
|
||||
async function buildBrowserEntrypointResult(args, deps = defaultDeps) {
|
||||
// 1. Parameter validation
|
||||
const validation = validateArgs(args);
|
||||
if (!validation.valid) {
|
||||
return buildArtifact({
|
||||
status: 'blocked',
|
||||
blockedReason: 'validation_failed',
|
||||
reasons: validation.errors,
|
||||
rows: [],
|
||||
args
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Page context validation
|
||||
const pageValidation = typeof deps.validatePageContext === 'function'
|
||||
? deps.validatePageContext(args)
|
||||
: { ok: true };
|
||||
if (!pageValidation?.ok) {
|
||||
return buildArtifact({
|
||||
status: 'blocked',
|
||||
blockedReason: pageValidation?.reason || 'page_context_mismatch',
|
||||
reasons: [pageValidation?.reason || 'page_context_mismatch'],
|
||||
rows: [],
|
||||
args
|
||||
});
|
||||
}
|
||||
|
||||
// 3. Data fetching
|
||||
const reasons = [];
|
||||
let rawData = null;
|
||||
try {
|
||||
rawData = await (deps.queryData ? deps.queryData(args) : Promise.resolve([]));
|
||||
} catch (error) {
|
||||
return buildArtifact({
|
||||
status: 'error',
|
||||
fatalError: error.message,
|
||||
reasons: ['api_query_failed:' + error.message],
|
||||
rows: [],
|
||||
args
|
||||
});
|
||||
}
|
||||
|
||||
// 4. Row normalization
|
||||
const rows = normalizeRows(rawData);
|
||||
if (rows.length === 0 && Array.isArray(rawData) && rawData.length > 0) {
|
||||
reasons.push('row_normalization_partial');
|
||||
}
|
||||
|
||||
// 5. Build artifact
|
||||
return buildArtifact({ reasons, rows, args });
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify the change**
|
||||
|
||||
```bash
|
||||
cargo build
|
||||
```
|
||||
|
||||
Expected: Build succeeds.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add src/generated_scene/generator.rs
|
||||
git commit -m "feat(generator): enhance buildBrowserEntrypointResult with validation flow"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Add Helper Functions and Constants
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/generated_scene/generator.rs` (add helper functions to template)
|
||||
|
||||
**Goal:** Add utility functions used by the enhanced template.
|
||||
|
||||
- [ ] **Step 1: Add helper functions after COLUMN_DEFS constant**
|
||||
|
||||
Add these utility functions to the template after the constant definitions:
|
||||
|
||||
```javascript
|
||||
const REPORT_NAME = '{scene_id}';
|
||||
const COLUMNS = {columns_json};
|
||||
|
||||
function pickFirstNonEmpty(...values) {
|
||||
for (const value of values) {
|
||||
if (typeof value === 'string' && value.trim() !== '') {
|
||||
return value.trim();
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function isNonEmptyString(value) {
|
||||
return typeof value === 'string' && value.trim() !== '';
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify the change**
|
||||
|
||||
```bash
|
||||
cargo build
|
||||
```
|
||||
|
||||
Expected: Build succeeds.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add src/generated_scene/generator.rs
|
||||
git commit -m "feat(generator): add helper functions for enhanced template"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 7: Update Module Exports
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/generated_scene/generator.rs:407-409` (current module.exports in template)
|
||||
|
||||
**Goal:** Update module exports to include new functions.
|
||||
|
||||
- [ ] **Step 1: Update module.exports**
|
||||
|
||||
Replace the current export block:
|
||||
|
||||
**Current:**
|
||||
```javascript
|
||||
if (typeof module !== 'undefined') {
|
||||
module.exports = { buildBrowserEntrypointResult, normalizePayload, validateArgs, buildRequest, normalizeRows, buildArtifact, API_ENDPOINTS, STATIC_PARAMS, COLUMN_DEFS };
|
||||
}
|
||||
```
|
||||
|
||||
**Enhanced:**
|
||||
```javascript
|
||||
if (typeof module !== 'undefined') {
|
||||
module.exports = {
|
||||
buildBrowserEntrypointResult,
|
||||
normalizePayload,
|
||||
validateArgs,
|
||||
buildRequest,
|
||||
normalizeRows,
|
||||
determineArtifactStatus,
|
||||
buildArtifact,
|
||||
API_ENDPOINTS,
|
||||
STATIC_PARAMS,
|
||||
COLUMN_DEFS,
|
||||
COLUMNS,
|
||||
REPORT_NAME
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify the change**
|
||||
|
||||
```bash
|
||||
cargo build
|
||||
```
|
||||
|
||||
Expected: Build succeeds.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add src/generated_scene/generator.rs
|
||||
git commit -m "feat(generator): update module exports for enhanced template"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 8: Integration Test - Generate and Verify Script
|
||||
|
||||
**Files:**
|
||||
- Test: Generate a skill package and verify the output
|
||||
|
||||
**Goal:** Verify the enhanced template generates valid JavaScript.
|
||||
|
||||
- [ ] **Step 1: Build the project**
|
||||
|
||||
```bash
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
Expected: Build succeeds.
|
||||
|
||||
- [ ] **Step 2: Generate a test skill package**
|
||||
|
||||
Use the scene generator to create a test skill:
|
||||
|
||||
```bash
|
||||
# Assuming you have a test scene directory
|
||||
cargo run --bin sg_scene_generate -- --source-dir "examples/test-scene" --scene-id "test-enhanced" --scene-name "Test Enhanced" --output-root "tmp_test_enhanced" --scene-info-json '{"sceneId":"test-enhanced","sceneName":"Test Enhanced","apiEndpoints":[{"name":"testApi","url":"http://example.com/api/test","method":"POST"}],"staticParams":{},"columnDefs":[["col1","Column 1"]]}'
|
||||
```
|
||||
|
||||
Expected: Skill package generated without errors.
|
||||
|
||||
- [ ] **Step 3: Verify generated script syntax**
|
||||
|
||||
Check the generated JavaScript for syntax errors:
|
||||
|
||||
```bash
|
||||
node --check tmp_test_enhanced/skills/test-enhanced/scripts/collect_test_enhanced.js
|
||||
```
|
||||
|
||||
Expected: No syntax errors.
|
||||
|
||||
- [ ] **Step 4: Run the generated test**
|
||||
|
||||
```bash
|
||||
node tmp_test_enhanced/skills/test-enhanced/scripts/collect_test_enhanced.test.js
|
||||
```
|
||||
|
||||
Expected: Test passes (may fail on API call, but artifact structure should be valid).
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "test: verify enhanced template generates valid JavaScript"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Self-Review Checklist
|
||||
|
||||
**1. Spec Coverage:**
|
||||
- [x] URL construction bug fix → Task 1
|
||||
- [x] jQuery + fetch dual support → Task 2
|
||||
- [x] determineArtifactStatus function → Task 3
|
||||
- [x] Enhanced buildArtifact → Task 4
|
||||
- [x] Enhanced buildBrowserEntrypointResult → Task 5
|
||||
- [x] Helper functions → Task 6
|
||||
- [x] Module exports → Task 7
|
||||
- [x] Integration testing → Task 8
|
||||
|
||||
**2. Placeholder Scan:**
|
||||
- No TBD, TODO, or placeholder text found
|
||||
- All code snippets are complete
|
||||
- All commands have expected output
|
||||
|
||||
**3. Type Consistency:**
|
||||
- `buildArtifact` parameter signature consistent across all call sites
|
||||
- `args` object properties consistently named
|
||||
- Status values: blocked/error/partial/empty/ok consistently used
|
||||
|
||||
---
|
||||
|
||||
## Execution Handoff
|
||||
|
||||
Plan complete and saved to `docs/superpowers/plans/2026-04-17-progressive-template-enhancement-plan.md`. Two execution options:
|
||||
|
||||
**1. Subagent-Driven (recommended)** - I dispatch a fresh subagent per task, review between tasks, fast iteration
|
||||
|
||||
**2. Inline Execution** - Execute tasks in this session using executing-plans, batch execution with checkpoints
|
||||
|
||||
Which approach?
|
||||
Reference in New Issue
Block a user