diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5c0bb0f..a226a42 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,6 +48,8 @@ jobs: working-directory: webui/frontend - name: Frontend tests run: make webui-test + - name: Dist freshness (issue #80) + run: make webui-verify-dist # Integration coverage lives in ci-lakebase.yml: this project is Lakebase-only, and # the integration suite runs against an ephemeral Lakebase branch (real lakebase_ann / diff --git a/Makefile b/Makefile index a780241..c71fb7c 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .DEFAULT_GOAL := help -.PHONY: install run test test-integration lint fmt fmt-check requirements clean help migrate migration set-secrets deploy deploy-prod smoke index destroy diagrams webui-wheel webui-build webui-test +.PHONY: install run test test-integration lint fmt fmt-check requirements clean help migrate migration set-secrets deploy deploy-prod smoke index destroy diagrams webui-wheel webui-build webui-test webui-verify-dist # Secret scope/key for `set-secrets`. These MUST match the bundle variables # `github_token_secret_scope` / `github_token_secret_key` in databricks.yml @@ -109,6 +109,18 @@ webui-build: ## Build the webui frontend (npm ci + vite build) into webui/fronte webui-test: ## Run the webui frontend test suite (vitest; gated in CI's webui job, needs npm ci first) cd webui/frontend && npm test +webui-verify-dist: ## Rebuild the frontend and fail if the committed dist/ is stale (CI freshness gate; issue #80) + cd webui/frontend && npm ci && npm run build + # Must run from the repo root: the git pathspec below is repo-root-relative. The `cd` on the + # build line above does not leak here — each recipe line runs in its own shell. + @status=$$(git status --porcelain -- webui/frontend/dist); \ + if [ -n "$$status" ]; then \ + echo "ERROR: webui/frontend/dist/ is stale relative to src/ — run 'make webui-build' and commit the result."; \ + echo "$$status"; \ + git --no-pager diff --stat -- webui/frontend/dist; \ + exit 1; \ + fi + destroy: ## Tear down the whole bundle for TARGET (typed-confirm; irreversible Lakebase data loss) bash scripts/deploy.sh destroy $(TARGET) diff --git a/README.md b/README.md index c8f394e..5fd2a4a 100644 --- a/README.md +++ b/README.md @@ -603,6 +603,7 @@ For the webui frontend specifically (requires Node): ```bash make webui-build # npm ci + vite build -> webui/frontend/dist/ (commit the result) make webui-test # vitest; advisory, not a repo gate +make webui-verify-dist # rebuild + fail if committed dist/ is stale (CI freshness gate; issue #80) ``` This project is **Lakebase-only**: there is no local/CI Postgres image. The integration diff --git a/webui/AGENTS.md b/webui/AGENTS.md index ac513f4..f535030 100644 --- a/webui/AGENTS.md +++ b/webui/AGENTS.md @@ -26,7 +26,7 @@ The second Databricks App in this bundle: a browser-facing search UI over the sa ### Working In This Directory - **The wheel mechanism is sync-critical.** `main.py` imports `app.*` from the wheel installed via `pyproject.toml`, not from sibling source. `scripts/deploy.sh` runs `make webui-wheel` before `bundle deploy`; never hand-place or commit wheels, and never add a `requirements.txt` here — "requirements.txt always takes precedence" on the Apps runtime and would force pip + Python 3.11, on which the wheel (`requires-python >=3.12`) refuses to install (the failure modes are documented in `pyproject.toml`/`app.yaml` comments). -- **`frontend/dist/` is committed** — never hand-edit it; rebuild with `make webui-build` and commit the result. There is no build-time check that `dist/` matches `frontend/src/`. +- **`frontend/dist/` is committed** — never hand-edit it; rebuild with `make webui-build` and commit the result. CI's `webui` job enforces freshness via `make webui-verify-dist` — a stale `dist/` fails the build. - **Auth model:** standard Databricks Apps workspace auth — any identity with `CAN_USE` on the app resource can use it. No OAuth app connection (that prerequisite is specific to the MCP app's `/mcp` transport). Never add auth logic here. - **Read-only DB access:** the app SP gets `SELECT`/`USAGE` only (`scripts/migrate.py --apply-grants`). Routes must never write; `/ready` is the grant oracle (missing grant → 503). - **Error contract:** recoverable conditions (parse errors, semantic disabled/schema-missing) are payload fields, never exceptions — only `CursorError`, `DataError` (NUL-byte inputs → 400), and backend faults (→ 502, generic body, detail logged server-side only) become HTTP errors. Preserve the no-leak policy: never echo raw DB/SDK errors in response bodies. diff --git a/webui/frontend/AGENTS.md b/webui/frontend/AGENTS.md index 93882e7..2d58cf6 100644 --- a/webui/frontend/AGENTS.md +++ b/webui/frontend/AGENTS.md @@ -25,7 +25,7 @@ Vite + React 18 + TypeScript SPA for the code-search web UI. Talks to the FastAP ## For AI Agents ### Working In This Directory -- **`dist/` is committed build output — never hand-edit it.** After any `src/` (or config) change, run `make webui-build` (`npm ci && npm run build`) and commit the resulting `dist/` diff. There is no build-time check that `dist/` and `src/` are in sync; a stale `dist/` ships silently. +- **`dist/` is committed build output — never hand-edit it.** After any `src/` (or config) change, run `make webui-build` (`npm ci && npm run build`) and commit the resulting `dist/` diff. CI's `webui` job enforces freshness via `make webui-verify-dist` — a stale `dist/` fails the build. - The build must stay self-contained and same-origin: no absolute API base URLs (the client uses relative `/api/...` paths), no new runtime CDN/network dependencies. - Keep the dependency budget: prefer hand-rolled solutions (`router.ts`, `theme.ts`) over adding a router/state/fetch library. Shiki must stay on the fine-grained `shiki/core` API with static per-language imports — importing top-level `shiki` pulls the full ~80-language bundle because Vite cannot tree-shake a runtime `bundledLanguages[key]` lookup. - Deep links work because the backend falls back to `index.html` for non-`/api` 404s; new client routes need a matching case in `src/router.ts`, nothing server-side. diff --git a/webui/frontend/README.md b/webui/frontend/README.md index 8650c3e..897cc41 100644 --- a/webui/frontend/README.md +++ b/webui/frontend/README.md @@ -19,7 +19,7 @@ npm run build # tsc -b && vite build -> dist/ `dist/` is committed to the repo (see the root `.gitignore` negation) because DABs source sync respects `.gitignore`, and CI does not run a Node build step for v1 — `webui/main.py` serves this directory directly via `SPAStaticFiles`. **Rebuild and commit `dist/` whenever `src/` -changes**; there is no build-time check that they're in sync. +changes**; CI's `webui` job enforces dist freshness via `make webui-verify-dist`. ## Test