Skip to content

fix: reliable timesheet saves in Docker (surface errors, 0.5h steps, node middleware) - #8

Merged
flo-kn merged 4 commits into
mainfrom
fix/middleware-nodejs-runtime-session-refresh
Jul 21, 2026
Merged

fix: reliable timesheet saves in Docker (surface errors, 0.5h steps, node middleware)#8
flo-kn merged 4 commits into
mainfrom
fix/middleware-nodejs-runtime-session-refresh

Conversation

@flo-kn

@flo-kn flo-kn commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes timesheet hour edits appearing to save but silently not persisting on the Docker/Supabase deployment, plus two related robustness/UX improvements.

1. Surface save errors instead of swallowing them (the reported bug)

handleSave awaited each Supabase insert/update/delete without checking the returned error, then always showed "Changes saved!". When a write was rejected — most commonly the hours CHECK constraint (0 <= hours <= 24) returning HTTP 400 — the failure was swallowed. The UI reported success, nothing persisted, and on reload the old value reappeared while dashboard totals never changed, so it looked like saved hours had vanished.

Now every write checks its error and throws on failure so the real message is shown, and a pre-save validation flags out-of-range hours per day before the request is sent.

2. Restrict hours to 0.5 increments

The input used a 0.25 step (too granular). Changed to 0.5 and enforced half-hour increments in the pre-save validation, since the step attribute alone is only a soft browser hint.

3. Migrate middleware.tsproxy.ts (Node.js runtime)

Separate latent bug found while investigating: on this codebase (Next 16.0.10 + Turbopack) the legacy middleware.ts convention still defaults to the edge runtime — verified via middleware-manifest.json referencing server/edge/chunks/*. In the edge runtime the internal SUPABASE_URL (http://kong:8000) is not readable server-side, so session refresh in Docker fell back to the public URL and failed with repeated AuthRetryableFetchError: fetch failed, breaking refresh after the 1h JWT expiry.

Fixed idiomatically by migrating to the Next 16 proxy.ts convention, which defaults to the Node.js runtime (no runtime option needed — setting one in a proxy file throws). This keeps session refresh on Node so the internal URL is read as intended, removes the runtime landmine for the recommended middleware→proxy codemod, and silences the middleware convention is deprecated build warning.

Testing

  • Reproduced the exact failure against Kong/PostgREST: PATCH with hours > 24400 23514 violates check constraint time_entries_hours_check (matches the browser request seen in Kong access logs); valid values persist.
  • Rebuilt/redeployed the app container for each change; app responds 200.
  • Post-migration: empty middleware-manifest.json, no edge chunk references updateSession, /dashboard307 /auth/login, login 200, zero fetch failed after restart.

Manual check to confirm: enter an hours value over 24 and verify the hint appears instead of a false "saved".

flo-kn added 4 commits July 21, 2026 06:57
…abase

The auth middleware ran in Next's edge runtime, which does not expose
runtime environment variables such as SUPABASE_URL. The session-refresh
Supabase client therefore fell back to NEXT_PUBLIC_SUPABASE_URL
(e.g. http://localhost:8001), which is unreachable from inside the
container where Supabase is at kong:8000. Token refresh failed with
repeated "AuthRetryableFetchError: fetch failed", and because refreshed
cookies are only persisted by the middleware, sessions went stale after
the 1h JWT expiry. With RLS enabled, all server-side reads then returned
empty — dashboard totals stopped updating and timesheet entries appeared
to vanish, even though writes (via the browser client) persisted fine.

Running the middleware in the Node.js runtime lets it read SUPABASE_URL
at request time, exactly like the server components already do, so the
fix holds regardless of how the Supabase URL is configured.
handleSave awaited each Supabase insert/update/delete without inspecting
the returned error, then unconditionally showed "Changes saved!". When a
write was rejected — most commonly by the hours CHECK constraint
(hours must be between 0 and 24) returning HTTP 400 — the failure was
swallowed: the UI reported success, but nothing persisted. On reload the
old value reappeared and dashboard totals never changed, making it look
like saved hours had vanished.

Now every write checks its error and throws on failure, so the catch
block reports the real message. A pre-save validation also flags any
out-of-range hours with a clear per-day hint before the request is sent,
turning a silent 400 into actionable feedback.
The hours input used a 0.25 step, which is finer than needed. Change the
step to 0.5 and enforce half-hour increments in the pre-save validation
so a manually typed value like 0.25 is rejected with a clear hint rather
than accepted (the step attribute alone is only a soft browser hint).
Replaces the earlier `runtime: "nodejs"` workaround with the idiomatic
Next 16 convention.

Context: on this codebase (Next 16.0.10 + Turbopack), the legacy
middleware.ts convention still defaults to the *edge* runtime — verified
via middleware-manifest.json referencing server/edge/chunks/*. In the
edge runtime the internal SUPABASE_URL (http://kong:8000) is not readable,
so session refresh in Docker fell back to the public URL and failed with
repeated "AuthRetryableFetchError: fetch failed". The `runtime: "nodejs"`
option fixed that but is a landmine: setting `runtime` in a proxy file
throws, so the recommended middleware -> proxy codemod would break the
build.

Migrating to proxy.ts resolves it correctly: Proxy defaults to the
Node.js runtime (verified: empty middleware-manifest, logic compiled into
the node server bundle, no edge chunk references updateSession), so the
internal SUPABASE_URL is read server-side as intended. It also removes the
`runtime` landmine and silences the "middleware convention is deprecated"
build warning.
@flo-kn
flo-kn merged commit 6f742a6 into main Jul 21, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant