diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 047645c..f3076d9 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "1.25.0"
+ ".": "1.26.0"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 309a3ca..27527cb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,18 @@
# Changelog
+## [1.26.0](https://github.com/dograh-hq/dograh/compare/dograh-v1.25.0...dograh-v1.26.0) (2026-04-21)
+
+
+### Features
+
+* refactor node spec and add mcp tools ([#244](https://github.com/dograh-hq/dograh/issues/244)) ([00a1a22](https://github.com/dograh-hq/dograh/commit/00a1a22b749dab5b828ee529d7272cbdaaeb9aca))
+
+
+### Bug Fixes
+
+* compare dirty against correct baseline ([6606a7f](https://github.com/dograh-hq/dograh/commit/6606a7f901f351d5832ebc27a0900c1195a4090c))
+* fix slack community URL ([86026f5](https://github.com/dograh-hq/dograh/commit/86026f5c6ffdbfbef96cec8aff6c4265f061b77e))
+
## [1.25.0](https://github.com/dograh-hq/dograh/compare/dograh-v1.24.0...dograh-v1.25.0) (2026-04-17)
diff --git a/api/pyproject.toml b/api/pyproject.toml
index a8c71eb..bb1bae8 100644
--- a/api/pyproject.toml
+++ b/api/pyproject.toml
@@ -1,5 +1,5 @@
[project]
name = "dograh-api"
-version = "1.25.0"
+version = "1.26.0"
description = "Backend API for Dograh voice AI platform"
requires-python = ">=3.12"
diff --git a/docs/deployment/update.mdx b/docs/deployment/update.mdx
new file mode 100644
index 0000000..d4dc3a1
--- /dev/null
+++ b/docs/deployment/update.mdx
@@ -0,0 +1,113 @@
+---
+title: "Update"
+description: "Update your self-hosted Dograh stack to a newer image version"
+---
+
+This guide covers updating a Dograh stack you've already deployed with [Docker](/deployment/docker) or a [custom domain](/deployment/custom-domain). You run commands from the same directory that contains your `docker-compose.yaml` (this is the `dograh/` directory if you used `setup_remote.sh`).
+
+## Find an image version
+
+Dograh publishes two images — `dograh-api` and `dograh-ui` — to both container registries:
+
+- **GitHub Container Registry** — [github.com/orgs/dograh-hq/packages](https://github.com/orgs/dograh-hq/packages)
+- **Docker Hub** — [hub.docker.com/u/dograhai](https://hub.docker.com/u/dograhai)
+
+Each release is published under two kinds of tags:
+
+| Tag style | Example | When to use |
+|-----------|---------|-------------|
+| **Release tag** | `v0.8.2` | Stable, recommended for production |
+| **Git commit SHA** | `a1b2c3d` | Bleeding edge — any commit merged to `main` |
+| `latest` | `latest` | Tracks the most recent release tag |
+
+
+Always update **`dograh-api`** and **`dograh-ui`** to the **same tag**. The two images are built from the same commit and the UI expects API responses in a matching shape — mixing versions will break the app.
+
+
+## Option A: Update to the latest release
+
+If your `docker-compose.yaml` uses `:latest` (the default), just pull and restart:
+
+
+```bash Local deployment
+docker compose down
+docker compose up --pull always
+```
+```bash Remote deployment
+cd dograh
+sudo docker compose --profile remote down
+sudo docker compose --profile remote up --pull always
+```
+
+
+`--pull always` forces Docker to fetch the latest `:latest` from the registry instead of reusing your cached image.
+
+## Option B: Pin a specific tag
+
+To update (or roll back) to a specific release or commit, edit `docker-compose.yaml` and change the `image:` lines for both `api` and `ui` services to the same tag.
+
+Open the file:
+
+```bash
+nano docker-compose.yaml
+```
+
+Find these two lines:
+
+```yaml
+ api:
+ image: ${REGISTRY:-dograhai}/dograh-api:latest
+ ui:
+ image: ${REGISTRY:-dograhai}/dograh-ui:latest
+```
+
+Replace `:latest` with your chosen tag on **both** services — for example:
+
+```yaml
+ api:
+ image: ${REGISTRY:-dograhai}/dograh-api:v0.8.2
+ ui:
+ image: ${REGISTRY:-dograhai}/dograh-ui:v0.8.2
+```
+
+
+You can use either registry. Leave `REGISTRY` unset for Docker Hub (`dograhai`), or export `REGISTRY=ghcr.io/dograh-hq` to pull from GitHub Container Registry.
+
+
+Then bring the stack down and back up:
+
+
+```bash Local deployment
+docker compose down
+docker compose up --pull always
+```
+```bash Remote deployment
+cd dograh
+sudo docker compose --profile remote down
+sudo docker compose --profile remote up --pull always
+```
+
+
+## Verify the update
+
+Check the running image tags:
+
+```bash
+docker compose ps --format "table {{.Service}}\t{{.Image}}"
+```
+
+You should see the API and UI both running the tag you pinned.
+
+Hit the health endpoint to confirm the API is responding:
+
+```bash
+curl http://localhost:8000/api/v1/health
+```
+
+## Roll back
+
+If something breaks, roll back by pinning the previous tag using the same process in **Option B** and restarting. Your Postgres data volume persists across `down`/`up` cycles, so agents and call history are preserved.
+
+
+Rolling back across a database migration is not always safe — if the newer release ran a schema migration, downgrading may leave the DB in a state the older API doesn't understand. If in doubt, [open an issue](https://github.com/dograh-hq/dograh/issues) before rolling back.
+
diff --git a/docs/docs.json b/docs/docs.json
index 95fa29a..ddb4405 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -141,6 +141,7 @@
"deployment/introduction",
"deployment/docker",
"deployment/custom-domain",
+ "deployment/update",
"deployment/web-widget",
"deployment/heroku"
]
diff --git a/scripts/rolling_update.sh b/scripts/rolling_update.sh
index d39e603..244933f 100755
--- a/scripts/rolling_update.sh
+++ b/scripts/rolling_update.sh
@@ -217,6 +217,15 @@ if ! alembic -c "$BASE_DIR/api/alembic.ini" upgrade head; then
fi
log_info "Migrations complete"
+TS_VALIDATOR_DIR="$BASE_DIR/api/mcp_server/ts_validator"
+if [[ -f "$TS_VALIDATOR_DIR/package.json" ]]; then
+ log_info "Installing ts_validator npm dependencies"
+ if ! (cd "$TS_VALIDATOR_DIR" && npm install); then
+ log_error "npm install for ts_validator failed. Aborting — nothing has been touched."
+ exit 1
+ fi
+fi
+
###############################################################################
### PHASE 2: START NEW WORKERS
###############################################################################
diff --git a/scripts/start_services.sh b/scripts/start_services.sh
index 4ee9148..c706afb 100755
--- a/scripts/start_services.sh
+++ b/scripts/start_services.sh
@@ -127,7 +127,16 @@ NGINX_UPSTREAM_TEMPLATE="$BASE_DIR/nginx/dograh_upstream.conf.template"
NGINX_UPSTREAM_CONF="/etc/nginx/conf.d/dograh_upstream.conf"
###############################################################################
-### 4) Run migrations
+### 4) Install ts_validator npm dependencies
+###############################################################################
+
+TS_VALIDATOR_DIR="$BASE_DIR/api/mcp_server/ts_validator"
+if [[ -f "$TS_VALIDATOR_DIR/package.json" ]]; then
+ (cd "$TS_VALIDATOR_DIR" && npm install)
+fi
+
+###############################################################################
+### 5) Run migrations
###############################################################################
alembic -c "$BASE_DIR/api/alembic.ini" upgrade head
diff --git a/ui/package.json b/ui/package.json
index f2bbccb..002e7d7 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -1,6 +1,6 @@
{
"name": "ui",
- "version": "1.25.0",
+ "version": "1.26.0",
"private": true,
"scripts": {
"dev": "cross-env NODE_OPTIONS=--enable-source-maps next dev --turbopack",
diff --git a/ui/src/components/flow/nodes/GenericNode.tsx b/ui/src/components/flow/nodes/GenericNode.tsx
index b8b395e..fcd7fee 100644
--- a/ui/src/components/flow/nodes/GenericNode.tsx
+++ b/ui/src/components/flow/nodes/GenericNode.tsx
@@ -369,9 +369,10 @@ export const GenericNode = memo(({ data, selected, id, type }: GenericNodeProps)
);
const isDirty = useMemo(() => {
- const d = data as unknown as Record;
- return propertyNames.some((n) => values[n] !== d[n]);
- }, [values, data, propertyNames]);
+ if (!spec) return false;
+ const baseline = seedValues(data, spec);
+ return propertyNames.some((n) => values[n] !== baseline[n]);
+ }, [values, data, spec, propertyNames]);
const handleSave = async () => {
if (!spec) return;
diff --git a/ui/src/components/layout/AppSidebar.tsx b/ui/src/components/layout/AppSidebar.tsx
index 62685e0..8e5870c 100644
--- a/ui/src/components/layout/AppSidebar.tsx
+++ b/ui/src/components/layout/AppSidebar.tsx
@@ -2,6 +2,7 @@
import type { Team } from "@stackframe/stack";
import {
+ ArrowUpCircle,
AudioLines,
Brain,
ChevronLeft,
@@ -54,6 +55,7 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { useAppConfig } from "@/context/AppConfigContext";
+import { useLatestReleaseVersion } from "@/hooks/useLatestReleaseVersion";
import type { LocalUser } from "@/lib/auth";
import { useAuth } from "@/lib/auth";
import { cn } from "@/lib/utils";
@@ -89,6 +91,12 @@ export function AppSidebar() {
// Version info from app config context
const versionInfo = config ? { ui: config.uiVersion, api: config.apiVersion } : null;
+ // Check for updates only on self-hosted (OSS) deployments — cloud is managed for the user.
+ const { latest: latestRelease, isBehind, isLatest } = useLatestReleaseVersion(
+ versionInfo?.ui,
+ { enabled: config?.deploymentMode === "oss" },
+ );
+
const isActive = (path: string) => {
return pathname.startsWith(path);
};
@@ -232,17 +240,53 @@ export function AppSidebar() {
{/* Logo - only show when expanded */}
{effectiveState === "expanded" && (
-
- Dograh
- {versionInfo && (
-
- v{versionInfo.ui}
-
+