shopping: ship as plugin, integrate phone view into relay UI, fix sync
Restructure the shopping-list feature end to end:
- New public/plugins/shopping/ plugin with backend.php (proxy
routes registered via PluginManager::onApiRoute), shopping.js
(self-contained; injects the header Shopping button, owns the
picker dialog + sync loop via hyprcrd.hooks), plugin.json and a
placeholder css file. Core no longer knows about shopping —
index.php loses the static button and the proxy block, app.js
loses the initShopping wiring, the old js/modules/shopping.js
is gone.
- Phone view moves out of a standalone /shopping page and into
the relay UI as a regular `view-shopping` behind a nav-burger
entry (relay/ui/layout.php + app.js + style.css). Same session
auth as compose/chat/account — no separate login. Auto-refresh
every 4 s while the view is active; no manual refresh button.
Headings "# h1 … ###### h6" render between items for grouping.
Orphan section headings (no open items under them) are hidden.
- Sync: drop timestamp cursors (broke across SQLite-UTC / PHP
timezones and silently blocked pushes after a pull). Both sides
diff the (uid, title, body) triple instead — timezone-agnostic
and authoritative. Client ticks push-before-pull so fresh local
edits land on the relay before a stale pull can overwrite them.
Relay drops its "server newer" guard and stamps server times
with gmdate so anything left comparing to SQLite is UTC.
- Client also ticks on cards.reloaded, not just sync.tick, so
any edit path (editor save, checkbox toggle, context menu,
undo) pushes the update immediately.
- Tests: updated for the plugin (still uses the same selectors
because the plugin injects the same ids), plus a plugin-
registration check and "(no active list)" option assertion.
- Help section copy updated — references the Shopping header
button and the shopping tab in the relay UI rather than the
gone /shopping URL.
Both #einkauf and #shopping work as trigger tags; phone is
check-only, editing stays in hyprcrd. Version bumped to 1.9.21.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
b9d610b612
commit
d052fb5f9d
14 files changed
+417
-325
No files matched your search
+4
-46
@@ -1148,40 +1148,6 @@ if (str_starts_with($route, '/api/')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── Shopping list (proxy to relay) ───────────────────────────
|
||||
// Thin forwarder. The client (shopping.js) talks to us, we relay
|
||||
// to /api/shopping with the configured X-Relay-User/-Key headers.
|
||||
// Phone UI talks to the relay directly via session auth.
|
||||
if (($route === '/api/shopping' || $route === '/api/shopping/check') && in_array($method, ['GET', 'POST'])) {
|
||||
$relayUrl = $db->query("SELECT value FROM settings WHERE key = 'relay_url'")->fetchColumn();
|
||||
$relayKey = $db->query("SELECT value FROM settings WHERE key = 'relay_key'")->fetchColumn();
|
||||
$relayUser = $db->query("SELECT value FROM settings WHERE key = 'relay_username'")->fetchColumn();
|
||||
if (!$relayUrl || !$relayKey || !$relayUser) {
|
||||
echo json_encode(['ok' => false, 'error' => 'relay not configured']); exit;
|
||||
}
|
||||
$ch = curl_init(rtrim($relayUrl, '/') . $route);
|
||||
$opts = [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Content-Type: application/json',
|
||||
'X-Relay-User: ' . $relayUser,
|
||||
'X-Relay-Key: ' . $relayKey,
|
||||
],
|
||||
CURLOPT_TIMEOUT => 10,
|
||||
];
|
||||
if ($method === 'POST') {
|
||||
$opts[CURLOPT_POST] = true;
|
||||
$opts[CURLOPT_POSTFIELDS] = file_get_contents('php://input') ?: '{}';
|
||||
}
|
||||
curl_setopt_array($ch, $opts);
|
||||
$response = curl_exec($ch);
|
||||
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
http_response_code($status ?: 502);
|
||||
echo is_string($response) ? $response : json_encode(['ok' => false, 'error' => 'relay unreachable']);
|
||||
exit;
|
||||
}
|
||||
|
||||
http_response_code(404); echo json_encode(['error' => 'not found']); exit;
|
||||
}
|
||||
|
||||
@@ -1357,14 +1323,6 @@ if (str_starts_with($route, '/api/')) {
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="relative" id="header-menu-wrap">
|
||||
<button id="header-menu-btn" class="flex items-center gap-1 px-2 py-1.5 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors" data-tip="menu">
|
||||
<i class="ph ph-list-dashes text-sm"></i>
|
||||
</button>
|
||||
<div id="header-menu" class="hidden absolute right-0 mt-1 min-w-[180px] bg-t-surface border border-t-border shadow-2xl z-50">
|
||||
<button id="menu-shopping" class="w-full text-left px-3 py-2 text-xs text-t-muted hover:bg-t-raised hover:text-t-accent transition-colors flex items-center gap-2"><i class="ph ph-shopping-cart text-sm"></i><span class="font-pixel text-[10px]">Shopping list</span></button>
|
||||
</div>
|
||||
</div>
|
||||
<button id="calendar-btn" class="flex items-center gap-2 px-2.5 py-1.5 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors">
|
||||
<i class="ph ph-calendar-dots text-sm"></i><span class="font-pixel text-[10px]">Calendar</span>
|
||||
</button>
|
||||
@@ -1693,14 +1651,14 @@ if (str_starts_with($route, '/api/')) {
|
||||
<section>
|
||||
<h3 class="font-pixel text-xs text-t-accent mb-3 flex items-center gap-2"><i class="ph ph-shopping-cart text-sm"></i>shopping list (phone sync)</h3>
|
||||
<div class="text-xs leading-relaxed space-y-3 text-t-muted mb-3">
|
||||
<p>Tag a card with <span class="text-t-accent">#einkauf</span> or <span class="text-t-accent">#shopping</span> to mark it as a shopping-list candidate. Open the header's <i class="ph ph-list-dashes text-xs"></i> menu → <span class="text-t-bright font-pixel">Shopping list</span> to pick which one is the currently <span class="text-t-bright">active</span> list — only one at a time is mirrored to the phone.</p>
|
||||
<p>The active card's body is pushed to the relay on every sync tick. On your phone, open <span class="text-t-accent"><your-relay>/shopping</span> and log in with your relay credentials. The page shows only the <span class="text-t-bright">unchecked</span> <code class="text-t-accent">- [ ]</code> items as tappable rows. Tapping an item flips it to <code class="text-t-accent">- [x]</code> on the relay in-place; the next client tick pulls that back and updates the local card.</p>
|
||||
<p>Tag a card with <span class="text-t-accent">#einkauf</span> or <span class="text-t-accent">#shopping</span> to mark it as a shopping-list candidate. Click the header's <span class="text-t-bright font-pixel">Shopping</span> button to pick which one is the currently <span class="text-t-bright">active</span> list — only one at a time is mirrored to the phone.</p>
|
||||
<p>The active card's body is pushed to the relay on every sync tick. On your phone, log into the relay UI (same credentials as everywhere else) and open <span class="text-t-bright font-pixel">shopping</span> from the menu. You see only the <span class="text-t-bright">unchecked</span> <code class="text-t-accent">- [ ]</code> items as tappable rows. Tapping an item flips it to <code class="text-t-accent">- [x]</code> on the relay in-place; the next client tick pulls that back and updates the local card.</p>
|
||||
<p>Editing stays in hyprcrd — the phone can only check/uncheck, never change text. That keeps conflict handling trivial (last-writer wins by timestamp).</p>
|
||||
</div>
|
||||
<table class="w-full text-xs">
|
||||
<tr><td class="py-1 text-t-dim w-40 font-pixel"><i class="ph ph-hash text-xs"></i> trigger tags</td><td><span class="text-t-accent">#einkauf</span> or <span class="text-t-accent">#shopping</span> — either counts</td></tr>
|
||||
<tr><td class="py-1 text-t-dim font-pixel"><i class="ph ph-list-dashes text-xs"></i> pick active</td><td>header menu → Shopping list → radio select → Save</td></tr>
|
||||
<tr><td class="py-1 text-t-dim font-pixel"><i class="ph ph-device-mobile text-xs"></i> phone URL</td><td><code>https://<relay>/shopping</code> — session-auth, mobile layout</td></tr>
|
||||
<tr><td class="py-1 text-t-dim font-pixel"><i class="ph ph-shopping-cart text-xs"></i> pick active</td><td>header Shopping button → radio select → Save</td></tr>
|
||||
<tr><td class="py-1 text-t-dim font-pixel"><i class="ph ph-device-mobile text-xs"></i> phone access</td><td>relay UI → shopping tab (same login)</td></tr>
|
||||
<tr><td class="py-1 text-t-dim font-pixel"><i class="ph ph-arrows-clockwise text-xs"></i> sync cadence</td><td>piggybacks on the shared sync tick (see Settings → Relay)</td></tr>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
Reference in new issue
Block a user