diff --git a/CHANGELOG.md b/CHANGELOG.md index a3e8c0cf..a100cd0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Changed + +- Update generated and upgraded data apps to `@altertable/data-app` 0.65.0. + ## [1.9.0](https://github.com/altertable-ai/altertable-cli/compare/v1.8.0...v1.9.0) (2026-10-01) diff --git a/cli/src/commands/app/lib/distribution.test.ts b/cli/src/commands/app/lib/distribution.test.ts index 71997606..c30e09a0 100644 --- a/cli/src/commands/app/lib/distribution.test.ts +++ b/cli/src/commands/app/lib/distribution.test.ts @@ -40,7 +40,7 @@ test("distribution embeds a deterministic starter with an exact package pin and ); expect(files["src/App.tsx"]).toBe(payload.starter["src/App.tsx"]); expect(files["src/server.ts"]).toContain("@altertable/data-app/server/bun"); - expect(files["src/main.tsx"]).toContain("@altertable/data-app/react/styles.css"); + expect(files["src/main.tsx"]).toContain("injectDataAppStyles();"); expect(files["bun.lock"]).toContain('"name": "example"'); }); diff --git a/data-app/AGENTS.md b/data-app/AGENTS.md index 3cfe60bf..943e14f6 100644 --- a/data-app/AGENTS.md +++ b/data-app/AGENTS.md @@ -9,6 +9,6 @@ See [development commands](README.md#develop). Keep SQL and metric definitions in app-owned source. Import only public package exports; local Bun serving uses `/server/bun`, and each browser entry -imports `/react/styles.css`. Keep starter and browser-test package pins aligned, commit their +calls `injectDataAppStyles()` before mounting. Keep starter and browser-test package pins aligned, commit their lockfiles, and preserve documentation links to installed package docs. Do not copy runtime source into this repository or generated apps. diff --git a/data-app/README.md b/data-app/README.md index e2687f4d..c183c638 100644 --- a/data-app/README.md +++ b/data-app/README.md @@ -27,7 +27,7 @@ Generated projects commit their source, package manifest, and lockfile; they do source. Public imports use `@altertable/data-app/...`. The starter owns React, ReactDOM, and its authoring tools. The package owns its implementation -dependencies. Its stylesheet is imported explicitly by the browser entry. The app's `AGENTS.md` +dependencies. The browser entry calls `injectDataAppStyles()` before mounting. The app's `AGENTS.md` points to the installed package guide and docs; dependency guides are not assumed to load automatically. To adopt a package release, update its exact pin in both `starter/package.json` and diff --git a/data-app/starter/app.json b/data-app/starter/app.json index 818f4643..68e19974 100644 --- a/data-app/starter/app.json +++ b/data-app/starter/app.json @@ -3,7 +3,7 @@ "title": "Getting started", "scope": { "organization": "Your organization", "environment": "your environment" }, "appearance": { - "mode": "light", + "theme": "light", "baseColor": "neutral", "accentColor": "#405d47", "density": "comfortable", diff --git a/data-app/starter/bun.lock b/data-app/starter/bun.lock index 4c47ce3b..bf60d004 100644 --- a/data-app/starter/bun.lock +++ b/data-app/starter/bun.lock @@ -5,7 +5,7 @@ "": { "name": "data-app-starter", "dependencies": { - "@altertable/data-app": "0.61.0", + "@altertable/data-app": "0.65.0", "react": "19.3.0", "react-dom": "19.3.0", }, @@ -21,7 +21,7 @@ }, }, "packages": { - "@altertable/data-app": ["@altertable/data-app@0.61.0", "", { "dependencies": { "@floating-ui/react": "0.27.20", "@internationalized/date": "3.12.4", "@tanstack/react-query": "5.104.0", "fuzzysort": "4.0.2", "lucide-react": "1.48.0", "react-aria-components": "1.21.1" }, "peerDependencies": { "react": ">=19.2.0", "react-dom": ">=19.2.0" } }, "sha512-68nAEv/G0C7ZJ6P+Y0kAEBXEG4cMvEqORJYqR1/9dd+CeEYHMJDN4v20/7VUnYnEfz6reXJGyuKq3MMv6zbhpA=="], + "@altertable/data-app": ["@altertable/data-app@0.65.0", "", { "dependencies": { "@floating-ui/react": "0.27.20", "@internationalized/date": "3.12.4", "@tanstack/react-query": "5.104.0", "fflate": "0.8.3", "fuzzysort": "4.0.2", "lucide-react": "1.48.0", "react-aria-components": "1.21.1" }, "peerDependencies": { "react": ">=19.2.0", "react-dom": ">=19.2.0" } }, "sha512-BvaUwOhjUx0PzQbc8DUf5NUPpXL4VBcZ5LGegFB8Y41rQnmJIQk81AmP+Dg5iz1j/NwlsuiB8ABBUE6nY6e1aQ=="], "@floating-ui/core": ["@floating-ui/core@1.8.0", "", { "dependencies": { "@floating-ui/utils": "^0.2.12" } }, "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ=="], @@ -193,6 +193,8 @@ "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + "fflate": ["fflate@0.8.3", "", {}, "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA=="], + "fuzzysort": ["fuzzysort@4.0.2", "", {}, "sha512-3c7yD6rK1EXIsqpcu1ZVzXJugODWNMzvUij6yTiziaAHrpNS8kzFOI9mJLE8pmG4K9JOaVgzQRgklfOEk8f4Tg=="], "lucide-react": ["lucide-react@1.48.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-R0CIKY/fXiC6y9xRBADgsK+VW2p/pcTJOMhLZf1T+uG+vJUvyUR02nGOgmIIC7MPkiNK4Ox8QDnbqF8rJYWPZQ=="], diff --git a/data-app/starter/fixtures/main.tsx b/data-app/starter/fixtures/main.tsx index d3dd39fe..eb7ff00c 100644 --- a/data-app/starter/fixtures/main.tsx +++ b/data-app/starter/fixtures/main.tsx @@ -1,5 +1,4 @@ -import "@altertable/data-app/react/styles.css"; -import { mountDataApp } from "@altertable/data-app/react"; +import { injectDataAppStyles, mountDataApp } from "@altertable/data-app/react"; import { useState } from "react"; import { createDataHooks } from "@altertable/data-app/react"; import { createDataClient } from "@altertable/data-app/client"; @@ -30,7 +29,7 @@ const { defineDataView, useView } = createDataHooks<{ const config = { title: "Orders exploration", scope: { organization: "Acme", environment: "production" }, - appearance: { mode: "light" }, + appearance: { theme: "light" as const }, }; const identifiers = defineDataIdentifiers({ tables: { @@ -105,6 +104,10 @@ function Fixture() { } + csvExport={() => ({ + filename: "orders.csv", + tables: [{ name: "Orders", columns: ["Orders", "Customers"], rows: [[120, 80]] }], + })} story={() => [ { id: "orders", @@ -200,4 +203,5 @@ function Fixture() { ); } +injectDataAppStyles(); mountDataApp({ config, component: Fixture }); diff --git a/data-app/starter/fixtures/view.tsx b/data-app/starter/fixtures/view.tsx index 3d51b4c3..e9550dc8 100644 --- a/data-app/starter/fixtures/view.tsx +++ b/data-app/starter/fixtures/view.tsx @@ -1,7 +1,7 @@ -import "@altertable/data-app/react/styles.css"; import { createDataClient } from "@altertable/data-app/client"; import { createDataHooks, + injectDataAppStyles, mountDataApp, } from "@altertable/data-app/react"; import { @@ -24,7 +24,7 @@ import { type Activity = { count: number; features: string[] }; const config = { - appearance: { mode: "light" }, + appearance: { theme: "light" as const }, title: "Usage exploration", scope: { organization: "Acme", environment: "production" }, }; @@ -98,8 +98,24 @@ function Fixture() { request={activity} dataContext={context} aboutEmpty={{ glossary: { title: "No definitions" }, queries: { title: "No SQL" } }} + story={({ data }) => [ + context.finding({ + id: "activity", + headline: `${data.count} recorded actions`, + visual:

