Widgets bind to a message name and read it live off the socket the editor already had — lifted out of the flow editor so a dashboard route gets the same values, which also gives the home page live data for free. The input widgets close the loop the other way: a slider publishes into the graph and whatever consumes that message runs. Verified end to end in the running app — moving a slider set a flow input, and the stat bound to what the flow computed from it followed. View mode is plain CSS grid. A wall panel that only displays should not download the code that lets someone drag things around, and it now does not. Editing is a widget picker, a per-widget width control and a settings card fed by the message catalog. No new dependencies: the slider is a range input, the gauge is an arc, and the markdown is a five-line subset. Charts are the one widget still missing — they need a charting library and the chart tokens the design guidelines reserved — so they are stored and validated but not offered. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011LF61rxW1FG5YCD2J9YqjY
79 lines
3.0 KiB
JavaScript
79 lines
3.0 KiB
JavaScript
/** Feature check: build a dashboard, read a live value, move a control. */
|
|
import { mkdir } from "node:fs/promises"
|
|
import { chromium } from "@playwright/test"
|
|
|
|
const APP_URL = process.env.APP_URL || "http://app.localhost"
|
|
const EMAIL = process.env.FIRST_SUPERUSER
|
|
const PASSWORD = process.env.FIRST_SUPERUSER_PASSWORD
|
|
const OUT = process.env.SCREENSHOT_DIR || "screenshots"
|
|
const NAME = process.env.DASHBOARD_NAME || "house"
|
|
|
|
const browser = await chromium.launch()
|
|
for (const theme of ["light", "dark"]) {
|
|
const dir = `${OUT}/${theme}`
|
|
await mkdir(dir, { recursive: true })
|
|
const context = await browser.newContext({
|
|
viewport: { width: 1440, height: 900 },
|
|
colorScheme: theme,
|
|
})
|
|
await context.addInitScript((t) => {
|
|
localStorage.setItem("fluksio-ui-theme", t)
|
|
}, theme)
|
|
const page = await context.newPage()
|
|
|
|
await page.goto(`${APP_URL}/login`, { waitUntil: "networkidle" })
|
|
await page.getByTestId("email-input").fill(EMAIL)
|
|
await page.getByTestId("password-input").fill(PASSWORD)
|
|
await page.getByRole("button", { name: /log in/i }).click()
|
|
await page.waitForURL(`${APP_URL}/`, { timeout: 15000 })
|
|
|
|
await page.goto(`${APP_URL}/dashboards`, { waitUntil: "networkidle" })
|
|
|
|
// Light builds it; dark just looks at what light left behind.
|
|
if (theme === "light") {
|
|
await page.getByTestId("new-dashboard-name").fill(NAME)
|
|
await page.getByTestId("create-dashboard").click()
|
|
await page.waitForURL(/\/dashboards\/.+/, { timeout: 15000 })
|
|
|
|
// A reading, a dial and a control over the same message.
|
|
for (const [kind, message] of [
|
|
["stat", "heating.applied"],
|
|
["gauge", "heating.setpoint"],
|
|
["slider", "heating.setpoint"],
|
|
]) {
|
|
await page.getByTestId(`add-widget-${kind}`).click()
|
|
await page.waitForSelector("[data-testid=widget-settings]")
|
|
await page.getByTestId("widget-message").click()
|
|
await page.getByRole("option", { name: message }).click()
|
|
await page.waitForTimeout(400)
|
|
}
|
|
await page.waitForTimeout(1500) // let the autosave land
|
|
} else {
|
|
await page.getByTestId("dashboard-card").first().click()
|
|
await page.waitForURL(/\/dashboards\/.+/, { timeout: 15000 })
|
|
}
|
|
|
|
// View mode is what a panel shows.
|
|
const done = page.getByTestId("toggle-edit")
|
|
if ((await done.textContent())?.includes("Done")) await done.click()
|
|
await page.waitForTimeout(1200)
|
|
await page.screenshot({ path: `${dir}/app-dashboard-view.png` })
|
|
|
|
if (theme === "light") {
|
|
// Move the slider and confirm the value actually reached the engine.
|
|
const slider = page.locator('input[type="range"]').first()
|
|
await slider.click()
|
|
await slider.press("ArrowRight")
|
|
await page.waitForTimeout(1500)
|
|
await page.screenshot({ path: `${dir}/app-dashboard-control.png` })
|
|
|
|
await page.getByTestId("toggle-edit").click()
|
|
await page.waitForTimeout(1000)
|
|
await page.screenshot({ path: `${dir}/app-dashboard-edit.png` })
|
|
}
|
|
|
|
console.log(` ${theme}: done`)
|
|
await context.close()
|
|
}
|
|
await browser.close()
|