diff --git a/.gitignore b/.gitignore
index 08124e2..83e231c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,6 +17,10 @@ yarn-error.log*
/build
/dist
+# Version files (auto-generated by prebuild script)
+/src/version.ts
+/src/version.json
+
# IDE
.vscode/
.idea/
@@ -36,6 +40,10 @@ yarn-error.log*
/public/static/
/hint-report/
+# Temp files and custom exclude patterns
~*
\#*
_*
+
+# project specific
+src/version.json
diff --git a/docs/README.dev-notes.md b/docs/README.dev-notes.md
index cbbb31b..e6ce134 100644
--- a/docs/README.dev-notes.md
+++ b/docs/README.dev-notes.md
@@ -8,6 +8,55 @@
- **Recharts 3.3.0** for data visualization
- **Vite 5** as build tool
+## Version Management
+
+The project uses a **hybrid versioning approach**:
+- **Semver** (e.g., `0.2.1`) in `package.json` for npm ecosystem compatibility
+- **Git hash** (e.g., `+a3f2b9c`) automatically appended at build time
+- **Displayed version** in UI footer: `0.2.1+a3f2b9c` (or `0.2.1+a3f2b9c-dirty` if uncommitted changes)
+
+### Version Scripts
+
+```sh
+# Bump version manually (updates package.json)
+npm run version:patch # 0.2.1 → 0.2.2 (bug fixes)
+npm run version:minor # 0.2.1 → 0.3.0 (new features)
+npm run version:major # 0.2.1 → 1.0.0 (breaking changes)
+
+# Generate version.json with git info (runs automatically on build)
+npm run prebuild
+
+# Build for production (automatically generates version)
+npm run build
+```
+
+### How It Works
+
+1. **Manual semver bump**: Run `npm run version:patch|minor|major` when you want to increment the version
+2. **Automatic git info**: On build, `scripts/generate-version.js` creates `src/version.json` with:
+ - `version`: Semver + git hash (e.g., `0.2.1+a3f2b9c`)
+ - `semver`: Just the semver (e.g., `0.2.1`)
+ - `commit`: Git commit hash (e.g., `a3f2b9c`)
+ - `branch`: Current git branch
+ - `buildDate`: ISO timestamp
+ - `gitDate`: Commit date
+3. **Fallback handling**: If `version.json` is missing (fresh clone before first build), `src/constants/defaults.ts` generates a fallback version from `package.json`
+
+### Version Display
+
+The version is displayed in the UI footer (bottom right) next to the GitHub repository link. The format is:
+- `0.2.1+a3f2b9c` (clean working directory)
+- `0.2.1+a3f2b9c-dirty` (uncommitted changes present)
+- `0.2.1-dev` (fallback when version.json is missing)
+
+### Files
+
+- `scripts/generate-version.js` - Generates version.json from git + package.json
+- `scripts/bump-version.js` - Manually bumps semver in package.json
+- `src/version.json` - Auto-generated, ignored by git
+- `src/version.json.template` - Fallback template (committed to repo)
+- `src/constants/defaults.ts` - Exports `APP_VERSION` and `BUILD_INFO`
+
## Initial Setup (Fresh Clone)
```sh
diff --git a/package.json b/package.json
index d55da39..f2d059f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "med-plan-assistant",
- "version": "0.2.1",
+ "version": "0.2.2",
"private": true,
"dependencies": {
"@radix-ui/react-label": "^2.1.8",
@@ -25,6 +25,11 @@
},
"scripts": {
"start": "vite",
+ "prebuild": "node scripts/generate-version.js",
+ "version:bump": "node scripts/bump-version.js",
+ "version:patch": "node scripts/bump-version.js patch",
+ "version:minor": "node scripts/bump-version.js minor",
+ "version:major": "node scripts/bump-version.js major",
"start:wsl2": "HOST=0.0.0.0 vite",
"kill": "lsof -ti:3000 | xargs kill -9 2>/dev/null && echo 'Cleared port 3000' || echo 'Port 3000 was not in use'",
"build": "vite build",
diff --git a/scripts/bump-version.js b/scripts/bump-version.js
new file mode 100644
index 0000000..1ee27f2
--- /dev/null
+++ b/scripts/bump-version.js
@@ -0,0 +1,45 @@
+// scripts/bump-version.js - Manual version bump script
+const fs = require('fs');
+const path = require('path');
+
+const args = process.argv.slice(2);
+const bumpType = args[0] || 'patch'; // patch, minor, major
+
+if (!['patch', 'minor', 'major'].includes(bumpType)) {
+ console.error('Usage: npm run version:bump [patch|minor|major]');
+ console.error(' patch: 0.2.1 → 0.2.2 (bug fixes)');
+ console.error(' minor: 0.2.1 → 0.3.0 (new features)');
+ console.error(' major: 0.2.1 → 1.0.0 (breaking changes)');
+ process.exit(1);
+}
+
+const packagePath = path.join(__dirname, '../package.json');
+const pkg = require(packagePath);
+
+const [major, minor, patch] = pkg.version.split('.').map(Number);
+
+let newVersion;
+switch (bumpType) {
+ case 'major':
+ newVersion = `${major + 1}.0.0`;
+ break;
+ case 'minor':
+ newVersion = `${major}.${minor + 1}.0`;
+ break;
+ case 'patch':
+ default:
+ newVersion = `${major}.${minor}.${patch + 1}`;
+ break;
+}
+
+const oldVersion = pkg.version;
+pkg.version = newVersion;
+
+fs.writeFileSync(packagePath, JSON.stringify(pkg, null, 2) + '\n');
+
+console.log(`✓ Bumped version: ${oldVersion} → ${newVersion}`);
+console.log(`\nNext steps:`);
+console.log(` 1. Review the change: git diff package.json`);
+console.log(` 2. Commit: git add package.json && git commit -m "Bump version to ${newVersion}"`);
+console.log(` 3. (Optional) Tag: git tag v${newVersion}`);
+console.log(` 4. Build to see full version: npm run build`);
diff --git a/scripts/generate-version.js b/scripts/generate-version.js
new file mode 100644
index 0000000..cb8e052
--- /dev/null
+++ b/scripts/generate-version.js
@@ -0,0 +1,63 @@
+// scripts/generate-version.js - Generate version info from git
+const { execSync } = require('child_process');
+const fs = require('fs');
+const path = require('path');
+const pkg = require('../package.json');
+
+try {
+ const gitHash = execSync('git rev-parse --short HEAD').toString().trim();
+ const gitBranch = execSync('git rev-parse --abbrev-ref HEAD').toString().trim();
+ const gitDate = execSync('git log -1 --format=%cd --date=format:%Y-%m-%d').toString().trim();
+ const isDirty = execSync('git status --porcelain').toString().trim() !== '';
+
+ const version = {
+ version: `${pkg.version}+${gitHash}${isDirty ? '-dirty' : ''}`,
+ semver: pkg.version,
+ commit: gitHash,
+ branch: gitBranch,
+ buildDate: new Date().toISOString(),
+ gitDate: gitDate,
+ };
+
+ fs.writeFileSync(
+ path.join(__dirname, '../src/version.json'),
+ JSON.stringify(version, null, 2)
+ );
+
+ // Also generate version.ts for better Vite/TypeScript compatibility
+ const versionTs = `// Auto-generated by scripts/generate-version.js
+export const VERSION_INFO = ${JSON.stringify(version, null, 2)} as const;
+`;
+ fs.writeFileSync(
+ path.join(__dirname, '../src/version.ts'),
+ versionTs
+ );
+
+ console.log(`✓ Generated version: ${version.version}`);
+ console.log(` Semver: ${version.semver}, Commit: ${version.commit}, Branch: ${version.branch}`);
+} catch (error) {
+ console.warn('⚠ Could not generate git version, using package.json fallback');
+ const version = {
+ version: pkg.version,
+ semver: pkg.version,
+ commit: 'unknown',
+ branch: 'unknown',
+ buildDate: new Date().toISOString(),
+ gitDate: 'unknown',
+ };
+
+ fs.writeFileSync(
+ path.join(__dirname, '../src/version.json'),
+ JSON.stringify(version, null, 2)
+ );
+
+ const versionTs = `// Auto-generated by scripts/generate-version.js
+export const VERSION_INFO = ${JSON.stringify(version, null, 2)} as const;
+`;
+ fs.writeFileSync(
+ path.join(__dirname, '../src/version.ts'),
+ versionTs
+ );
+
+ console.log(`✓ Fallback version: ${version.version}`);
+}
diff --git a/src/App.tsx b/src/App.tsx
index 3293d3b..74b93be 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -19,7 +19,7 @@ import Settings from './components/settings';
import LanguageSelector from './components/language-selector';
import DisclaimerModal from './components/disclaimer-modal';
import { Button } from './components/ui/button';
-import { PROJECT_REPOSITORY_URL } from './constants/defaults';
+import { PROJECT_REPOSITORY_URL, APP_VERSION } from './constants/defaults';
// Custom Hooks
import { useAppState } from './hooks/useAppState';
@@ -220,15 +220,20 @@ const MedPlanAssistant = () => {
>
{t('disclaimerModalFooterLink')}
-
-