What We Optimized Today—and Why It’s Now Your Perfect PWA Template
Core Message: From a functional CRUD test page, we’ve crafted a lean, Mobile-First PWA template—free from external dependencies, with clean UX and maximum reusability for your projects.
---
Key Improvements in Detail
Fixed Navigation (from sticky to fixed)
  Problem: On iOS/Safari/PWA,position: sticky (especially combined with Safe Area and blur effects) can unpredictably “break” during scrolling.
  Solution: The header now behaves like a native app—stable, always visible, and jump-free.Dynamic Header Height
  Why? To ensure content starts precisely below the navigation bar on any device, the header height is measured via JavaScript and set as a CSS variable.
  Result: Pixel-perfect layout, regardless of device or browser.Optimized Input Fields
  Mobile-First: Compact height but larger font for better readability and usability—delivering a true “app-like” feel.Smart Editing Behavior
  UX Boost:
  - The last edited record automatically moves to position 1 and is highlighted after reload.
  - Scroll offset is calculated to prevent content from disappearing under the header.Dual-Function Refresh Button
  - Tap: Soft refresh (reloads data + checks for Service Worker updates).
  - Long-press: Hard refresh (clears cache/SW + full reload).
  Especially useful: Ideal for developers testing PWA updates or users who want manual control.“Scroll to Top” Button
  Practical for long lists: Appears only after scrolling and returns users to the top with a single tap.
---
Why This Template Speeds Up Your Projects
  - Safe Area support,
  - optimized touch targets,
  - clear actions,
  - modern card-based lists.
  - Clean, modular code,
  - event delegation,
  - clear API layer (list/get/save/delete).
  Ready for any CRUD application (customers, items, notes, tickets, room planning tools, etc.).
---
Conclusion: A template that works, scales, and feels like a native app—without bloat, with a focus on performance and user experience.

A proxy solution (browser → PHP proxy → microservice/DBF server) gives you these practical advantages—especially in your PWA + microservice setup:
Security: one central gatekeeper
The browser only talks to the proxy (same domain). The proxy can enforce: tokens, allow-lists (actions/paths), rate limits, input validation. The microservice can stay “lean” and internal.
No CORS / mixed-content headaches
Everything is same-origin, so you avoid CORS headers, preflight requests, and iOS/PWA edge cases.
Central validation & sanitizing
Normalize/validate inputs (field types, lengths, allowed values) and block dangerous parameters before they ever reach the DBF server.
Audit logging in one place
Consistent logs of who/when/what—without spreading logging logic across every microservice route.
Hide internal architecture
The client never sees ports, hostnames, or internal routes/error details. You can change microservice endpoints later without breaking the frontend.
Stability & performance controls
The proxy can handle timeouts cleanly, standardize JSON error responses, and optionally cache safe calls like list for short periods.
A repeatable pattern for all apps
Agenda, room planner, DMS, xbrowse—same “front door” (*_proxy.php) everywhere. That consistency makes everything easier to maintain.
<?php
/* ==================================================
BASIC HEADERS
================================================== */
header('Content-Type: application/json; charset=utf-8');
/* ==================================================
PROXY-SELFSCHUTZ (minimal & ausreichend)
→ verhindert Direktaufruf aus dem Internet
================================================== */
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
$referer = $_SERVER['HTTP_REFERER'] ?? '';
$allowedOrigins = [
'http://localhost',
'http://127.0.0.1',
'https://demo.space',
'https://www.demo.space'
];
$allowed = false;
foreach ($allowedOrigins as $a) {
if (strpos($origin, $a) === 0 || strpos($referer, $a) === 0) {
$allowed = true;
break;
}
}
if (!$allowed) {
http_response_code(403);
echo json_encode([
'success' => false,
'message' => 'PROXY_FORBIDDEN'
], JSON_UNESCAPED_UNICODE);
exit;
}
/* ==================================================
RAW INPUT
================================================== */
$inputRaw = file_get_contents('php://input');
if ($inputRaw === false || $inputRaw === '') {
$inputRaw = '{}';
}
/* ==================================================
MICROservice URL
================================================== */
$msUrl = 'http://127.0.0.1:9090/agenda';
/* ==================================================
SERVICE TOKEN (Bearer)
================================================== */
$token = getenv('GS_TOKEN');
if (!$token) {
http_response_code(500);
echo json_encode([
'success' => false,
'message' => 'GS_TOKEN_NOT_SET'
], JSON_UNESCAPED_UNICODE);
exit;
}
/* ==================================================
STREAM CONTEXT
================================================== */
$headers = "Content-Type: application/json\r\n";
$headers .= "Authorization: Bearer {$token}\r\n";
$headers .= "Connection: close\r\n";
$ctx = stream_context_create([
'http' => [
'method' => 'POST',
'header' => $headers,
'content' => $inputRaw,
'timeout' => 300,
'ignore_errors' => true // wichtig: wir wollen auch 401/500 lesen
]
]);
/* ==================================================
CONNECT
================================================== */
$T0 = hrtime(true);
$fp = @fopen($msUrl, 'rb', false, $ctx);
if (!$fp) {
http_response_code(502);
echo json_encode([
'success' => false,
'message' => 'CONNECT_FAILED',
'error' => error_get_last()['message'] ?? 'unknown'
], JSON_UNESCAPED_UNICODE);
exit;
}
/* ==================================================
HTTP STATUS AUS MICROSERVICE ĂśBERNEHMEN
================================================== */
$statusLine = $GLOBALS['http_response_header'][0] ?? '';
if (preg_match('~HTTP/\S+\s+(\d{3})~', $statusLine, $m)) {
http_response_code((int)$m[1]);
}
/* ==================================================
STREAM RESPONSE (transparent)
================================================== */
while (!feof($fp)) {
$chunk = fread($fp, 8192);
if ($chunk === false) break;
if ($chunk !== '') {
echo $chunk;
@flush();
}
}
fclose($fp);
/* ==================================================
OPTIONAL: Timing (nur intern nutzbar)
================================================== */
$ms = round((hrtime(true) - $T0) / 1e6, 2);
// hier NICHT ausgeben – nur loggen, falls gewünscht