From 620d1ca88296a28eb53db52863acda130147b9fe Mon Sep 17 00:00:00 2001 From: Timo Klerx Date: Mon, 22 Jun 2026 08:39:23 +0200 Subject: [PATCH 1/2] feat: add release and postgres ops tooling --- .gitignore | 2 + CONTINUE.md | 16 ++++-- CONTINUE_LOG.md | 9 ++++ Dockerfile.cli-builder | 18 +++++++ README.md | 17 ++++++ package.json | 2 + scripts/backup-postgres.sh | 64 +++++++++++++++++++++++ scripts/deploy.sh | 65 ++++++++++++++++++++++- scripts/install-cli-releases.ps1 | 54 +++++++++++++++++++ scripts/install-cli-releases.sh | 42 +++++++++++++++ scripts/restore-postgres.sh | 90 ++++++++++++++++++++++++++++++++ 11 files changed, 373 insertions(+), 6 deletions(-) create mode 100644 Dockerfile.cli-builder create mode 100644 scripts/backup-postgres.sh create mode 100644 scripts/install-cli-releases.ps1 create mode 100644 scripts/install-cli-releases.sh create mode 100644 scripts/restore-postgres.sh diff --git a/.gitignore b/.gitignore index 9460903..0bb5bc1 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,8 @@ __pycache__/ *.db-shm *.db-wal data/documents/ +data/cli-releases/ +backups/ test-results/ .artifacts/ *.tsbuildinfo diff --git a/CONTINUE.md b/CONTINUE.md index 5d0fac8..2af92d6 100644 --- a/CONTINUE.md +++ b/CONTINUE.md @@ -4,7 +4,7 @@ ## Current Snapshot -- Updated: 2026-06-21 20:34:43 +- Updated: 2026-06-22 08:30:16 - Branch: `main` ## Recent Non-Continuity Commits @@ -17,16 +17,23 @@ ## Git Status +- M .gitignore - M .githooks/pre-merge-commit - M .githooks/pre-push - M CONTINUE.md - M CONTINUE_LOG.md - M README.md -- M eslint.config.mjs - M package.json +- M scripts/deploy.sh +- M eslint.config.mjs - M scripts/check-text-conventions.mjs - M specs/OVERVIEW.md - M validate.ps1 +- ?? Dockerfile.cli-builder +- ?? scripts/backup-postgres.sh +- ?? scripts/install-cli-releases.ps1 +- ?? scripts/install-cli-releases.sh +- ?? scripts/restore-postgres.sh ## Active Specs @@ -34,5 +41,6 @@ ## Next Recommended Actions -1. Commit and push CI fixes for PR #6 (`prettier`, `spec-overview`). -2. No unchecked tasks detected in the active specs. +1. Review and commit the imported operational tooling: Dockerized CLI release artifacts plus guarded PostgreSQL backup/restore scripts. +2. Commit and push CI fixes for PR #6 (`prettier`, `spec-overview`). +3. No unchecked tasks detected in the active specs. diff --git a/CONTINUE_LOG.md b/CONTINUE_LOG.md index 0f97933..ec77cd4 100644 --- a/CONTINUE_LOG.md +++ b/CONTINUE_LOG.md @@ -1614,3 +1614,12 @@ - Ran Prettier on `CONTINUE.md`, `CONTINUE_LOG.md`, and `scripts/check-text-conventions.mjs`. - Regenerated `specs/OVERVIEW.md` with `pnpm run specs:overview:update`. - Focused verification passed: `pnpm run check:text` and Prettier check for the affected files. + +## 2026-06-22 08:30:16 + +- Compared `C:\dev\resource-planning-codex` operational tooling with this template. +- Added Dockerized GoReleaser helper tooling for `starterctl` snapshots and release artifact installation scripts for POSIX and PowerShell. +- Added guarded PostgreSQL backup and restore scripts adapted to this repo's Compose defaults and Prisma PostgreSQL config. +- Updated `scripts/deploy.sh` to derive build metadata, take a pre-deploy PostgreSQL backup, optionally install CLI release artifacts, and restart app plus worker. +- Updated README and `.gitignore` for CLI release artifacts and PostgreSQL dump storage. +- Verification passed: `package.json` parse, PowerShell CLI release installer smoke test, and source-name scan for copied product strings. POSIX shell syntax checks could not run locally because this Windows host's `bash.exe` points at a broken WSL installation. diff --git a/Dockerfile.cli-builder b/Dockerfile.cli-builder new file mode 100644 index 0000000..b666de2 --- /dev/null +++ b/Dockerfile.cli-builder @@ -0,0 +1,18 @@ +FROM golang:1.25-alpine + +ARG GORELEASER_VERSION=2.16.0 +ARG TARGETARCH + +RUN apk add --no-cache ca-certificates curl git tar \ + && case "${TARGETARCH:-amd64}" in \ + amd64) goreleaser_arch="x86_64" ;; \ + arm64) goreleaser_arch="arm64" ;; \ + *) echo "unsupported TARGETARCH=${TARGETARCH}" >&2; exit 1 ;; \ + esac \ + && curl -fsSL "https://github.com/goreleaser/goreleaser/releases/download/v${GORELEASER_VERSION}/goreleaser_Linux_${goreleaser_arch}.tar.gz" \ + | tar -xz -C /usr/local/bin goreleaser \ + && goreleaser --version + +WORKDIR /workspace/cli + +ENTRYPOINT ["goreleaser"] diff --git a/README.md b/README.md index 159c114..094b9e1 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,20 @@ That guide covers: - manual cross-platform builds - GoReleaser snapshot packaging +Create local GoReleaser archives with: + +```powershell +pnpm run cli:dist +``` + +Install downloaded or locally built CLI release archives into the deployment +artifact folder with: + +```powershell +$env:CLI_RELEASES_SOURCE_DIR='.\cli\dist' +pnpm run cli:install-releases +``` + ## Docker Deployment - Local `pnpm run dev` uses SQLite via `DATABASE_URL=file:./dev.db`. @@ -147,6 +161,9 @@ That guide covers: - `pnpm docker build app migrate worker` builds the production app, migration, and worker images. - `pnpm docker up -d worker` starts the Python background worker against the same Postgres database. - Review [`docs/runtime-credentials.md`](./docs/runtime-credentials.md) before adding secrets to app, worker, or migration environments. +- `sh ./scripts/deploy.sh` now performs a pre-deploy PostgreSQL dump, applies migrations, and restarts the app and worker. Set `CLI_RELEASES_BUILD_DURING_DEPLOY=docker` to build `starterctl` release archives in a GoReleaser container, `CLI_RELEASES_BUILD_DURING_DEPLOY=true` to build with host tools, or `CLI_RELEASES_SOURCE_DIR=/path/to/artifacts` to install prebuilt archives. +- Manual PostgreSQL backup: `sh ./scripts/backup-postgres.sh`. Dumps are written to `./backups/postgres/business-app-starter-.dump` and validated with `pg_restore -l`. +- Guarded PostgreSQL restore: `RESTORE_CONFIRM=restore BACKUP_FILE=./backups/postgres/business-app-starter-.dump sh ./scripts/restore-postgres.sh`. The restore script validates the dump, takes a safety backup unless `SKIP_PRE_RESTORE_BACKUP=true`, restores with `pg_restore --clean --if-exists`, reruns Prisma migration/seed steps, and restarts runtime services by default. ## Mail Integration diff --git a/package.json b/package.json index efd469f..f39a353 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,8 @@ "dev": "node scripts/run-next.mjs dev", "docker": "node scripts/docker-compose.mjs", "build": "next build", + "cli:dist": "cd cli && goreleaser release --snapshot --clean", + "cli:install-releases": "pwsh -NoProfile -ExecutionPolicy Bypass -File scripts/install-cli-releases.ps1", "start": "node scripts/run-next.mjs start", "test": "vitest run", "test:watch": "vitest", diff --git a/scripts/backup-postgres.sh b/scripts/backup-postgres.sh new file mode 100644 index 0000000..874d7af --- /dev/null +++ b/scripts/backup-postgres.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env sh +set -eu + +COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}" +COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-webapp-template}" +export COMPOSE_PROJECT_NAME +POSTGRES_SERVICE="${POSTGRES_SERVICE:-postgres}" +POSTGRES_DB="${POSTGRES_DB:-business_app_starter}" +POSTGRES_USER="${POSTGRES_USER:-starter}" +BACKUP_DIR="${BACKUP_DIR:-./backups/postgres}" +BACKUP_KEEP_COUNT="${BACKUP_KEEP_COUNT:-5}" +KEEP_DAYS="${KEEP_DAYS:-90}" +BACKUP_NAME_PREFIX="${BACKUP_NAME_PREFIX:-business-app-starter}" + +timestamp="$(date -u +%Y-%m-%dT%H-%M-%SZ)" +backup_file="${BACKUP_DIR}/${BACKUP_NAME_PREFIX}-${timestamp}.dump" + +step() { + printf '\n=== %s ===\n' "$1" +} + +step "Prepare backup directory" +mkdir -p "$BACKUP_DIR" +printf 'Backup file: %s\n' "$backup_file" + +step "Create PostgreSQL dump" +docker compose -f "$COMPOSE_FILE" exec -T "$POSTGRES_SERVICE" \ + pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -F c > "$backup_file" + +step "Verify backup archive" +if [ ! -s "$backup_file" ]; then + printf 'Backup archive is empty: %s\n' "$backup_file" >&2 + exit 1 +fi + +archive_entries="$(docker compose -f "$COMPOSE_FILE" exec -T "$POSTGRES_SERVICE" \ + pg_restore -l < "$backup_file" | wc -l | tr -d ' ')" +table_data_entries="$(docker compose -f "$COMPOSE_FILE" exec -T "$POSTGRES_SERVICE" \ + pg_restore -l < "$backup_file" | grep -c 'TABLE DATA' || true)" + +printf 'Archive entries: %s\n' "$archive_entries" +printf 'Table data entries: %s\n' "$table_data_entries" + +if [ "${archive_entries:-0}" -eq 0 ] || [ "${table_data_entries:-0}" -eq 0 ]; then + printf 'Backup archive contains no restorable table data entries: %s\n' "$backup_file" >&2 + exit 1 +fi + +step "Prune old dumps by age" +find "$BACKUP_DIR" -type f -name '*.dump' -mtime +"$KEEP_DAYS" -print -delete || true + +step "Prune old dumps by count" +if [ "$BACKUP_KEEP_COUNT" -gt 0 ]; then + ls -1t "$BACKUP_DIR"/"$BACKUP_NAME_PREFIX"-*.dump 2>/dev/null \ + | awk -v keep="$BACKUP_KEEP_COUNT" 'NR > keep { print }' \ + | while IFS= read -r old_backup; do + [ -n "$old_backup" ] || continue + printf 'Removing old backup: %s\n' "$old_backup" + rm -f "$old_backup" + done +fi + +step "Backup completed" +ls -lh "$backup_file" diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 7ef2c4c..6db0bb8 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -2,11 +2,31 @@ set -eu COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}" +COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-webapp-template}" +export COMPOSE_PROJECT_NAME + +generate_app_version() { + head_iso="$(git show -s --format=%cI HEAD)" + head_date="$(TZ=UTC git show -s --date=format-local:%Y%m%d --format=%cd HEAD)" + day_start="$(TZ=UTC git show -s --date=format-local:%Y-%m-%dT00:00:00.000Z --format=%cd HEAD)" + sequence="$(git rev-list --count --first-parent --since="$day_start" --until="$head_iso" HEAD)" + printf '%s.%s' "$head_date" "$sequence" +} + +APP_VERSION="${APP_VERSION:-$(generate_app_version)}" +export APP_VERSION step() { printf '\n=== %s ===\n' "$1" } +step "Build metadata" +printf 'APP_VERSION=%s\n' "$APP_VERSION" +printf 'COMPOSE_PROJECT_NAME=%s\n' "$COMPOSE_PROJECT_NAME" + +step "Compose data volumes" +docker compose -f "$COMPOSE_FILE" config --volumes + step "Build shared app image" docker compose -f "$COMPOSE_FILE" build app @@ -16,10 +36,51 @@ docker compose -f "$COMPOSE_FILE" up -d postgres step "Prisma pre-deploy verification" docker compose -f "$COMPOSE_FILE" run --rm --entrypoint sh migrate -lc "node scripts/prisma-predeploy-check.js" +step "Pre-deploy PostgreSQL backup" +BACKUP_KEEP_COUNT="${BACKUP_KEEP_COUNT:-5}" sh ./scripts/backup-postgres.sh + +if [ "${CLI_RELEASES_BUILD_DURING_DEPLOY:-false}" = "docker" ]; then + step "Clean old CLI build cache volumes" + docker volume rm webapp-template-cli-go-mod webapp-template-cli-go-build >/dev/null 2>&1 || true + + step "Build CLI release builder image" + docker build -f Dockerfile.cli-builder -t webapp-template-cli-builder:latest . + + step "Build CLI release artifacts" + docker run --rm \ + -e GOMODCACHE=/tmp/go/pkg/mod \ + -e GOCACHE=/tmp/go-build \ + -v "$(pwd):/workspace" \ + -w /workspace/cli \ + webapp-template-cli-builder:latest \ + release --snapshot --clean --config .goreleaser.yaml + + step "Install CLI release artifacts" + CLI_RELEASES_SOURCE_DIR="${CLI_RELEASES_SOURCE_DIR:-./cli/dist}" \ + CLI_RELEASES_HOST_DIR="${CLI_RELEASES_HOST_DIR:-./data/cli-releases}" \ + sh ./scripts/install-cli-releases.sh +elif [ "${CLI_RELEASES_BUILD_DURING_DEPLOY:-false}" = "true" ]; then + step "Build CLI release artifacts" + pnpm cli:dist + + step "Install CLI release artifacts" + CLI_RELEASES_SOURCE_DIR="${CLI_RELEASES_SOURCE_DIR:-./cli/dist}" \ + CLI_RELEASES_HOST_DIR="${CLI_RELEASES_HOST_DIR:-./data/cli-releases}" \ + sh ./scripts/install-cli-releases.sh +elif [ -n "${CLI_RELEASES_SOURCE_DIR:-}" ]; then + step "Install CLI release artifacts" + CLI_RELEASES_SOURCE_DIR="$CLI_RELEASES_SOURCE_DIR" \ + CLI_RELEASES_HOST_DIR="${CLI_RELEASES_HOST_DIR:-./data/cli-releases}" \ + sh ./scripts/install-cli-releases.sh +else + step "CLI release artifacts" + printf 'CLI_RELEASES_BUILD_DURING_DEPLOY is not true/docker and CLI_RELEASES_SOURCE_DIR is not set; keeping existing artifacts in %s\n' "${CLI_RELEASES_HOST_DIR:-./data/cli-releases}" +fi + step "Prisma migrate deploy" docker compose -f "$COMPOSE_FILE" run --rm --entrypoint sh migrate -lc "pnpm exec prisma migrate deploy --config prisma.config.postgres.ts" -step "Start app from shared image" -docker compose -f "$COMPOSE_FILE" up -d app +step "Rebuild and restart app + worker" +docker compose -f "$COMPOSE_FILE" up -d --build app worker printf '\nDeploy completed successfully.\n' diff --git a/scripts/install-cli-releases.ps1 b/scripts/install-cli-releases.ps1 new file mode 100644 index 0000000..5eb9a5b --- /dev/null +++ b/scripts/install-cli-releases.ps1 @@ -0,0 +1,54 @@ +param( + [string]$SourceDir = $env:CLI_RELEASES_SOURCE_DIR, + [string]$TargetDir = $(if ($env:CLI_RELEASES_HOST_DIR) { $env:CLI_RELEASES_HOST_DIR } else { ".\data\cli-releases" }) +) + +$ErrorActionPreference = "Stop" + +if (-not $SourceDir) { + throw "CLI_RELEASES_SOURCE_DIR or -SourceDir is required." +} +if (-not (Test-Path -LiteralPath $SourceDir -PathType Container)) { + throw "Source directory does not exist: $SourceDir" +} + +$patterns = @( + "starterctl_*_windows_amd64.zip", + "starterctl_*_windows_arm64.zip", + "starterctl_*_linux_amd64.tar.gz", + "starterctl_*_linux_arm64.tar.gz", + "starterctl_*_darwin_amd64.tar.gz", + "starterctl_*_darwin_arm64.tar.gz", + "checksums.txt" +) + +$targetFullPath = [System.IO.Path]::GetFullPath($TargetDir) +$tmpDir = "$targetFullPath.tmp" +if (Test-Path -LiteralPath $tmpDir) { + Remove-Item -LiteralPath $tmpDir -Recurse -Force +} +New-Item -ItemType Directory -Path $tmpDir -Force | Out-Null + +$copied = 0 +foreach ($pattern in $patterns) { + Get-ChildItem -LiteralPath $SourceDir -Filter $pattern -File | ForEach-Object { + Copy-Item -LiteralPath $_.FullName -Destination $tmpDir + $script:copied += 1 + } +} + +if ($copied -eq 0) { + Remove-Item -LiteralPath $tmpDir -Recurse -Force + throw "No CLI release artifacts found in $SourceDir" +} + +if (Test-Path -LiteralPath $targetFullPath) { + Remove-Item -LiteralPath $targetFullPath -Recurse -Force +} +$parent = Split-Path -Parent $targetFullPath +if ($parent) { + New-Item -ItemType Directory -Path $parent -Force | Out-Null +} +Move-Item -LiteralPath $tmpDir -Destination $targetFullPath + +Write-Host "Installed $copied CLI release artifact(s) into $targetFullPath" diff --git a/scripts/install-cli-releases.sh b/scripts/install-cli-releases.sh new file mode 100644 index 0000000..c62f087 --- /dev/null +++ b/scripts/install-cli-releases.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env sh +set -eu + +SOURCE_DIR="${CLI_RELEASES_SOURCE_DIR:-}" +TARGET_DIR="${CLI_RELEASES_HOST_DIR:-./data/cli-releases}" + +fail() { + printf 'ERROR: %s\n' "$1" >&2 + exit 1 +} + +[ -n "$SOURCE_DIR" ] || fail "CLI_RELEASES_SOURCE_DIR is required." +[ -d "$SOURCE_DIR" ] || fail "CLI_RELEASES_SOURCE_DIR does not exist: $SOURCE_DIR" + +TMP_DIR="${TARGET_DIR}.tmp" +rm -rf "$TMP_DIR" +mkdir -p "$TMP_DIR" + +found=0 +for pattern in \ + "starterctl_*_windows_amd64.zip" \ + "starterctl_*_windows_arm64.zip" \ + "starterctl_*_linux_amd64.tar.gz" \ + "starterctl_*_linux_arm64.tar.gz" \ + "starterctl_*_darwin_amd64.tar.gz" \ + "starterctl_*_darwin_arm64.tar.gz" \ + "checksums.txt" +do + for file in "$SOURCE_DIR"/$pattern; do + [ -f "$file" ] || continue + cp "$file" "$TMP_DIR"/ + found=$((found + 1)) + done +done + +[ "$found" -gt 0 ] || fail "No CLI release artifacts found in $SOURCE_DIR" + +rm -rf "$TARGET_DIR" +mkdir -p "$(dirname "$TARGET_DIR")" +mv "$TMP_DIR" "$TARGET_DIR" + +printf 'Installed %s CLI release artifact(s) into %s\n' "$found" "$TARGET_DIR" diff --git a/scripts/restore-postgres.sh b/scripts/restore-postgres.sh new file mode 100644 index 0000000..ed9e7f2 --- /dev/null +++ b/scripts/restore-postgres.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env sh +set -eu + +COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}" +COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-webapp-template}" +export COMPOSE_PROJECT_NAME +POSTGRES_SERVICE="${POSTGRES_SERVICE:-postgres}" +POSTGRES_DB="${POSTGRES_DB:-business_app_starter}" +POSTGRES_USER="${POSTGRES_USER:-starter}" +BACKUP_FILE="${BACKUP_FILE:-${1:-}}" +RESTORE_CONFIRM="${RESTORE_CONFIRM:-}" +SKIP_PRE_RESTORE_BACKUP="${SKIP_PRE_RESTORE_BACKUP:-false}" +RESTORE_RESTART_SERVICES="${RESTORE_RESTART_SERVICES:-true}" + +step() { + printf '\n=== %s ===\n' "$1" +} + +fail() { + printf '%s\n' "$1" >&2 + exit 1 +} + +if [ -z "$BACKUP_FILE" ]; then + fail "Usage: BACKUP_FILE=./backups/postgres/business-app-starter-.dump sh ./scripts/restore-postgres.sh" +fi + +if [ ! -f "$BACKUP_FILE" ]; then + fail "Backup file not found: $BACKUP_FILE" +fi + +if [ ! -s "$BACKUP_FILE" ]; then + fail "Backup file is empty: $BACKUP_FILE" +fi + +step "Restore target" +printf 'COMPOSE_PROJECT_NAME=%s\n' "$COMPOSE_PROJECT_NAME" +printf 'POSTGRES_SERVICE=%s\n' "$POSTGRES_SERVICE" +printf 'POSTGRES_DB=%s\n' "$POSTGRES_DB" +printf 'POSTGRES_USER=%s\n' "$POSTGRES_USER" +printf 'BACKUP_FILE=%s\n' "$BACKUP_FILE" + +step "Verify backup archive" +archive_entries="$(docker compose -f "$COMPOSE_FILE" exec -T "$POSTGRES_SERVICE" \ + pg_restore -l < "$BACKUP_FILE" | wc -l | tr -d ' ')" +table_data_entries="$(docker compose -f "$COMPOSE_FILE" exec -T "$POSTGRES_SERVICE" \ + pg_restore -l < "$BACKUP_FILE" | grep -c 'TABLE DATA' || true)" + +printf 'Archive entries: %s\n' "$archive_entries" +printf 'Table data entries: %s\n' "$table_data_entries" + +if [ "${archive_entries:-0}" -eq 0 ] || [ "${table_data_entries:-0}" -eq 0 ]; then + fail "Backup archive contains no restorable table data entries: $BACKUP_FILE" +fi + +if [ "$RESTORE_CONFIRM" != "restore" ]; then + printf '\nThis will overwrite database "%s" in Compose project "%s".\n' "$POSTGRES_DB" "$COMPOSE_PROJECT_NAME" + printf 'Set RESTORE_CONFIRM=restore to confirm this destructive restore.\n' + exit 1 +fi + +if [ "$SKIP_PRE_RESTORE_BACKUP" != "true" ]; then + step "Create safety backup before restore" + sh ./scripts/backup-postgres.sh +else + step "Skip pre-restore safety backup" +fi + +step "Stop runtime services" +docker compose -f "$COMPOSE_FILE" stop app worker >/dev/null 2>&1 || true + +step "Restore PostgreSQL dump" +docker compose -f "$COMPOSE_FILE" exec -T "$POSTGRES_SERVICE" \ + pg_restore --clean --if-exists --no-owner -U "$POSTGRES_USER" -d "$POSTGRES_DB" < "$BACKUP_FILE" + +step "Prisma pre-deploy verification" +docker compose -f "$COMPOSE_FILE" run --rm --entrypoint sh migrate -lc "node scripts/prisma-predeploy-check.js" + +step "Prisma migrate deploy" +docker compose -f "$COMPOSE_FILE" run --rm --entrypoint sh migrate -lc "pnpm exec prisma migrate deploy --config prisma.config.postgres.ts" + +step "Seed initial admin if needed" +docker compose -f "$COMPOSE_FILE" run --rm --entrypoint sh migrate -lc "node scripts/seed-initial-admin.mjs" + +if [ "$RESTORE_RESTART_SERVICES" = "true" ]; then + step "Restart app + worker" + docker compose -f "$COMPOSE_FILE" up -d app worker +fi + +printf '\nRestore completed successfully.\n' From 82b174dc5ba1256e7abe9dc8b783b7c3c16b14ba Mon Sep 17 00:00:00 2001 From: Timo Klerx Date: Mon, 22 Jun 2026 08:49:49 +0200 Subject: [PATCH 2/2] chore: refresh specs overview --- specs/OVERVIEW.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/OVERVIEW.md b/specs/OVERVIEW.md index 96e427d..208e7f2 100644 --- a/specs/OVERVIEW.md +++ b/specs/OVERVIEW.md @@ -1,6 +1,6 @@ # Business App Starter Specs Overview -Last Updated: 2026-06-21 +Last Updated: 2026-06-22 Purpose: Track the status of all planned features, their implementation progress, and next steps.