diff --git a/.codex b/.codex deleted file mode 100644 index e69de29..0000000 diff --git a/CHANGELOG.md b/CHANGELOG.md index 035b5d2..f12a628 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,59 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.25.2] - 2026-04-26 + +### Changed +- Docs: `SPEC.md` updated to reflect all changes since v0.24.0 — Budget Entries table now documents `subcategory` column and DB-backed `category` FK; new `Budget Categories`, `Budget Subcategories`, and `API Tokens` data-model tables added; Settings section updated with API Tokens tab, corrected language list (added Japanese, Arabic, Hindi, Portuguese), and tab count (six → seven); Budget module section now covers subcategories, custom categories, and all new endpoints; new API Documentation section documents OpenAPI 3.0 spec and authentication options; design tokens `--blur-2xs` and `--module-reminders` added to Colors section +- Docs: `README.md` Highlights updated — Budget Tracking now mentions DB-backed subcategories; new API Tokens entry added + +## [0.25.1] - 2026-04-26 + +### Changed +- Dashboard: empty widget states now render as a compact inline row (icon + text) instead of a centred column, saving ~40px of vertical space per empty widget on mobile + +### Fixed +- Dashboard: widget body bottom padding increased from 12px to 16px for slightly more breathing room +- Dashboard: widget reordering in "Anpassen" modal now uses the View Transition API for smooth animations; respects `prefers-reduced-motion` + +## [0.25.0] - 2026-04-25 + +### Added +- API token authentication: admins can create named Bearer / X-API-Key tokens for external integrations; tokens are SHA-256-hashed at rest, support optional expiry and revocation, and track last-used timestamp +- Settings: new "API Tokens" section for admins to create and revoke tokens; the full token value is shown only once immediately after creation +- OpenAPI 3.0 specification served at `/api/v1/openapi.json` and `/openapi.json` (download via `?download=1`) +- Budget: new endpoints `GET /api/v1/budget/categories` and `GET /api/v1/budget/categories/:key/subcategories` with optional `?lang=` localisation + +### Changed +- `server/logger.js` now serialises `Error` objects into structured JSON fields (name, message, stack) instead of logging `{}` + +## [0.24.4] - 2026-04-26 + +### Added +- Accessibility: `layout.css` now has a `@media (prefers-contrast: more)` block — ghost and secondary buttons get explicit borders, cards lose decorative shadows, form inputs get a 2px border, focus rings become thicker (3px, 4px offset), and active nav items get an underline as a colour-independent indicator + +### Fixed +- Design tokens: corrected `--sidebar-width-expanded` comment from `1280px+` to `1440px+` to match the actual breakpoint in `layout.css` + +## [0.24.3] - 2026-04-26 + +### Added +- Design tokens: `--blur-2xs: blur(2px)` added to the blur scale — fills the gap below `--blur-xs` (4px), used for subtle overlay blurs +- Design tokens: `--module-reminders: #0E7490` (Cyan-700, WCAG AA) added for the reminders feature; dark mode variant `#22D3EE` (Cyan-400) + +### Fixed +- Design tokens: hardcoded `blur(16px)`, `blur(2px)`, and `blur(12px)` in `layout.css` replaced with `var(--blur-md)`, `var(--blur-2xs)`, and `var(--blur-sm)` — `prefers-reduced-transparency` now correctly disables all backdrop-filter effects including bottom nav, more-sheet backdrop, and sticky headers +- Accessibility: `layout.css` now has a `prefers-reduced-transparency` block for `.nav-bottom`, `.more-backdrop`, and `.sticky-header` — these three elements previously kept their backdrop-filter active even when the user requested reduced transparency +- Reminders: reminder bell icon in toasts now uses `var(--module-reminders)` instead of the generic `var(--color-accent)` + +## [0.24.2] - 2026-04-26 + +### Fixed +- Design tokens: added missing `--shadow-xl` and `--shadow-xs` tokens (with dark mode variants) — resolves undefined CSS custom property references in kanban drag ghost and dashboard widget toggle +- Design tokens: `--color-surface-raised` replaced with `--color-surface-hover` in `dashboard.css` — was undefined, causing unstyled hover states in the widget customizer +- Design tokens: `--color-text` replaced with `--color-text-primary` in `dashboard.css` — was undefined, causing invisible text on hover in the widget customizer +- Design tokens: hardcoded `font-weight` values (`700`, `500`, `600`) in `reminders.css` replaced with `--font-weight-bold`, `--font-weight-medium`, `--font-weight-semibold` + ## [0.24.1] - 2026-04-25 ### Fixed diff --git a/README.md b/README.md index c96043b..077eb10 100644 --- a/README.md +++ b/README.md @@ -53,10 +53,12 @@ **Calendar Sync:** Two-way sync with Google Calendar (OAuth) and Apple iCloud (CalDAV); subscribe to any public ICS/webcal URL with per-subscription color, private/shared visibility, and automatic sync -**Budget Tracking:** Income and expenses, recurring entries, configurable currency (15 currencies), monthly trends, CSV export +**Budget Tracking:** Income and expenses, recurring entries, DB-backed expense categories with subcategories (35 predefined + custom), configurable currency (15 currencies), monthly trends, CSV export **Notes & Contacts:** Colored sticky notes with Markdown, contact directory with vCard import/export +**API Tokens:** Admins can create named Bearer / X-API-Key tokens for external integrations; tokens are SHA-256-hashed at rest, support optional expiry and revocation. OpenAPI 3.0 specification available at `/api/v1/openapi.json`. + **Zero Build Step:** Pure ES modules, no bundler, no transpiler, no framework. Ships what you write. **Privacy First:** SQLCipher AES-256 encrypted database, fully self-hosted, zero telemetry diff --git a/docs/SPEC.md b/docs/SPEC.md index 9c80042..71fd5f1 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -150,13 +150,37 @@ Display metadata (name, color) for synced Google/Apple calendars. Populated auto |--------|------|-----------| | title | TEXT | NOT NULL | | amount | REAL | NOT NULL (positive = income, negative = expense) | -| category | TEXT | Groceries, Rent, Insurance, Transport, Leisure, Clothing, Health, Education, Other | +| category | TEXT | FK → Budget Categories (by key), NOT NULL | +| subcategory | TEXT | FK → Budget Subcategories (by key), default '' | | date | TEXT | DATE, NOT NULL | | is_recurring | INTEGER | 0/1 | | recurrence_rule | TEXT | iCal RRULE | | recurrence_parent_id | INTEGER | FK → Budget Entries (generated instance points to original) | | created_by | INTEGER | FK → Users, NOT NULL | +### Budget Categories +Expense and income category list, DB-backed with stable English slug keys. Predefined set (8 expense, 5 income); users can add custom categories inline from the entry modal. + +| Column | Type | Constraint | +|--------|------|-----------| +| key | TEXT | PRIMARY KEY (stable English slug, e.g. `housing`) | +| name | TEXT | NOT NULL | +| type | TEXT | `'expense'` or `'income'` | +| sort_order | INTEGER | NOT NULL DEFAULT 0 | +| created_at | TEXT | ISO 8601 | + +### Budget Subcategories +Optional subcategories scoped to an expense category. Predefined set (35 entries); users can add custom subcategories inline. Income categories have no subcategories. + +| Column | Type | Constraint | +|--------|------|-----------| +| key | TEXT | PRIMARY KEY | +| category_key | TEXT | FK → Budget Categories (CASCADE delete), NOT NULL | +| name | TEXT | NOT NULL | +| sort_order | INTEGER | NOT NULL DEFAULT 0 | +| created_at | TEXT | ISO 8601 | +| UNIQUE | | (category_key, name) | + ### Budget Recurrence Skipped Stores instances of a recurring entry deleted by the user so they are not re-generated. @@ -166,6 +190,21 @@ Stores instances of a recurring entry deleted by the user so they are not re-gen | month | TEXT | YYYY-MM, NOT NULL | | PRIMARY KEY | | (parent_id, month) | +### API Tokens +Named Bearer / X-API-Key tokens for non-interactive external integrations. Admin-only creation and revocation. Token values are SHA-256-hashed at rest; the plaintext is shown only once after creation. + +| Column | Type | Constraint | +|--------|------|-----------| +| id | INTEGER | PRIMARY KEY AUTOINCREMENT | +| name | TEXT | NOT NULL | +| token_hash | TEXT | NOT NULL UNIQUE (SHA-256) | +| token_prefix | TEXT | NOT NULL (first 8 chars, for display) | +| created_by | INTEGER | FK → Users (CASCADE delete), NOT NULL | +| expires_at | TEXT | ISO 8601, nullable | +| revoked_at | TEXT | ISO 8601, nullable | +| last_used_at | TEXT | ISO 8601, nullable | +| created_at | TEXT | ISO 8601 NOT NULL | + ### ICS Subscriptions External calendar feeds subscribed by users (read-only, auto-synced). @@ -307,8 +346,9 @@ User management and app configuration. Logged-in users only. - **User management (admin):** create new users, edit/delete existing users, assign roles (admin/member) - **Calendar integration:** connect/disconnect Google Calendar OAuth, store Apple Calendar (CalDAV) credentials, configure sync interval; manage ICS URL subscriptions (add, delete, sync now, set color and visibility) - **Weather:** configure OpenWeatherMap location -- **Language:** System (follows `navigator.language`), German, English, Spanish, French, Italian, Swedish, Greek, Russian, Turkish, Chinese - via `oikos-locale-picker` web component; switch without page reload -- **Tab navigation:** Settings is organized in six tabs (General, Meals, Budget, Shopping, Calendar, Account). Sticky tab bar, active tab persists in sessionStorage, Calendar tab auto-activates after OAuth callbacks. +- **Language:** System (follows `navigator.language`), German, English, Spanish, French, Italian, Swedish, Greek, Russian, Turkish, Chinese, Japanese, Arabic, Hindi, Portuguese - via `oikos-locale-picker` web component; switch without page reload +- **API Tokens (admin):** create named Bearer / X-API-Key tokens for external integrations; the full token value is shown only once immediately after creation; tokens can be revoked at any time; support optional expiry and track last-used timestamp +- **Tab navigation:** Settings is organized in seven tabs (General, Meals, Budget, Shopping, Calendar, API Tokens, Account). Sticky tab bar, active tab persists in sessionStorage, Calendar tab auto-activates after OAuth callbacks. - **App info:** version, license ### Budget (`/budget`) @@ -317,10 +357,24 @@ User management and app configuration. Logged-in users only. - Monthly overview: income vs. expenses, balance, bar chart by category (Canvas, no library) - Transaction list: chronological, filterable -- CRUD: title, amount, category, date +- CRUD: title, amount, category, subcategory, date +- Categories: DB-backed with stable English slug keys; 8 predefined expense categories, 5 income categories; users can add custom categories inline from the entry modal +- Subcategories: 35 predefined subcategories across expense categories; users can add custom subcategories inline; displayed alongside category in each entry's metadata line - Recurring entries - Monthly comparison (current vs. previous month) -- CSV export +- CSV export includes a subcategory column and English column headers +- API: `GET /api/v1/budget/categories`, `GET /api/v1/budget/categories/:key/subcategories` (optional `?lang=` localisation), `POST /api/v1/budget/categories`, `POST /api/v1/budget/categories/:key/subcategories` + +--- + +## API Documentation + +An OpenAPI 3.0 specification is served at `/api/v1/openapi.json` and `/openapi.json`. Append `?download=1` to download as a file. The spec covers all authenticated endpoints and can be imported into any OpenAPI-compatible client (Insomnia, Postman, etc.). + +Authentication options for external integrations: +- **Session cookie:** standard browser session after login +- **Bearer token:** `Authorization: Bearer ` — tokens created via Settings → API Tokens (admin only) +- **X-API-Key header:** `X-API-Key: ` — alternative header accepted alongside Bearer --- @@ -368,6 +422,7 @@ Source of truth: `public/styles/tokens.css`. Key values (as of v0.20.39): --module-notes: #CA8A04; /* Gold, 4.08:1 — icons/large-text only */ --module-contacts: #0969DA; /* Blue — distinct from Indigo primary */ --module-budget: #0F766E; /* Teal-700, 5.11:1 */ + --module-reminders: #0E7490; /* Cyan-700, WCAG AA */ --module-settings: #6E7781; /* Neutral grey */ /* Priority */ @@ -378,6 +433,7 @@ Source of truth: `public/styles/tokens.css`. Key values (as of v0.20.39): /* Glass layer tokens */ --glass-bg: rgba(255,255,255,0.72); --glass-border: rgba(255,255,255,0.55); + --blur-2xs: blur(2px); --blur-md: 16px; --radius-glass-button: 9999px; /* capsule */ --ease-glass: cubic-bezier(0.34, 1.56, 0.64, 1); /* spring */ @@ -417,6 +473,7 @@ Source of truth: `public/styles/tokens.css`. Key values (as of v0.20.39): --module-meals: #FB923C; /* Orange-400 */ --module-shopping: #F472B6; /* Pink-400 — mirrors light entanglement */ --module-budget: #2DD4BF; /* Teal-400 */ + --module-reminders: #22D3EE; /* Cyan-400 */ --meal-dinner: #818CF8; --glass-bg: rgba(28,28,26,0.75); --glass-border: rgba(255,255,255,0.12); @@ -437,7 +494,7 @@ Additive CSS file loaded globally after `layout.css`. Implements a Liquid Glass **Phase 1-3 (Shell + Components + Polish):** - **Translucent surfaces:** `backdrop-filter: blur()` on bottom nav, sidebar, modal overlay, cards on hover. All blur effects are inside `@supports (backdrop-filter: blur(1px))` for progressive enhancement. -- **Glass tokens:** Section 16 of `tokens.css` defines `--glass-bg*`, `--glass-border*`, `--blur-xs` through `--blur-xl`, `--opacity-glass-*`, `--glass-highlight*`, `--glass-shadow-sm/md/lg`, `--radius-glass-card/inner/chip/button`, `--ease-glass`, `--transition-glass`. Full dark mode overrides. +- **Glass tokens:** Section 16 of `tokens.css` defines `--glass-bg*`, `--glass-border*`, `--blur-2xs` through `--blur-xl`, `--opacity-glass-*`, `--glass-highlight*`, `--glass-shadow-sm/md/lg`, `--radius-glass-card/inner/chip/button`, `--ease-glass`, `--transition-glass`. Full dark mode overrides. - **Capsule shapes:** Buttons, FAB, and search inputs use `--radius-glass-button` (pill shape). - **Spring animations:** Modal entrance (`glass-modal-scale-in` / `glass-sheet-in`), page transitions, and list stagger all use `cubic-bezier(0.34, 1.56, 0.64, 1)` spring easing. - **FAB attention pulse:** `fab-ring-pulse` keyframe expands a ring around the FAB to signal readiness. diff --git a/docs/ux-audit-plan.md b/docs/ux-audit-plan.md index 14d7e92..02cf108 100644 --- a/docs/ux-audit-plan.md +++ b/docs/ux-audit-plan.md @@ -47,5 +47,5 @@ 2. **Lack of Visual Feedback in Customization**: Reordering widgets in the customize modal (`rebuildList()`) happens instantly without transition, feeling jarring. ### Implementation Steps -- [ ] **Compact Empty States (`dashboard.js`)**: Offen — `.widget__empty` hat bereits reduziertes Padding (`space-5`), aber kein echtes Row-Layout. Niedrige Priorität. -- [ ] **Animate Widget Reordering (`dashboard.js`)**: Offen — View Transition API wäre sinnvoll, aber kein Bug. Niedrige Priorität. +- [x] **Compact Empty States (`dashboard.css`)**: `.widget__empty` auf horizontales Row-Layout umgestellt, Icon 28→20px, Padding reduziert — spart ~40px vertikalen Platz pro leerem Widget. +- [x] **Animate Widget Reordering (`dashboard.js`)**: `rebuildList()` nutzt nun `document.startViewTransition()` mit `prefers-reduced-motion`-Guard und `view-transition-name` je Row. diff --git a/package-lock.json b/package-lock.json index eea26e8..910da4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "oikos", - "version": "0.24.1", + "version": "0.25.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "oikos", - "version": "0.24.1", + "version": "0.25.2", "license": "MIT", "dependencies": { "bcrypt": "^6.0.0", diff --git a/package.json b/package.json index ae257c6..5c06a9f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oikos", - "version": "0.24.1", + "version": "0.25.2", "description": "Self-hosted family planner - calendar, tasks, shopping, meal planning, budget and more. Private, open-source, no subscription.", "main": "server/index.js", "type": "module", diff --git a/public/doc-assets/swagger-init.js b/public/doc-assets/swagger-init.js deleted file mode 100644 index 75d47d8..0000000 --- a/public/doc-assets/swagger-init.js +++ /dev/null @@ -1,11 +0,0 @@ -window.addEventListener('DOMContentLoaded', () => { - window.ui = window.SwaggerUIBundle({ - url: '/openapi.json', - dom_id: '#swagger-ui', - deepLinking: true, - docExpansion: 'list', - persistAuthorization: true, - displayRequestDuration: true, - filter: true, - }); -}); diff --git a/public/doc-assets/swagger.html b/public/doc-assets/swagger.html deleted file mode 100644 index 0e9abd9..0000000 --- a/public/doc-assets/swagger.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - Oikos API Docs - - - - - - -
- Oikos API Documentation - -
-
- - diff --git a/public/locales/de.json b/public/locales/de.json index 3dcfe7f..9c36484 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -631,28 +631,28 @@ "currencyLabel": "Währung", "currencyHint": "Legt die Währung für den gesamten Budget-Bereich fest.", "currencySaved": "Währung gespeichert.", - "apiTokensTitle": "API Tokens", - "apiTokensCardTitle": "Access Tokens", - "apiTokensHint": "Create API tokens for external integrations. The full token is shown only once after creation.", - "apiTokenNameLabel": "Token name", - "apiTokenExpiresLabel": "Expiration date", - "apiTokenExpiresHint": "Leave empty to create a token without expiration.", - "apiTokenCreatedLabel": "New API token", - "apiTokenCreatedHint": "Store this token securely. It cannot be shown again.", - "apiTokenCreate": "Create token", - "apiTokenInvalidExpiration": "Please enter a valid expiration date.", - "apiTokenCreatedToast": "API token created.", - "apiTokenRevokedToast": "API token revoked.", - "apiTokenRevokeConfirm": "Revoke API token \"{{name}}\"?", - "apiTokenRevoke": "Revoke token", - "apiTokenRevoked": "Revoked", - "apiTokenExpired": "Expired", - "apiTokenActive": "Active", - "apiTokenPrefix": "Prefix", - "apiTokenExpires": "Expires", - "apiTokenNeverExpires": "No expiration", - "apiTokenLastUsed": "Last used", - "apiTokenNeverUsed": "Never used", + "apiTokensTitle": "API-Tokens", + "apiTokensCardTitle": "Zugriffstoken", + "apiTokensHint": "Erstelle API-Tokens für externe Integrationen. Der vollständige Token wird nach der Erstellung nur einmal angezeigt.", + "apiTokenNameLabel": "Tokenname", + "apiTokenExpiresLabel": "Ablaufdatum", + "apiTokenExpiresHint": "Leer lassen, um einen Token ohne Ablaufdatum zu erstellen.", + "apiTokenCreatedLabel": "Neuer API-Token", + "apiTokenCreatedHint": "Speichere diesen Token sicher. Er kann nicht erneut angezeigt werden.", + "apiTokenCreate": "Token erstellen", + "apiTokenInvalidExpiration": "Bitte gib ein gültiges Ablaufdatum ein.", + "apiTokenCreatedToast": "API-Token erstellt.", + "apiTokenRevokedToast": "API-Token widerrufen.", + "apiTokenRevokeConfirm": "API-Token \"{{name}}\" widerrufen?", + "apiTokenRevoke": "Token widerrufen", + "apiTokenRevoked": "Widerrufen", + "apiTokenExpired": "Abgelaufen", + "apiTokenActive": "Aktiv", + "apiTokenPrefix": "Präfix", + "apiTokenExpires": "Läuft ab", + "apiTokenNeverExpires": "Kein Ablaufdatum", + "apiTokenLastUsed": "Zuletzt verwendet", + "apiTokenNeverUsed": "Nie verwendet", "ics": { "title": "ICS-Abonnements", "add": "Abonnement hinzufügen", diff --git a/public/pages/dashboard.js b/public/pages/dashboard.js index 00fa84e..5a706b9 100644 --- a/public/pages/dashboard.js +++ b/public/pages/dashboard.js @@ -546,7 +546,7 @@ function openCustomizeModal(currentConfig, onSave) { const isFirst = i === 0; const isLast = i === draft.length - 1; return ` -
+