From b6c2e7903127eeda8a7aafe27de8ac34ffa2a9b7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Nov 2026 00:25:07 +0000 Subject: [PATCH 1/6] build(deps): bump oauthlib from 4.0.0 to 4.0.1 Bumps [oauthlib](https://github.com/oauthlib/oauthlib) from 4.0.0 to 4.0.1. - [Release notes](https://github.com/oauthlib/oauthlib/releases) - [Changelog](https://github.com/oauthlib/oauthlib/blob/v4.0.1/CHANGELOG.rst) - [Commits](https://github.com/oauthlib/oauthlib/compare/v4.0.0...v4.0.1) --- updated-dependencies: - dependency-name: oauthlib dependency-version: 4.0.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index 0afc7556..872ac4e0 100644 --- a/uv.lock +++ b/uv.lock @@ -644,11 +644,11 @@ wheels = [ [[package]] name = "oauthlib" -version = "4.0.0" +version = "4.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7a/d8/a1bcc8ba112a627f8ffbdc212a78ce18d3ac07e91a5ca65d27918eee25a1/oauthlib-4.0.0.tar.gz", hash = "sha256:efb274799819440f95b4ab3b818869f1ce9ae26c5beacba0201d1a1b76b54f86", size = 187232, upload-time = "2026-09-28T06:01:18.77Z" } +sdist = { url = "https://files.pythonhosted.org/packages/33/17/2bb072de0b990303c985f26869fd47bb179024a051f47e7f9d208eb92f8e/oauthlib-4.0.1.tar.gz", hash = "sha256:3d73febdb6faa00f277c81fb04be7b7586fb96fc3b7f0b9c772dada69a25ede7", size = 191219, upload-time = "2026-10-31T09:52:58.008Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/f4/78229a1066068ca14fc60fb26cf7381cabe4382261392b90e5f9552722d4/oauthlib-4.0.0-py3-none-any.whl", hash = "sha256:624c28c13a0a59cabf9747dfa52af63be3e512a7f2714df16e91b5b3a145e6cd", size = 159715, upload-time = "2026-09-28T06:01:17.008Z" }, + { url = "https://files.pythonhosted.org/packages/df/ef/3b8adde1b32a6f540758bff734d3442c621734c83708e5f02b0ba2f43b84/oauthlib-4.0.1-py3-none-any.whl", hash = "sha256:498a298876e996301950a5b053d12c31628bb7b895db03666430c69ce182eb61", size = 161934, upload-time = "2026-10-31T09:52:56.206Z" }, ] [[package]] From 9f4ea846fd615c1fe1958682c6e918034e6dc8ae Mon Sep 17 00:00:00 2001 From: Asish Kumar Date: Sat, 7 Nov 2026 14:45:49 +0530 Subject: [PATCH 2/6] fix(datepicker): keep the mobile overlay attached after boosted navigation On touch devices AirDatepicker opens pickers in mobile mode, which closes them through a shared overlay rather than on blur. The library keeps that overlay in its module scope, inside a global container appended to the body. Boosted navigation swaps the body, so the container and overlay are removed: the library notices the missing container and builds a new one for the next picker, but keeps reusing the detached overlay. From then on nothing receives the outside click, and an open picker stays on screen over the form until the page is fully reloaded. Create every picker through a helper that remembers the overlay and moves it back into the current global container whenever it was left detached. Fixes #227 --- frontend/src/js/datepicker.js | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/frontend/src/js/datepicker.js b/frontend/src/js/datepicker.js index 286db394..4a8696a7 100644 --- a/frontend/src/js/datepicker.js +++ b/frontend/src/js/datepicker.js @@ -108,6 +108,21 @@ function isMobile() { return isMobileDevice() || isTouchDevice(); } +// AirDatepicker creates a single overlay for all mobile pickers and keeps it in +// its module scope, inside a global container on the body. A boosted navigation +// swaps the body: AirDatepicker then rebuilds the container, but keeps the old +// overlay, which is left detached and can't receive the clicks that close a picker. +let mobileOverlay = null; + +function createAirDatepicker(element, opts) { + const datepicker = new AirDatepicker(element, opts); + mobileOverlay ??= document.querySelector('.air-datepicker-overlay'); + if (mobileOverlay && !mobileOverlay.isConnected) { + document.getElementById(AirDatepicker.defaultGlobalContainerId).appendChild(mobileOverlay); + } + return datepicker; +} + window.DatePicker = async function createDynamicDatePicker(element) { let todayButton = { content: element.dataset.nowButtonTxt, @@ -182,7 +197,7 @@ window.DatePicker = async function createDynamicDatePicker(element) { opts["selectedDates"] = [element.dataset.value]; opts["startDate"] = [element.dataset.value]; } - return new AirDatepicker(element, opts); + return createAirDatepicker(element, opts); }; window.MonthYearPicker = async function createDynamicDatePicker(element) { @@ -258,7 +273,7 @@ window.MonthYearPicker = async function createDynamicDatePicker(element) { opts["selectedDates"] = [new Date(element.dataset.value + "T00:00:00")]; opts["startDate"] = [new Date(element.dataset.value + "T00:00:00")]; } - return new AirDatepicker(element, opts); + return createAirDatepicker(element, opts); }; window.YearPicker = async function createDynamicDatePicker(element) { @@ -334,5 +349,5 @@ window.YearPicker = async function createDynamicDatePicker(element) { opts["selectedDates"] = [new Date(element.dataset.value + "T00:00:00")]; opts["startDate"] = [new Date(element.dataset.value + "T00:00:00")]; } - return new AirDatepicker(element, opts); + return createAirDatepicker(element, opts); }; From fa120bb9f0c577ee3225f5ec9b236722a541b921 Mon Sep 17 00:00:00 2001 From: Herculino Trotta Date: Sat, 7 Nov 2026 13:25:50 -0300 Subject: [PATCH 3/6] docs(readme): credit Simafri for demo and site hosting --- .github/img/simafri-logo.png | Bin 0 -> 19675 bytes README.md | 6 ++++++ 2 files changed, 6 insertions(+) create mode 100644 .github/img/simafri-logo.png diff --git a/.github/img/simafri-logo.png b/.github/img/simafri-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..0bbd987c51563b7fc5f56bf6c96a98b239661e7d GIT binary patch literal 19675 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 literal 0 HcmV?d00001 diff --git a/README.md b/README.md index 10bde2d3..28bfb2d5 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,12 @@ +

+ Simafri +
+ Demo and site hosting sponsored by Simafri (managed VPS) +

+ # Why WYGIWYH? Managing money can feel unnecessarily complex, but it doesn’t have to be. WYGIWYH (pronounced "wiggy-wih") is based on a simple principle: From da7a77a06d740aa68194dd86d6689fb79267d57f Mon Sep 17 00:00:00 2001 From: Herculino Trotta Date: Sat, 7 Nov 2026 13:28:27 -0300 Subject: [PATCH 4/6] docs(readme): update demo url and space out sponsor note --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 28bfb2d5..a11cb405 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@ +

+

Simafri
@@ -60,7 +62,7 @@ Frustrated by the lack of comprehensive options, I set out to build **WYGIWYH**, # Demo -You can try WYGIWYH on [wygiwyh-demo.herculino.com](https://wygiwyh-demo.herculino.com/) with the credentials below: +You can try WYGIWYH on [demo.wygiwyh.herculino.com](https://demo.wygiwyh.herculino.com/) with the credentials below: > [!NOTE] > E-mail: `demo@demo.com` From 26d0751df673ee87f1f331539152f5c05403edf0 Mon Sep 17 00:00:00 2001 From: Herculino Trotta Date: Sun, 8 Nov 2026 02:25:31 -0300 Subject: [PATCH 5/6] fix(datepicker): reset a stale overlay before reattaching it Navigating back while a mobile picker was open reattached its overlay still active, leaving the page covered by an overlay nothing could close. Also skip the reattach when the global container doesn't exist yet. --- frontend/src/js/datepicker.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/src/js/datepicker.js b/frontend/src/js/datepicker.js index 4a8696a7..6f5f4fbc 100644 --- a/frontend/src/js/datepicker.js +++ b/frontend/src/js/datepicker.js @@ -117,8 +117,10 @@ let mobileOverlay = null; function createAirDatepicker(element, opts) { const datepicker = new AirDatepicker(element, opts); mobileOverlay ??= document.querySelector('.air-datepicker-overlay'); - if (mobileOverlay && !mobileOverlay.isConnected) { - document.getElementById(AirDatepicker.defaultGlobalContainerId).appendChild(mobileOverlay); + const container = document.getElementById(AirDatepicker.defaultGlobalContainerId); + if (mobileOverlay && !mobileOverlay.isConnected && container) { + mobileOverlay.classList.remove('-active-'); // its picker was swapped out while open + container.appendChild(mobileOverlay); } return datepicker; } From a691da75237d3780765e68c1c4a989d61d153d42 Mon Sep 17 00:00:00 2001 From: Herculino Trotta Date: Sun, 8 Nov 2026 02:48:59 -0300 Subject: [PATCH 6/6] chore(dev): run the dev stack in multiple git worktrees Drop fixed container names so each worktree gets its own containers, add docker/dev/worktree.sh to set up and tear down a worktree, and document it under a new Development section in the README. --- .gitattributes | 2 ++ README.md | 42 +++++++++++++++++++++++++++++++++++++++++ docker-compose.dev.yml | 16 +++++++++++----- docker/dev/worktree.sh | 43 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 .gitattributes create mode 100755 docker/dev/worktree.sh diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..bd327742 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Shell scripts run inside Linux containers and Git Bash, which fail on CRLF +*.sh text eol=lf diff --git a/README.md b/README.md index a11cb405..9154a734 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,48 @@ You can now access localhost:OUTBOUND_PORT > - If you're planning on running this behind Tailscale or other similar service also add your machine given IP to `DJANGO_ALLOWED_HOSTS` > - If you're going to use another IP that isn't localhost, add it to `DJANGO_ALLOWED_HOSTS`, without `http://` +## Development + +The development stack runs from `docker-compose.dev.yml`: + +```bash +$ docker compose -f docker-compose.dev.yml up -d +``` + +Set `COMPOSE_FILE=docker-compose.dev.yml` in your `.env` to drop the `-f` flag. + +### Working in multiple git worktrees + +You can run one dev stack per [git worktree](https://git-scm.com/docs/git-worktree), side by side. Each worktree gets its own containers, database and volumes, because Compose names them after the worktree's folder. + +A new worktree has no `.env` (it's gitignored). After creating one, run this from the worktree's root: + +```bash +$ sh docker/dev/worktree.sh +``` + +It copies `.env` from your main checkout, gives the worktree its own host ports (printed when it finishes) and installs `frontend/node_modules` for tools you run outside Docker. The database starts empty. + +To stop the worktree's containers and delete its volumes before removing it: + +```bash +$ sh docker/dev/worktree.sh teardown +``` + +The commands work on Linux, macOS and Windows (Git Bash or WSL). PowerShell and cmd have no `sh`, so on Windows set up git aliases once in your clone; they run through Git's bundled shell from any terminal, and every worktree of the clone shares them: + +```bash +git config alias.wt-setup '!sh docker/dev/worktree.sh' +git config alias.wt-teardown '!sh docker/dev/worktree.sh teardown' +``` + +Run those two lines in Git Bash or PowerShell; cmd doesn't understand the single quotes. + +Then use `git wt-setup` and `git wt-teardown`. Tools that create worktrees for you, such as Orca, Conductor or Superset, usually have setup and archive hooks: put these commands there. + +> [!NOTE] +> Ports come from the worktree's folder name, so two worktrees can rarely land on the same ones. If `docker compose up` reports a port in use, change `OUTBOUND_PORT`, `WEBPACK_OUTBOUND_PORT` or `SQL_OUTBOUND_PORT` in that worktree's `.env`. + ## Latest changes Features are only added to `main` when ready, if you want to run the latest version, you must build from source or use the `:nightly` tag on docker. Keep in mind that there can be undocumented breaking changes. diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 53aace60..43396dce 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -11,7 +11,6 @@ services: context: . dockerfile: ./docker/dev/django/Dockerfile image: wygiwyh_dev_server - container_name: wygiwyh_dev_server volumes: - ./app/:/usr/src/app/:z - ./frontend/:/usr/src/frontend:z @@ -21,6 +20,9 @@ services: - "${OUTBOUND_PORT:-8000}:${INTERNAL_PORT:-8000}" env_file: - .env + environment: + # The browser loads assets straight from vite, so Django must point at the published port + - DJANGO_VITE_DEV_SERVER_PORT=${WEBPACK_OUTBOUND_PORT:-5173} depends_on: - db - vite @@ -31,21 +33,20 @@ services: context: . dockerfile: ./docker/dev/vite/Dockerfile image: wygiwyh_dev_node - container_name: wygiwyh_dev_node volumes: - ./frontend/:/usr/src/frontend - ./app/:/usr/src/app/ # http://jdlm.info/articles/2016/03/06/lessons-building-node-app-docker.html - wygiwyh_node_modules:/usr/src/frontend/node_modules ports: - - '${WEBPACK_OUTBOUND_PORT}:5173' + - '${WEBPACK_OUTBOUND_PORT:-5173}:${WEBPACK_OUTBOUND_PORT:-5173}' environment: - WATCHPACK_POLLING=true + - VITE_DEV_SERVER_PORT=${WEBPACK_OUTBOUND_PORT:-5173} restart: unless-stopped db: image: postgres:15 - container_name: ${SQL_HOST} volumes: - wygiwyh_dev_postgres_data:/var/lib/postgresql/data/ environment: @@ -53,5 +54,10 @@ services: - POSTGRES_PASSWORD=${SQL_PASSWORD} - POSTGRES_DB=${SQL_DATABASE} ports: - - '${SQL_PORT}:5432' + - '${SQL_OUTBOUND_PORT:-${SQL_PORT:-5432}}:5432' + networks: + default: + # Keeps .env files that point SQL_HOST at the old fixed container name working + aliases: + - ${SQL_HOST:-db} restart: unless-stopped diff --git a/docker/dev/worktree.sh b/docker/dev/worktree.sh new file mode 100755 index 00000000..e87d8645 --- /dev/null +++ b/docker/dev/worktree.sh @@ -0,0 +1,43 @@ +#!/bin/sh +# Prepares or tears down the dev environment of an extra git worktree. +# sh docker/dev/worktree.sh copy .env from the main checkout with free host ports, install host node_modules +# sh docker/dev/worktree.sh teardown stop this worktree's containers and delete its volumes +# Plain POSIX sh: no GNU-only flags, so it runs on Linux, macOS and Git for Windows. +set -eu + +if [ "${1:-setup}" = "teardown" ]; then + # Without .env compose can't find its config, and nothing was ever started here + [ -f .env ] || exit 0 + exec docker compose down -v --remove-orphans +fi + +main=$(git worktree list --porcelain | head -n 1 | cut -d ' ' -f 2-) +if [ "$main" = "$(git rev-parse --show-toplevel)" ]; then + echo "This is the main checkout, nothing to do." >&2 + exit 0 +fi + +if [ ! -f .env ]; then + if [ ! -f "$main/.env" ]; then + echo "No .env in the main checkout ($main). Create it from .env.example first." >&2 + exit 1 + fi + # Same folder name -> same offset, so re-creating a worktree keeps its ports + off=$(( $(basename "$PWD" | cksum | cut -d ' ' -f 1) % 100 + 1 )) + grep -v -E '^(OUTBOUND_PORT|WEBPACK_OUTBOUND_PORT|SQL_OUTBOUND_PORT)=' "$main/.env" > .env || true + { + echo "" + echo "# Host ports for this worktree, set by docker/dev/worktree.sh" + echo "OUTBOUND_PORT=$((18000 + off))" + echo "WEBPACK_OUTBOUND_PORT=$((15100 + off))" + echo "SQL_OUTBOUND_PORT=$((15400 + off))" + } >> .env + echo "Created .env: app on http://localhost:$((18000 + off))" +fi + +# For tools run on the host (the vite container keeps its own copy in a volume) +if command -v npm > /dev/null 2>&1; then + npm ci --prefix frontend --no-audit --no-fund +else + echo "npm not found, skipping frontend/node_modules." >&2 +fi