fix: turn the default raw app into a feature tour (#11417)

* feat: turn the default raw app into a feature tour

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix: don't present ctx fields as unforgeable in the starter app

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore: format raw app templates

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Diego Imbert
2026-09-29 19:24:35 +02:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 05bcc361af
commit 83b8954ae2
5 changed files with 1223 additions and 106 deletions
@@ -53,8 +53,7 @@ import { evalValue } from '$lib/components/flows/utils.svelte'
import { updateRawAppPolicy } from '$lib/components/raw_apps/rawAppPolicy'
import {
FRAMEWORK_TEMPLATES,
STARTER_RUNNABLE,
STARTER_RUNNABLE_KEY,
STARTER_RUNNABLES,
type FrameworkKey
} from '$lib/components/raw_apps/templates'
import {
@@ -6411,13 +6410,13 @@ async function initApp(
const value: AppDraftValue = {
summary,
files: { ...template },
runnables: { [STARTER_RUNNABLE_KEY]: { ...STARTER_RUNNABLE } }
runnables: structuredClone(STARTER_RUNNABLES)
}
await recomputeAppPolicy(value)
const result = await saveAppDraft(workspace, path, value)
return finishAppDraftWrite(result, ctx, () => ({
content: `Saved app "${path}" draft (${framework})`,
message: `Initialized a per-user draft app "${path}" from the ${framework} template with a starter runnable "${STARTER_RUNNABLE_KEY}" (saved server-side, not a deployed workspace item). Use write_app_file / write_app_runnable to evolve it.`
message: `Initialized a per-user draft app "${path}" from the ${framework} template (saved server-side, not a deployed workspace item). The template is a demo tour with starter runnables ${Object.keys(STARTER_RUNNABLES).join(', ')}: replace its UI and delete the runnables the app does not need (delete_app_runnable). Use write_app_file / write_app_runnable to evolve it.`
}))
}
File diff suppressed because it is too large Load Diff
@@ -50,6 +50,23 @@ describe('genWmillTs', () => {
expect(dts).toContain('myflow: (args: { string_input: string }) => Promise<any>;')
})
it('leaves ctx fields out of the args type, the server fills them', () => {
const runnables: Record<string, Runnable> = {
myflow: {
type: 'path',
runType: 'flow',
path: 'u/dev/my_flow',
name: 'My flow',
schema: flowSchema,
fields: {
string_input: { type: 'ctx', ctx: 'username', fieldType: 'text' }
}
}
}
expect(genWmillTs(runnables)).toContain('myflow: (args: { count?: number }) => Promise<any>;')
})
it('quotes flow input names that are not valid identifiers', () => {
const runnables: Record<string, Runnable> = {
myflow: {
+12 -4
View File
@@ -291,8 +291,14 @@ export function unsandboxedRawAppHtml(
</html>`
}
function removeStaticFields(schema: Schema, fields: Record<string, { type: string }>): Schema {
const staticFields = Object.keys(fields).filter((k) => fields[k].type == 'static')
// The app fills static and ctx fields itself, so the frontend never passes them.
function removeServerFilledFields(
schema: Schema,
fields: Record<string, { type: string }>
): Schema {
const staticFields = Object.keys(fields).filter(
(k) => fields[k].type == 'static' || fields[k].type == 'ctx'
)
return {
...schema,
properties: {
@@ -307,14 +313,16 @@ function hiddenRunnableToTsType(runnable: Runnable) {
if (isRunnableByName(runnable)) {
if (runnable?.inlineScript?.schema) {
return schemaToTsType(
removeStaticFields(runnable?.inlineScript?.schema, runnable?.fields ?? {})
removeServerFilledFields(runnable?.inlineScript?.schema, runnable?.fields ?? {})
)
} else {
return '{}'
}
} else if (isRunnableByPath(runnable)) {
if (runnable?.schema) {
return schemaToTsType(removeStaticFields(runnable.schema as Schema, runnable?.fields ?? {}))
return schemaToTsType(
removeServerFilledFields(runnable.schema as Schema, runnable?.fields ?? {})
)
} else {
return '{}'
}
@@ -30,11 +30,7 @@
import RawAppTemplatePicker, {
type RawAppTemplatePickerResult
} from '$lib/components/raw_apps/RawAppTemplatePicker.svelte'
import {
react19Template,
STARTER_RUNNABLE,
STARTER_RUNNABLE_KEY
} from '$lib/components/raw_apps/templates'
import { react19Template, STARTER_RUNNABLES } from '$lib/components/raw_apps/templates'
import { aiChatManager, AIMode } from '$lib/components/copilot/chat/AIChatManager.svelte'
type RawAppDraft = {
@@ -273,7 +269,7 @@
// Seed the React 19 template so the editor has a usable state even if the
// user dismisses the picker without selecting.
const seedFiles = { ...react19Template }
const seedRunnables = { [STARTER_RUNNABLE_KEY]: STARTER_RUNNABLE }
const seedRunnables = structuredClone(STARTER_RUNNABLES)
savedApp = {
summary: '',
value: { files: seedFiles as any, runnables: seedRunnables as any },
@@ -524,7 +520,7 @@
function onTemplatePickerStart(result: RawAppTemplatePickerResult, withPrompt: boolean) {
files = { ...result.files }
runnables = { ...result.runnables, [STARTER_RUNNABLE_KEY]: STARTER_RUNNABLE }
runnables = { ...result.runnables, ...structuredClone(STARTER_RUNNABLES) }
data = result.data
summary = result.summary
policy = result.policy