feat(v1.1.3-modules): dashboard kind dropdown + scripts-list and detail badges

- `Script` type gains `kind: 'endpoint' | 'module'`. `CreateScriptInput`
  + `UpdateScriptInput` carry an optional `kind` field.
- App page's script-create form grows a kind dropdown next to Name +
  Description. Selecting "module" surfaces a hint that modules cannot
  bind to routes / triggers.
- Scripts list renders a small badge after the version: blue
  "endpoint" or purple "module".
- Script detail page renders the same badge next to the H1.

`npm run check` passes (0 errors, 0 warnings).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-02 22:26:07 +02:00
parent 66b41bb978
commit 610fd4ffa2
3 changed files with 91 additions and 2 deletions

View File

@@ -21,6 +21,8 @@ export interface ScriptSandbox {
max_expr_depth?: number; max_expr_depth?: number;
} }
export type ScriptKind = 'endpoint' | 'module';
export interface Script { export interface Script {
id: string; id: string;
app_id: string; app_id: string;
@@ -28,6 +30,8 @@ export interface Script {
description: string | null; description: string | null;
version: number; version: number;
source: string; source: string;
/** v1.1.3 — 'endpoint' (default) handles routes/triggers; 'module' is imported by other scripts. */
kind: ScriptKind;
timeout_seconds: number; timeout_seconds: number;
memory_limit_mb: number; memory_limit_mb: number;
sandbox: ScriptSandbox; sandbox: ScriptSandbox;
@@ -173,6 +177,8 @@ export interface CreateScriptInput {
name: string; name: string;
description?: string | null; description?: string | null;
source: string; source: string;
/** Defaults to 'endpoint' server-side if omitted. v1.1.3. */
kind?: ScriptKind;
timeout_seconds?: number; timeout_seconds?: number;
memory_limit_mb?: number; memory_limit_mb?: number;
} }
@@ -184,6 +190,8 @@ export interface UpdateScriptInput {
timeout_seconds?: number; timeout_seconds?: number;
memory_limit_mb?: number; memory_limit_mb?: number;
sandbox?: ScriptSandbox; sandbox?: ScriptSandbox;
/** v1.1.3 — endpoint→module rejected if routes/triggers reference the script. */
kind?: ScriptKind;
} }
export interface DeadLetterRow { export interface DeadLetterRow {

View File

@@ -63,6 +63,10 @@
let createScriptName = $state(''); let createScriptName = $state('');
let createScriptDescription = $state(''); let createScriptDescription = $state('');
let createScriptSource = $state(SAMPLE_SOURCE); let createScriptSource = $state(SAMPLE_SOURCE);
// v1.1.3: endpoint (default — handles routes/triggers) vs module
// (library imported by other scripts). Modules cannot be bound to
// routes or used as trigger targets.
let createScriptKind = $state<'endpoint' | 'module'>('endpoint');
let creatingScript = $state(false); let creatingScript = $state(false);
let createScriptError = $state<string | null>(null); let createScriptError = $state<string | null>(null);
@@ -201,12 +205,14 @@
app_id: app.id, app_id: app.id,
name: createScriptName.trim(), name: createScriptName.trim(),
description: createScriptDescription.trim() || null, description: createScriptDescription.trim() || null,
source: createScriptSource source: createScriptSource,
kind: createScriptKind
}); });
showCreateScript = false; showCreateScript = false;
createScriptName = ''; createScriptName = '';
createScriptDescription = ''; createScriptDescription = '';
createScriptSource = SAMPLE_SOURCE; createScriptSource = SAMPLE_SOURCE;
createScriptKind = 'endpoint';
await loadScripts(app.id); await loadScripts(app.id);
} catch (e) { } catch (e) {
createScriptError = e instanceof Error ? e.message : String(e); createScriptError = e instanceof Error ? e.message : String(e);
@@ -473,6 +479,13 @@
<span>Name</span> <span>Name</span>
<input bind:value={createScriptName} required placeholder="echo" /> <input bind:value={createScriptName} required placeholder="echo" />
</label> </label>
<label>
<span>Kind</span>
<select bind:value={createScriptKind}>
<option value="endpoint">Endpoint (handles HTTP / triggers)</option>
<option value="module">Module (imported by other scripts)</option>
</select>
</label>
<label> <label>
<span>Description</span> <span>Description</span>
<input bind:value={createScriptDescription} placeholder="optional" /> <input bind:value={createScriptDescription} placeholder="optional" />
@@ -482,6 +495,13 @@
<span>Source (Rhai)</span> <span>Source (Rhai)</span>
<CodeEditor bind:value={createScriptSource} language="rhai" minHeight="14rem" /> <CodeEditor bind:value={createScriptSource} language="rhai" minHeight="14rem" />
</label> </label>
{#if createScriptKind === 'module'}
<p class="muted small">
Modules expose <code>fn</code> and <code>const</code> declarations to other
scripts via <code>import "name" as alias;</code>. They cannot be bound to
routes or used as trigger targets.
</p>
{/if}
{#if createScriptError} {#if createScriptError}
<div class="error">{createScriptError}</div> <div class="error">{createScriptError}</div>
{/if} {/if}
@@ -503,6 +523,11 @@
<div class="primary"> <div class="primary">
<strong>{script.name}</strong> <strong>{script.name}</strong>
<span class="muted">v{script.version}</span> <span class="muted">v{script.version}</span>
{#if script.kind === 'module'}
<span class="kind-badge kind-module" title="Library imported by other scripts">module</span>
{:else}
<span class="kind-badge kind-endpoint" title="Handles HTTP routes and trigger events">endpoint</span>
{/if}
</div> </div>
<div class="secondary muted">{script.description ?? '—'}</div> <div class="secondary muted">{script.description ?? '—'}</div>
</a> </a>
@@ -1154,4 +1179,30 @@
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
} }
.kind-badge {
display: inline-block;
padding: 0 0.45rem;
margin-left: 0.5rem;
border-radius: 0.25rem;
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
line-height: 1.5;
}
.kind-endpoint {
background: #1e3a5f;
color: #93c5fd;
}
.kind-module {
background: #3f2e7d;
color: #c4b5fd;
}
.small {
font-size: 0.85rem;
}
</style> </style>

View File

@@ -433,7 +433,14 @@
<code>{script.name}</code> <code>{script.name}</code>
</div> </div>
{/if} {/if}
<h1>{script.name}</h1> <h1>
{script.name}
{#if script.kind === 'module'}
<span class="kind-badge kind-module" title="Library imported by other scripts">module</span>
{:else}
<span class="kind-badge kind-endpoint" title="Handles HTTP routes and trigger events">endpoint</span>
{/if}
</h1>
<p class="muted"> <p class="muted">
v{script.version} · timeout {script.timeout_seconds}s · {script.description ?? 'no description'} v{script.version} · timeout {script.timeout_seconds}s · {script.description ?? 'no description'}
</p> </p>
@@ -1323,4 +1330,27 @@
margin: 0.25rem 0 0 4rem; margin: 0.25rem 0 0 4rem;
font-size: 0.75rem; font-size: 0.75rem;
} }
.kind-badge {
display: inline-block;
padding: 0 0.45rem;
margin-left: 0.5rem;
border-radius: 0.25rem;
font-size: 0.65rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
line-height: 1.5;
vertical-align: middle;
}
.kind-endpoint {
background: #1e3a5f;
color: #93c5fd;
}
.kind-module {
background: #3f2e7d;
color: #c4b5fd;
}
</style> </style>