From a6ecf7e9caa81f72f4e7969c52bda7f159c7e956 Mon Sep 17 00:00:00 2001 From: Ulas Kalayci Date: Thu, 30 Apr 2026 10:42:51 +0200 Subject: [PATCH] chore: release v0.38.1 --- BACKLOG.md | 17 +++++++++++++ CHANGELOG.md | 8 +++++++ CONTRIBUTING.md | 15 +++++++++--- README.md | 3 ++- docs/SPEC.md | 61 +++++++++++++++++++++++++++++++++++++++++++---- package-lock.json | 4 ++-- package.json | 2 +- 7 files changed, 99 insertions(+), 11 deletions(-) diff --git a/BACKLOG.md b/BACKLOG.md index 43f910c..ab0343e 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -86,3 +86,20 @@ New suggestion? → [Open an issue](https://github.com/ulsklyc/oikos/issues/new? | - | Calendar: custom event icons (102 Lucide options via visual picker); birthday events auto-assigned `cake` icon | v0.29.0 | | - | Calendar: extended reminder presets (2 days, 1 week, 2 weeks) + fully custom reminder (number + unit) | v0.29.0 | | - | Calendar: locale-aware date text inputs (MDY / DMY / YMD) in all date fields across Calendar, Tasks, Meals, Birthdays, Budget | v0.29.0 | +| - | i18n: recipe strings contributed by @baragoon for 13 locales (ar, el, es, fr, hi, it, ja, pt, ru, sv, tr, zh, uk) | v0.30.0 | +| - | Family: phone, email, and birthday fields on family member records, auto-synced to Contacts and Birthdays | v0.31.0 | +| - | Settings: dedicated Family Management tab and API Tokens tab (admin-only) | v0.31.0 | +| - | Settings: ICS subscription edit modal — update name, color, and shared visibility inline | v0.31.2 | +| - | Documents module: upload and manage family files with grid/list view, category tags, visibility ACL (family / restricted / private), archive, download | v0.32.0 | +| - | Tasks: archive status — completed tasks can be archived; visible in dedicated Archived filter | v0.32.0 | +| - | Tasks: inline reminder presets (15 min–2 weeks or custom offset from due date/time) | v0.32.0 | +| - | Typography: Plus Jakarta Sans variable font self-hosted under `public/fonts/` — no CDN dependency at runtime | v0.32.3 | +| - | Module toolbars sticky while scrolling (Tasks, Notes, Calendar, Contacts, Shopping) | v0.32.3 | +| - | Calendar: overlapping timed events render side-by-side in week and day views | v0.33.0 | +| - | Calendar: event file attachments (images, PDFs, Office documents ≤ 5 MB); drag-and-drop upload; inline image preview | v0.33.0 | +| - | Navigation: Kitchen (Meals / Recipes / Shopping) grouped behind a single bottom-bar entry with a persistent tab bar | v0.34.0 | +| - | Settings: Backup Management tab (admin-only) — database download and restore via file upload with pre-restore rollback copy | v0.35.0 | +| - | UX: empty states in all modules include a primary CTA button that triggers the page FAB | v0.36.0 | +| - | UX: `friendlyError()` helper — unhandled promise rejections show status-code-aware messages instead of raw error text | v0.36.0 | +| - | Date input: default format changed to DMY with dot separator; dot-separated dates accepted everywhere | v0.36.1 | +| - | Microinteraction long loops: FAB entry animation stops after 5 views; keyboard shortcut hint hides after first use; success toasts suppressed after 50 saves; empty-state CTA delayed fade-in | v0.38.0 | diff --git a/CHANGELOG.md b/CHANGELOG.md index 724dae1..ad690af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.38.1] - 2026-04-30 + +### Changed +- Docs: SPEC.md — `family_documents` and `family_document_access` tables added; `calendar_events` extended with `icon` and four attachment columns; `contacts` and `birthdays` extended with `family_user_id`; Tasks `status` includes `archived`; Documents module section added; Calendar section updated with icons, file attachments, and overlapping event rendering; Settings section updated with Backup Management tab and family member contact fields +- Docs: BACKLOG.md — completed features table brought up to date through v0.38.0 (v0.30.0–v0.38.0 entries added) +- Docs: README.md — Backup entry added to the feature table; Documents entry updated with exact category count +- Docs: CONTRIBUTING.md — `innerHTML` security note updated to reflect current `insertAdjacentHTML`/`replaceChildren`/`esc()` pattern; individual test-suite commands listed + ## [0.38.0] - 2026-04-30 ### Added diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4be0f6f..79c4c51 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,7 +47,17 @@ npm run dev npm test # All suites ``` -Tests use the Node.js built-in test runner with in-memory SQLite. No running server or database required - tests import route handlers directly. +Individual suites (faster during development): + +```bash +npm run test:db && npm run test:tasks && npm run test:shopping +npm run test:meals && npm run test:calendar && npm run test:ncb +npm run test:reminders && npm run test:dashboard && npm run test:api +npm run test:ics-parser && npm run test:ics-sub +npm run test:modal-utils && npm run test:ux-utils +``` + +Tests use the Node.js built-in test runner with in-memory SQLite (`--experimental-sqlite`). No running server or database required — tests import route handlers directly. --- @@ -177,9 +187,8 @@ PRs are reviewed by the maintainer. Expect feedback within a few days. Once appr - ES modules everywhere (`import`/`export`, never `require`) - Semicolons: **yes** -- Header comment in every file: purpose, module, dependencies - `try/catch` in every route handler - no unhandled promise rejections -- No `eval()`, no `innerHTML` with user input - use `textContent` or DOM API +- No dynamic code execution. Never write user data directly into an HTML string — use `esc()` from `public/utils/html.js` in template literals, or DOM API (`createElement`, `textContent`). Use `insertAdjacentHTML` to append HTML fragments, `replaceChildren()` to replace content. Direct `innerHTML` writes are blocked by a pre-commit hook. ### Frontend diff --git a/README.md b/README.md index b60c2c3..bea4e4b 100644 --- a/README.md +++ b/README.md @@ -53,13 +53,14 @@ The goal is a single, private place for everything that keeps a household runnin | **Meal Planning** | Weekly drag-and-drop planner. Export ingredient lists directly to your shopping list. | | **Recipes** | Create, duplicate, and scale reusable recipes. Pre-fill meal slots from a recipe or save any meal as a recipe. | | **Calendar** | Two-way sync with Google Calendar (OAuth) and Apple iCloud (CalDAV). Subscribe to any public ICS/webcal URL with per-subscription color and visibility. Overlapping timed events render side-by-side. Events support file attachments (images, PDFs, Office documents). | -| **Documents** | Upload and manage family files (PDF, images, Office documents up to 5 MB). Grid/list view, drag-and-drop upload, category tags (medical, school, identity, finance, and more), per-document visibility (family, selected members, private), archive and download. | +| **Documents** | Upload and manage family files (PDF, images, Office documents up to 5 MB). Grid/list view, drag-and-drop upload, 14 category tags (medical, school, identity, finance, and more), per-document visibility (family, selected members, private), archive and download. | | **Budget** | Track income and expenses with recurring entries, monthly trends, and CSV export. 35 predefined categories plus custom ones. Supports 15 currencies. | | **Notes & Contacts** | Colored sticky notes with Markdown support. Contact directory with vCard import/export. | | **Birthdays** | Birthday tracker with automatic annual calendar events, age display, profile photos, and 1-day-before reminders. | | **Reminders** | Time-based reminders on tasks and calendar events. In-app notification badge. | | **Family** | Assign family roles, profile pictures, phone, email, and birthday per member. Family details are automatically synced to Contacts and Birthdays. | | **API Tokens** | Named Bearer / X-API-Key tokens for external integrations. SHA-256-hashed at rest, with optional expiry. OpenAPI 3.0 spec at `/api/v1/openapi.json`. | +| **Backup** | Admin-only database backup and restore via the Settings UI. Download a snapshot or restore from a file upload with an automatic pre-restore rollback copy. | ## Design & Technology diff --git a/docs/SPEC.md b/docs/SPEC.md index 4a1bbc4..393e6d2 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -26,7 +26,7 @@ Every table: `id INTEGER PRIMARY KEY`, `created_at TEXT`, `updated_at TEXT` (ISO | description | TEXT | | | category | TEXT | Household, School, Shopping, Repairs, Other | | priority | TEXT | none (default), low, medium, high, urgent | -| status | TEXT | open, in_progress, done | +| status | TEXT | open, in_progress, done, archived | | due_date | TEXT | DATE, nullable | | due_time | TEXT | TIME, nullable | | assigned_to | INTEGER | FK → Users | @@ -108,6 +108,7 @@ Reusable recipe cards that can be pre-filled into meal slots. | all_day | INTEGER | 0/1 | | location | TEXT | | | color | TEXT | HEX | +| icon | TEXT | Lucide icon name, default 'calendar' | | assigned_to | INTEGER | FK → Users | | created_by | INTEGER | FK → Users, NOT NULL | | external_calendar_id | TEXT | ID from external calendar | @@ -116,6 +117,10 @@ Reusable recipe cards that can be pre-filled into meal slots. | subscription_id | INTEGER | FK → ICS Subscriptions (CASCADE delete) | | user_modified | INTEGER | 0/1 — prevents sync overwrite when 1 | | calendar_ref_id | INTEGER | FK → External Calendars (ON DELETE SET NULL) | +| attachment_name | TEXT | Original filename of attached file, nullable | +| attachment_mime | TEXT | MIME type (e.g. image/jpeg, application/pdf), nullable | +| attachment_size | INTEGER | File size in bytes, nullable | +| attachment_data | TEXT | Base64 data URL of attachment (≤ 5 MB), nullable | ### External Calendars Display metadata (name, color) for synced Google/Apple calendars. Populated automatically during sync. @@ -146,6 +151,7 @@ Display metadata (name, color) for synced Google/Apple calendars. Populated auto | email | TEXT | | | address | TEXT | | | notes | TEXT | | +| family_user_id | INTEGER | FK → Users (CASCADE delete), UNIQUE (one linked user per contact), nullable | ### Budget Entries | Column | Type | Constraint | @@ -215,6 +221,7 @@ Birthday records with optional profile photo and automatic calendar event + remi | notes | TEXT | nullable | | photo_data | TEXT | Base64 data URL (≤ 5 MB), nullable | | calendar_event_id | INTEGER | FK → calendar_events (SET NULL on delete), nullable | +| family_user_id | INTEGER | FK → Users (CASCADE delete), UNIQUE (one linked user per birthday), nullable | | created_by | INTEGER | FK → Users (CASCADE delete), NOT NULL | ### API Tokens @@ -247,6 +254,33 @@ External calendar feeds subscribed by users (read-only, auto-synced). | last_sync | TEXT | ISO timestamp of last successful sync | | created_at | TEXT | ISO timestamp | +### Family Documents +Upload and manage family files with per-document access control. + +| Column | Type | Constraint | +|--------|------|-----------| +| name | TEXT | NOT NULL (display name) | +| description | TEXT | nullable | +| category | TEXT | medical, school, identity, insurance, finance, home, vehicle, legal, travel, pets, warranty, taxes, work, other (default) | +| status | TEXT | active (default), archived | +| visibility | TEXT | family (default), restricted, private | +| original_name | TEXT | NOT NULL (original filename) | +| mime_type | TEXT | NOT NULL | +| file_size | INTEGER | NOT NULL (bytes) | +| content_data | TEXT | NOT NULL (Base64 data URL) | +| storage_provider | TEXT | local (default), external | +| storage_key | TEXT | nullable (external storage path) | +| created_by | INTEGER | FK → Users (CASCADE delete), NOT NULL | + +### Family Document Access +Allowlist for `visibility = 'restricted'` documents — only listed users can see the document. + +| Column | Type | Constraint | +|--------|------|-----------| +| document_id | INTEGER | FK → Family Documents (CASCADE delete), NOT NULL | +| user_id | INTEGER | FK → Users (CASCADE delete), NOT NULL | +| PRIMARY KEY | | (document_id, user_id) | + ### Sync Config Key-value table for OAuth tokens and CalDAV credentials. @@ -286,6 +320,8 @@ Skeleton loading instead of spinners. Clicking any widget navigates to that modu - Assignment to users (avatar color as indicator) - Priorities shown visually via color/icon - Recurring: automatically create next instance on completion +- Archive: completed tasks can be archived (status = 'archived'); visible in a separate Archived filter +- Inline reminder presets: offset from due date/time — 15 min, 1 h, 1 d, 2 d, 1 w, 2 w, or fully custom offset - Mobile swipe: left = done, right = edit - Badge for overdue tasks @@ -330,9 +366,12 @@ Reusable recipe cards linked to meal slots. - Recurring via iCal RRULE - **Google Calendar:** OAuth 2.0, Calendar API v3, two-way sync - **Apple Calendar:** CalDAV (tsdav), two-way sync -- **ICS Subscriptions:** Subscribe to any public ICS/webcal URL (e.g. public holidays, sports schedules). Per-subscription color, private/shared visibility, manual "Sync now" and automatic sync on the shared interval. RRULE events expanded into a rolling ±6/+12 month window. SSRF-protected (DNS pre-resolution), ETag/Last-Modified conditional fetch, 10 MB limit, 15 s timeout. User-edited events are protected from being overwritten (`user_modified`); a "Reset to original" link restores them. +- **ICS Subscriptions:** Subscribe to any public ICS/webcal URL (e.g. public holidays, sports schedules). Per-subscription color, private/shared visibility, manual "Sync now" and automatic sync on the shared interval. Edit name, color, and visibility of any subscription inline. RRULE events expanded into a rolling ±6/+12 month window. SSRF-protected (DNS pre-resolution), ETag/Last-Modified conditional fetch, 10 MB limit, 15 s timeout. User-edited events are protected from being overwritten (`user_modified`); a "Reset to original" link restores them. - **External calendar names & colors:** Google and Apple sync stores each calendar's display name and background color in the `external_calendars` table (migration v14). A colored `event-cal-label` badge appears in event popups, agenda, month, week, and day views when `cal_name` is present. - **Event location:** Event popup and dashboard display the location field with RFC 5545 backslash-escape normalization (`\n`, `\,`, `\;`, `\\`) via `fmtLocation()` in `public/utils/html.js`. +- **Custom event icons:** Each event can have an icon chosen from 102 validated Lucide icons via a visual picker. Birthday events are automatically assigned the `cake` icon. Icon stored in `calendar_events.icon`. +- **File attachments:** Events support a single file attachment (images, PDFs, Office documents, ≤ 5 MB). Images are displayed inline in the event popup; other files show a download link. Drag-and-drop upload supported in the event modal. Stored as Base64 in `attachment_data`. +- **Overlapping events:** In week and day views, timed events that overlap in time are rendered side-by-side using a column-layout algorithm instead of stacking. - Configurable sync interval (default 15 min) - External events visually distinguishable - Conflicts: external event wins, local additions are preserved @@ -356,6 +395,19 @@ Masonry grid with colored sticky notes. - vCard export: each contact downloadable as `.vcf` (`GET /api/v1/contacts/:id/vcard`) - vCard import: upload file → client-side parser (FN, TEL, EMAIL, ADR, NOTE, CATEGORIES) → create contact +### Documents (`/documents`) + +Upload and manage family files with per-document access control. + +- CRUD: name, description, category, file upload (PDF, images, text, Office documents; ≤ 5 MB) +- Drag-and-drop upload in the new-document modal +- **Grid / list view** toggle; view mode persisted in localStorage +- **Category tags:** 14 predefined categories (medical, school, identity, insurance, finance, home, vehicle, legal, travel, pets, warranty, taxes, work, other) +- **Visibility:** family (all members see it), restricted (only selected members), private (only the uploader) +- **Archive / restore** — archived documents hidden from the main view, accessible via the Archive filter +- **Download** — original file downloaded with its original filename +- API: `GET /api/v1/documents`, `POST /api/v1/documents`, `GET /api/v1/documents/:id`, `PUT /api/v1/documents/:id`, `DELETE /api/v1/documents/:id`, `GET /api/v1/documents/:id/download` + ### Login (`/login`) Unauthenticated users are redirected here. No public registration form - admin creates users via setup wizard (`setup.js`) or Settings. @@ -375,8 +427,9 @@ User management and app configuration. Logged-in users only. - **Weather:** configure OpenWeatherMap location - **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. -- **Family management (admin):** assign a `family_role` (Dad, Mom, Parent, Child, Grandparent, Relative, Other) to each user. Displayed in the family member list and profile views. +- **Backup Management (admin):** download the current database as a file (`GET /api/v1/backup/database`) or restore from a backup file (`POST /api/v1/backup/restore`, drag-and-drop supported). Validates that the uploaded file is a valid Oikos database. A rollback copy is created automatically before restore. +- **Tab navigation:** Settings is organized in eight tabs (General, Meals, Budget, Shopping, Calendar, Family, API Tokens, Account). Admin-only tabs: Family, API Tokens, Backup. Sticky tab bar, active tab persists in sessionStorage, Calendar tab auto-activates after OAuth callbacks. +- **Family management (admin):** assign a `family_role` (Dad, Mom, Parent, Child, Grandparent, Relative, Other) to each user, and set per-member phone, email, and birthday — automatically synced to Contacts and Birthdays. Displayed in the family member list and profile views. - **Profile picture:** users can upload a personal avatar (PNG/JPEG/WebP/GIF, ≤ 5 MB), stored as a Base64 data URL in `avatar_data`. Displayed alongside display name across the app. - **App info:** version, license diff --git a/package-lock.json b/package-lock.json index 0b9b035..3ca5b3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "oikos", - "version": "0.38.0", + "version": "0.38.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "oikos", - "version": "0.38.0", + "version": "0.38.1", "license": "MIT", "dependencies": { "bcrypt": "^6.0.0", diff --git a/package.json b/package.json index b651387..41d5176 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oikos", - "version": "0.38.0", + "version": "0.38.1", "description": "Self-hosted family planner - calendar, tasks, shopping, meal planning, budget and more. Private, open-source, no subscription.", "main": "server/index.js", "type": "module",