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:
thandClaude Opus 4.6 committed 2026-04-16 01:24:58 +02:00
1 parent b9d610b612
commit d052fb5f9d
14 files changed
+417 -325

No files matched your search

+37
View File
@@ -254,6 +254,43 @@ label.checkbox input[type="checkbox"] { accent-color:var(--accent); cursor:point
display:flex; gap:8px; align-items:center;
}
.compose-card .actions .spacer { flex:1; }
/* Shopping view — phone-friendly checklist. Large tap targets,
unchecked items only, tap to flip. */
.shopping-wrap { max-width: 520px; margin: 0 auto; display: flex; flex-direction: column; gap: 12px; }
.shopping-top { display: flex; align-items: center; gap: 10px; padding: 6px 2px; }
.shopping-items { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 6px; }
.shopping-items li {
background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
padding: 14px 16px; font-size: 16px; cursor: pointer; user-select: none;
-webkit-tap-highlight-color: transparent;
transition: transform 0.12s, opacity 0.3s, background 0.12s;
}
.shopping-items li:active { transform: scale(0.98); background: var(--raised, var(--surface)); }
.shopping-items li.checked { opacity: 0.35; text-decoration: line-through; }
.shopping-items .empty { text-align: center; padding: 40px 16px; color: var(--dim); background: transparent; border: none; cursor: default; }
.shopping-items .empty:active { transform: none; }
/* Section headings between items — "# Obst", "## Backwaren", etc. */
.shopping-items li.shopping-heading {
background: transparent;
border: none;
padding: 18px 4px 4px;
cursor: default;
font-weight: 600;
letter-spacing: 0.02em;
}
.shopping-items li.shopping-heading:active { transform: none; background: transparent; opacity: 1; }
.shopping-items li.shopping-heading:first-child { padding-top: 2px; }
.shopping-items li.shopping-heading.h1 { font-size: 18px; color: var(--bright); }
.shopping-items li.shopping-heading.h2 { font-size: 15px; color: var(--accent); }
.shopping-items li.shopping-heading.h3,
.shopping-items li.shopping-heading.h4,
.shopping-items li.shopping-heading.h5,
.shopping-items li.shopping-heading.h6 { font-size: 13px; color: var(--muted); }
.shopping-err { background: var(--danger-bg, #7f1d1d); color: var(--danger-fg, #fecaca); padding: 8px 12px; border-radius: 6px; font-size: 13px; }
.shopping-err.hidden { display: none; }
/* Chat view */
.chat-layout { display: flex; height: calc(100vh - 200px); min-height: 300px; border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden; }
.chat-sidebar { width: 200px; border-right: 1px solid var(--border); display: flex; flex-direction: column; background: var(--surface); }