Next 16's server-to-client serializer rejects values whose prototype is
not plain Object. node-appwrite returns row objects carrying internal
helpers (toString etc.), so every <ClientComponent prop={row}> crashed with
'Only plain objects, and a few built-ins, can be passed to Client
Components from Server Components.'
Added a tiny toPlain helper that JSON-roundtrips any value and applied it
at the boundary of every query that returns rows consumed by 'use client'
files:
- connection-queries (enrich)
- job-queries (inbound, outbound, approved labs)
- job-file-queries (listJobFiles)
- job-history-queries (listJobHistory)
- prosthetic-queries (listProsthetics, listActiveProsthetics)
- finance-queries (listFinanceEntries)
- notification-helpers (listNotifications)
- dashboard-queries (getDashboardData)
- jobs/[jobId] page (direct getRow for the job prop on JobActionsPanel)
Internal Maps inside queries stay live — only the data crossing the
server/client boundary is normalised.
- getDashboardData aggregates open jobs, pending-action jobs, unread
notifications, pending finance totals, approved connection count, recent
jobs (up to 8) and recent notifications (up to 5) — single Promise.all so
the dashboard renders in one round-trip.
- Four stat cards, each a Link to the relevant module; tone (positive /
negative) flips between clinic (payable) and lab (receivable).
- Clinic users with zero approved connections see a 'Bağlantı Kur' prompt
card so they don't get stuck on /jobs/new.
- Recent jobs table is role-aware: lab sees Klinik column + 'Son Gelen
İşler' header, clinic sees Laboratuvar column + 'Son Giden İşler' header.
- Recent notifications panel with read/unread dot, clickable header arrow
to /notifications.
- ActiveContext now carries 'kind' (mirror of TenantSettings.kind) so we no
longer reach into ctx.settings?.kind in callers.
Step-by-step instructions for the production deploy that has to happen in
the admin.kovaksoft.com UI (Coolify doesn't expose a clean CLI for creating
resources). Covers DNS A record, Coolify resource setup, ENV mapping,
domain + SSL, Gitea webhook for auto-deploy, and a verification cookbook.
Troubleshooting section includes the Node 26 / node-appwrite patch already
shipped in repo.
Previously, signing in with a kind toggle when the account had no
tenant_settings row in the lab database returned 'Bu hesap için kayıt
bulunamadı'. For a freshly-imported isletmem user that has never opened
DLS, the right behaviour is onboarding — not a dead end.
resolveTenantOnLogin now distinguishes three states:
- no_tenants → redirect /onboarding?kind=<pill> (session stays)
- mismatch → real error naming the existing kind, session rolled back
- matched → existing tenant set as active, /dashboard
Onboarding page accepts ?kind= and the workspace form pre-selects it, so
the user keeps their login pill choice without re-picking. Also fixed the
'teams.total > 0' redirect — it now requires a tenant_settings row before
sending users off to /dashboard, otherwise users with cross-app teams but
no DLS workspace would bounce.
node-appwrite 23.1.0 ships a bundled undici Agent via node-fetch-native-with-agent.
That bundle uses an older undici dispatcher API that crashes on Node 26 with
'invalid onError method' (UND_ERR_INVALID_ARG), making every Appwrite call
fail with 'fetch failed' / our user-facing 'Bağlantı hatası' fallback.
The patch replaces createAgent/createFetch with thin pass-throughs to
globalThis.fetch — Node native fetch handles HTTPS to db.kovaksoft.com
directly, no proxy/agent customization needed. Verified end-to-end via
users.listMemberships against the live project.
Also added dev-mode error surfacing in appwriteError so future SDK
exceptions show the real message instead of 'Bağlantı hatası'.
These DLS module routes were added in the previous bootstrap but the auth
middleware's PROTECTED_PREFIXES list still mirrored isletmem's CRM modules,
so /jobs/inbound etc. were returning 200 without a session and exposing the
placeholder shell. Build smoke test caught it; layout-level redirect alone
was not enforcing it for those paths.
Sign-in form now has a Klinik / Laboratuvar pill toggle (defaults to clinic).
The selected kind is posted alongside email+password; the server action
resolves the user's memberships, picks a tenant_settings row matching the
chosen kind, and sets it as active. If no matching tenant exists the session
is rolled back with a clear error.
Invite-flow logins skip the kind check — invite code drives team assignment.