{data.features.join(", ")}

, + evidence: { id: "activity", queryNames: ["activity"] }, + }), + ]} + csvExport={({ data }) => ({ + filename: "activity.csv", + tables: [ + { name: "Actions", columns: ["Count"], rows: [[data.count]] }, + { name: "Features", columns: ["Feature"], rows: data.features.map((feature) => [feature]) }, + ], + })} {...content} /> ); } +injectDataAppStyles(); mountDataApp({ config, component: Fixture }); diff --git a/data-app/starter/package.json b/data-app/starter/package.json index f29418fb..961bda35 100644 --- a/data-app/starter/package.json +++ b/data-app/starter/package.json @@ -15,7 +15,7 @@ "build": "bun build src/index.html --outdir=dist --production" }, "dependencies": { - "@altertable/data-app": "0.61.0", + "@altertable/data-app": "0.65.0", "react": "19.3.0", "react-dom": "19.3.0" }, diff --git a/data-app/starter/src/App.tsx b/data-app/starter/src/App.tsx index a8c25502..e621cc0a 100644 --- a/data-app/starter/src/App.tsx +++ b/data-app/starter/src/App.tsx @@ -1,6 +1,6 @@ import { GettingStarted } from "@altertable/data-app/react"; import { dataContext } from "#app/data-context.ts"; -import app from "#config"; +import app from "#app/config.ts"; /** Connectivity-only screen. Investigate the requested question, then replace this with a view * that leads with a supported finding and explores the useful angles behind it. */ diff --git a/data-app/starter/src/config.ts b/data-app/starter/src/config.ts new file mode 100644 index 00000000..a7cfa69c --- /dev/null +++ b/data-app/starter/src/config.ts @@ -0,0 +1,9 @@ +import { parseAppearance } from "@altertable/data-app/appearance"; +import type { DataAppConfig } from "@altertable/data-app/config"; +import app from "#config"; + +export default { + title: app.title, + scope: app.scope, + appearance: parseAppearance(app.appearance), +} satisfies DataAppConfig; diff --git a/data-app/starter/src/main.tsx b/data-app/starter/src/main.tsx index 6d36cdf8..c6d1cca3 100644 --- a/data-app/starter/src/main.tsx +++ b/data-app/starter/src/main.tsx @@ -1,6 +1,6 @@ -import "@altertable/data-app/react/styles.css"; -import { mountDataApp } from "@altertable/data-app/react"; +import { injectDataAppStyles, mountDataApp } from "@altertable/data-app/react"; import { App } from "#app/App.tsx"; -import app from "#config"; +import app from "#app/config.ts"; +injectDataAppStyles(); mountDataApp({ config: app, component: App }); diff --git a/data-app/starter/src/server.ts b/data-app/starter/src/server.ts index 265bd0e4..a0a817b4 100644 --- a/data-app/starter/src/server.ts +++ b/data-app/starter/src/server.ts @@ -1,6 +1,6 @@ import page from "#app/index.html"; import { serveLocalApp } from "@altertable/data-app/server/bun"; import { operations } from "#app/operations.ts"; -import app from "#config"; +import app from "#app/config.ts"; serveLocalApp({ page, operations, title: app.title }); diff --git a/data-app/tests/bun.lock b/data-app/tests/bun.lock index fb858745..146cb2f8 100644 --- a/data-app/tests/bun.lock +++ b/data-app/tests/bun.lock @@ -5,7 +5,7 @@ "": { "name": "data-app-browser-tests", "dependencies": { - "@altertable/data-app": "0.61.0", + "@altertable/data-app": "0.65.0", }, "devDependencies": { "@playwright/test": "1.63.0", @@ -16,7 +16,7 @@ }, }, "packages": { - "@altertable/data-app": ["@altertable/data-app@0.61.0", "", { "dependencies": { "@floating-ui/react": "0.27.20", "@internationalized/date": "3.12.4", "@tanstack/react-query": "5.104.0", "fuzzysort": "4.0.2", "lucide-react": "1.48.0", "react-aria-components": "1.21.1" }, "peerDependencies": { "react": ">=19.2.0", "react-dom": ">=19.2.0" } }, "sha512-68nAEv/G0C7ZJ6P+Y0kAEBXEG4cMvEqORJYqR1/9dd+CeEYHMJDN4v20/7VUnYnEfz6reXJGyuKq3MMv6zbhpA=="], + "@altertable/data-app": ["@altertable/data-app@0.65.0", "", { "dependencies": { "@floating-ui/react": "0.27.20", "@internationalized/date": "3.12.4", "@tanstack/react-query": "5.104.0", "fflate": "0.8.3", "fuzzysort": "4.0.2", "lucide-react": "1.48.0", "react-aria-components": "1.21.1" }, "peerDependencies": { "react": ">=19.2.0", "react-dom": ">=19.2.0" } }, "sha512-BvaUwOhjUx0PzQbc8DUf5NUPpXL4VBcZ5LGegFB8Y41rQnmJIQk81AmP+Dg5iz1j/NwlsuiB8ABBUE6nY6e1aQ=="], "@floating-ui/core": ["@floating-ui/core@1.8.0", "", { "dependencies": { "@floating-ui/utils": "^0.2.12" } }, "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ=="], @@ -108,6 +108,8 @@ "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], + "fflate": ["fflate@0.8.3", "", {}, "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA=="], + "fuzzysort": ["fuzzysort@4.0.2", "", {}, "sha512-3c7yD6rK1EXIsqpcu1ZVzXJugODWNMzvUij6yTiziaAHrpNS8kzFOI9mJLE8pmG4K9JOaVgzQRgklfOEk8f4Tg=="], "lucide-react": ["lucide-react@1.48.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-R0CIKY/fXiC6y9xRBADgsK+VW2p/pcTJOMhLZf1T+uG+vJUvyUR02nGOgmIIC7MPkiNK4Ox8QDnbqF8rJYWPZQ=="], diff --git a/data-app/tests/package.json b/data-app/tests/package.json index 2285efc0..4753eb6c 100644 --- a/data-app/tests/package.json +++ b/data-app/tests/package.json @@ -15,6 +15,6 @@ "typescript": "7.0.2" }, "dependencies": { - "@altertable/data-app": "0.61.0" + "@altertable/data-app": "0.65.0" } } diff --git a/data-app/tests/starter.spec.ts b/data-app/tests/starter.spec.ts index b815ab99..266f0d35 100644 --- a/data-app/tests/starter.spec.ts +++ b/data-app/tests/starter.spec.ts @@ -147,7 +147,7 @@ test("data view keeps the last result visible when refresh fails", async ({ await page.goto("/components"); const error = page.getByRole("alert"); await expect(error).toContainText("Couldn’t load results"); - await expect(error).toContainText("The data request failed."); + await expect(error).toContainText("The lakehouse isn’t responding."); await expect(error.getByRole("button", { name: "Retry" })).toBeVisible(); expect( await error.evaluate((element) => element.getBoundingClientRect().width), @@ -197,10 +197,12 @@ test("table pages show bounded rows and reset when filtering changes", async ({ await page.getByRole("searchbox", { name: "Search orders" }).fill("Order"); await expect(pagination).toContainText("1–4 of 11 results"); await page.getByRole("searchbox", { name: "Search orders" }).fill("Order 5"); - await expect(pagination).toContainText("1–1 of 1 result"); - await expect(pagination.getByRole("button", { name: "Previous page" })).toBeDisabled(); - await expect(pagination.getByRole("button", { name: "Next page" })).toBeDisabled(); + await expect(pagination).toHaveCount(0); await expect(table.getByText("Order 5")).toBeVisible(); + await expect(table.locator("tbody tr")).toHaveCount(1); + await page.getByRole("searchbox", { name: "Search orders" }).fill(""); + await expect(pagination).toContainText("1–4 of 11 results"); + await expect(pagination.getByRole("button", { name: "Previous page" })).toBeDisabled(); }); test("request progress appears before Refresh", async ({ page }) => {