diff --git a/experimental/apps-mcp/cmd/init_template.go b/experimental/apps-mcp/cmd/init_template.go
index 92a4b2c8ba..7ee8f7937c 100644
--- a/experimental/apps-mcp/cmd/init_template.go
+++ b/experimental/apps-mcp/cmd/init_template.go
@@ -273,6 +273,13 @@ After initialization:
configFile := tmpFile.Name()
+ // Create output directory if specified and doesn't exist
+ if outputDir != "" {
+ if err := os.MkdirAll(outputDir, 0o755); err != nil {
+ return fmt.Errorf("create output directory: %w", err)
+ }
+ }
+
r := template.Resolver{
TemplatePathOrUrl: templatePathOrUrl,
ConfigFile: configFile,
diff --git a/experimental/apps-mcp/lib/prompts/apps.tmpl b/experimental/apps-mcp/lib/prompts/apps.tmpl
index 1988a4268f..9f9b710488 100644
--- a/experimental/apps-mcp/lib/prompts/apps.tmpl
+++ b/experimental/apps-mcp/lib/prompts/apps.tmpl
@@ -23,10 +23,9 @@ invoke_databricks_cli 'experimental apps-mcp tools validate ./your-app-location'
# Deployment
-⚠️ Always use the sequence of commands:
+⚠️ Use the deploy command which validates, deploys, and runs the app:
-invoke_databricks_cli 'bundle deploy'
-invoke_databricks_cli 'bundle run app'
+invoke_databricks_cli 'experimental apps-mcp tools deploy'
# View and manage your app:
diff --git a/experimental/apps-mcp/templates/appkit/template/{{.project_name}}/CLAUDE.md b/experimental/apps-mcp/templates/appkit/template/{{.project_name}}/CLAUDE.md
index 8a0ee216d0..2984603e28 100644
--- a/experimental/apps-mcp/templates/appkit/template/{{.project_name}}/CLAUDE.md
+++ b/experimental/apps-mcp/templates/appkit/template/{{.project_name}}/CLAUDE.md
@@ -21,7 +21,11 @@ The init-template command validates this automatically.
## TypeScript Import Rules
-This template uses strict TypeScript settings with `verbatimModuleSyntax: true`. **Always use `import type` for type-only imports**:
+This template uses strict TypeScript settings with `verbatimModuleSyntax: true`. **Always use `import type` for type-only imports**.
+
+Template enforces `noUnusedLocals` - remove unused imports immediately or build fails.
+
+**Type-only imports**:
```typescript
// ✅ CORRECT - use import type for types
@@ -166,6 +170,20 @@ WHERE DATE(timestamp_column) >= :start_date
- **Dates**: Format as `YYYY-MM-DD`, use with `DATE()` in SQL
- **Optional**: Use empty string default, check with `(:param = '' OR column = :param)`
+## SQL Type Handling
+
+Numeric fields from Databricks SQL (especially `ROUND()`, `AVG()`, `SUM()`) are returned as strings in JSON. Convert before using numeric methods:
+
+```typescript
+// ❌ WRONG - fails at runtime
+{row.total_amount.toFixed(2)}
+
+// ✅ CORRECT
+{Number(row.total_amount).toFixed(2)}
+```
+
+Use helpers from `shared/types.ts`: `toNumber()`, `formatCurrency()`, `formatPercent()`.
+
## tRPC for Custom Endpoints:
**CRITICAL**: Do NOT use tRPC for SQL queries or data retrieval. Use `config/queries/` + `useAnalyticsQuery` instead.
@@ -363,6 +381,10 @@ npm run test:e2e:ui # Run with Playwright UI
- Feature components: `client/src/components/FeatureName.tsx`
- Split components when logic exceeds ~100 lines or component is reused
+### Radix UI Constraints
+
+- `SelectItem` cannot have `value=""`. Use sentinel value like `"all"` for "show all" options.
+
### Best Practices:
- Use shadcn/radix components (Button, Input, Card, etc.) for consistent UI
diff --git a/experimental/apps-mcp/templates/appkit/template/{{.project_name}}/shared/types.ts b/experimental/apps-mcp/templates/appkit/template/{{.project_name}}/shared/types.ts
index 58308d05bd..a31ee4e47d 100644
--- a/experimental/apps-mcp/templates/appkit/template/{{.project_name}}/shared/types.ts
+++ b/experimental/apps-mcp/templates/appkit/template/{{.project_name}}/shared/types.ts
@@ -1,3 +1,13 @@
export interface QueryResult {
value: string;
}
+
+// SQL type helpers - numeric fields from Databricks SQL return as strings
+export const toNumber = (val: string | number | null | undefined): number =>
+ Number(val || 0);
+
+export const formatCurrency = (val: string | number | null | undefined): string =>
+ `$${toNumber(val).toFixed(2)}`;
+
+export const formatPercent = (val: string | number | null | undefined): string =>
+ `${toNumber(val).toFixed(1)}%`;
diff --git a/experimental/apps-mcp/templates/appkit/template/{{.project_name}}/tests/smoke.spec.ts b/experimental/apps-mcp/templates/appkit/template/{{.project_name}}/tests/smoke.spec.ts
index 3f3d1d6490..d712c69588 100644
--- a/experimental/apps-mcp/templates/appkit/template/{{.project_name}}/tests/smoke.spec.ts
+++ b/experimental/apps-mcp/templates/appkit/template/{{.project_name}}/tests/smoke.spec.ts
@@ -12,10 +12,10 @@ test('smoke test - app loads and displays data', async ({ page }) => {
// Navigate to the app
await page.goto('/');
- // Wait for the page title to be visible
+ // ⚠️ UPDATE THESE SELECTORS after customizing App.tsx:
+ // - Change heading name to match your app title
+ // - Change data selector to match your primary data display
await expect(page.getByRole('heading', { name: 'Minimal Databricks App' })).toBeVisible();
-
- // Wait for SQL query result to load (wait for "hello world" to appear)
await expect(page.getByText('hello world', { exact: true })).toBeVisible({ timeout: 30000 });
// Wait for health check to complete (wait for "OK" status)