Einkauf (Grocery)

Keeps track of grocery orders — an always-open list for the next order, order history, purchase-frequency planning, and shop integrations such as REWE that the agent drives with a signed-in browser profile up to the cart.

What it does

grocery is a bundled iframe module for household grocery shopping. It is shop-independent at its core and adds shop integrations on top — the first one is REWE, driven by the Clapilot agent in a signed-in browser profile.

  • Nächste Bestellung / Next order — one always-open, shared list of items for the next order. Items can be added, renamed, re-quantified, annotated, marked as "in cart" or "unavailable", and deleted. Items carry a source badge: Geplant (added by automatic planning), Agent, Nochmal bestellt (reorder), or none for manual entries. A planning panel shows the next order date, the order cadence, the auto-planning switch, a Jetzt planen action, and Bald fällig products that will be due soon. The footer offers Bestellung vorbereiten (hands the list to the chat agent together with the selected shop) and Als bestellt markieren (records the current list as a placed order). Rows show the product image and a link to the product page (see Product images and links).
  • Bestellungen / Orders — recent orders with status (draft, cart_prepared, placed, delivered, cancelled), shop, dates, total, external order number, and the ordered items including the exact shop article with its image and link. Orders can be recorded manually (one item per line, for example 2 l Milch), changed in status, reordered onto the list, or deleted.
  • Produkte / Products — every product that was ever on the list or in an order, with order count, learned purchase interval, last purchase, next due date, and a per-product automatisch planen switch. The detail view edits category, unit, default quantity, an interval override, snooze date, notes, the product image URL and product link, stored shop articles, and archive state.
  • Shops — shop integrations. REWE is pre-seeded (disabled until configured); further generic shops can be added. Each shop has an enabled switch, a default flag, website URL, the browser profile the agent uses (see Settings → App-Verbindungen → persistent browser profiles), delivery or pickup, postal code, market, notes, and the name of its shop skill (see below).

All grocery data is workspace-global (household/organization level): every authenticated user of the instance sees and edits the same list, orders, products, and shops. created_by / added_by are attribution only. The one personal part is the browser profile picker, which lists the current user's own browser profiles (profiles hold personal login cookies).

The module is not installed by default. An admin enables it in the Module Store; the global migration db/migrations/333_grocery_module_default_off.sql keeps it off on legacy_all instances. Installing runs bundled-modules/grocery/migrations/001_create_grocery_schema.sql (tables grocery_settings, grocery_shops, grocery_products, grocery_orders, grocery_order_items, grocery_list_items) and 002_product_media.sql (grocery_products.image_url / product_url); the handler applies pending module migrations on first use.

Planning

  • Only placed and delivered orders count as purchase history; unavailable order items never count.
  • Order cadence = manual Bestellrhythmus (days) or the median gap between the last ten order dates (default 7 days).
  • Next order date = manual date (when today or later) or last order date + cadence (at least today); without any history: tomorrow. When an order is placed on or after a manual date, the manual date is cleared and the cadence takes over again.
  • Product interval = manual override or the median gap between the product's last eight purchase dates (needs two purchases). Next due date = last purchase + interval.
  • A product with automatisch planen is due when its next due date is closer to the next order than to the order after (due date ≤ next order date + half a cadence); soon within one more cadence.
  • Due products are added to the open list automatically (source Geplant, typical quantity = median of the last five purchases). Planning runs whenever the list or overview is loaded, after orders are placed, and on Jetzt planen. Deleting a planned item skips that product for the current order only (it is snoozed until the next order date).
  • Products bought three or more times are flagged as häufig and are good candidates for auto-planning.

Orders and the list

  • Adding a product that is already on the list merges the quantity (one list row per product).
  • Als bestellt markieren creates a placed order from all open/in-cart list items and removes them.
  • The agent records a filled shop cart as cart_prepared; the linked list items move to Im Warenkorb. When the order becomes placed/delivered, those items leave the list and feed the statistics; when it is cancelled or deleted, they return to Offen.
  • external_order_id is unique per shop, so importing a shop's order history twice is safe.
  • A shop's "already bought" catalog (REWE: Deine Produkte → Bereits gekauft) is imported with grocery_import_products / POST products/import: products are created or enriched with the exact shop article, without recording orders, so purchase frequencies are only learned from real orders.

Product images and links

Every product can show an image and a link to the actual product page:

  • From the shop article (automatic). When the agent fills a shop cart or imports a shop catalog, it stores the exact article per shop in shop_refs.<shop_key> — name, article URL, image URL, article id, and price. It captures photos and product links with browser_read images: true, which lists the page's content images with their alt text and enclosing link (on a product page also page_image, the og:image). The ordered article is also kept on each order item (shop_product.image_url / url).
  • Explicit values (manual). In the product detail, Bild-URL and Produktlink set an explicit image and product page (for example a manufacturer page or a different shop). Leaving a field empty falls back to the shop article; the placeholder shows which shop it comes from (Vom REWE-Artikel). The agent sets the same fields with grocery_update_product (image_url, product_url; null clears).

The API resolves what clients show as display: { image_url, url, shop_key } on products, on the product of list items, and on order items: the explicit value first, otherwise the most recently stored shop article with an image / link (shop_key names that shop and is null for an explicit link). Order items prefer the exact ordered article and fall back to the linked product.

In the web module, list rows, product rows, and order items get a thumbnail column as soon as at least one visible row has an image (rows without one show a neutral placeholder, and broken images fall back to it); the link appears in the row meta line as the shop name (REWE ↗) or Produktseite ↗. The product detail shows the image in its header, a thumbnail per stored shop article, and an open-link button next to Produktlink. Images are hotlinked from the stored http(s) URL (referrerpolicy="no-referrer", lazy loading); Clapilot does not download or proxy them. Only http/https URLs are accepted.

Shop integrations and the agent

Shops are driven by the chat agent through the local browser tools (browser_open with the shop's browser_profile_id, browser_snapshot, browser_click, browser_type, browser_navigate, browser_read). Setup for REWE:

  1. Settings → App-Verbindungen → persistent browser profiles: create or pick a profile and sign in to rewe.de in the live view (ideally also choose Lieferservice/Abholservice and the market), then Finish sign-in.
  2. Grocery → Shops → REWE: enable it, select that browser profile, choose delivery or pickup, and store the postal code.
  3. On the list tab choose REWE and press Bestellung vorbereiten (or ask the agent in chat).

The agent then follows the bundled base skill clapilot-grocery (workspace-seed/skills/clapilot-grocery/SKILL.md) and the shop skill named on the shop (grocery-shop-rewe): it opens REWE with the profile, adds every open list item to the cart (reusing the article stored in shop_refs from earlier runs), records the order as cart_prepared with the chosen articles and prices, and — with its browser still open — hands off with request_browser_signin: the user takes over the agent's live browser window (same cart and login, see Browser Profiles), reviews the cart, places the order, and hands control back. The agent never submits the order or payment. After the user confirms the order was placed, the agent (or the user in the Orders tab) sets the status to placed with the REWE order number.

Shop skills

Shop-specific know-how lives in an agent-maintained skill per shop (grocery-shop-<shop_key>):

  • For REWE the module ships a template (bundled-modules/grocery/skills/grocery-shop-rewe/SKILL.md) and installs it once into /app/workspace/skills/grocery-shop-rewe/ when the REWE shop is enabled or the shop list is loaded (never overwriting an existing copy). It is origin: agent and pinned: true, so the agent may edit it with skills_update and the Skill Curator leaves it alone; it survives reseeds and upgrades.
  • For other shops the agent creates grocery-shop-<shop_key> with skills_create on the first run and pins it.
  • After every shop run the agent extends the skill with what it learned (URLs, add-to-cart steps, cart location, quirks, household article preferences). Skills are visible under Module → Skills.

Agent tools

grocery_get_overview, grocery_list_items, grocery_add_items, grocery_update_item, grocery_remove_items, grocery_list_orders, grocery_get_order, grocery_record_order, grocery_update_order, grocery_list_products, grocery_update_product, grocery_import_products, grocery_plan_next_order, grocery_list_shops, grocery_update_shop — install-gated on the grocery module (native bundle grocery), available in chat and live agents, and via clapilot-cli grocery <action>. See Agent Tool Contracts for arguments and mutation semantics and API Reference for the module API.

The module page context (moduleSlug: grocery) activates a Grocery action rule, so questions like "what do we need?", "add oat milk", "we always need eggs every week", or "prepare the REWE order" are answered with the grocery tools instead of from memory. The module's Bestellung vorbereiten button posts clapilot:module-agent-prompt to the app shell, which sends the prepared request as a chat message (the same path as the Dashboard agent prompts).

Clients

Web

The bundled iframe module described above, including the Shops tab.

iOS and macOS

The Apple clients have a native SwiftUI Grocery screen in the side menu (MainAppSection.grocery, route /modules/grocery, shown only while the grocery module is installed; Views/GroceryView.swift and the Grocery* views next to it). It runs against the same module API (/api/modules/grocery/api/*) and never loads the web page for list, orders, or products. The top segmented control switches between three tabs:

  • Nächste Bestellung — the shared list grouped into Offen, Im Warenkorb, and Nicht verfügbar with sticky section headers. Rows show the source badge (Geplant, Agent, Nochmal bestellt), the note, the prepared cart, or the due date of a planned product, and a quantity stepper with the web module's steps (writes are debounced). Swiping a row removes it; on a planned row the action reads Überspringen and only skips the product for this order. A long-press or right-click menu changes the status. Tapping a row opens the item detail for name, quantity, unit, note, and status. It is a full-screen view on iPhone and a sheet on macOS. The planning surface shows the next order date and its source, the automatic-planning switch, the cadence and counters, Bald fällig products with an add button, and Jetzt planen. A prepared cart shows a notice that opens its order. On iPhone the add row (name, quantity, unit) and Bestellung vorbereiten stay anchored below the list. On macOS and iPad the add row sits above the list and the planning surface is a trailing column.
  • Bestellungen — compact rows with date, status chip, shop, item count, and total. The detail (full screen on iPhone, split pane with a collapsible list column on macOS) changes the status, shows the items with the exact shop article and link, and offers Nochmal bestellen and delete with a confirmation.
  • Produkte — server-side search and the filters Alle, Automatisch, Fällig, Häufig, and Archiviert. Each row has order count, interval, and next due date, plus the per-product automatic-planning switch. The detail shows the learned statistics and edits name, category, unit, default quantity, interval override, and notes. It also lists the stored shop articles, has Auf die Liste, and has the archive switch.

Product images and links work like on the web: list rows, product rows, and order items get a thumbnail column (GroceryProductThumbnail, 36/32 pt, placeholder shippingbox) as soon as one row has an image; unavailable items and archived products show it faded. Item and product details show a 56 pt image and the product-page link in the header, stored shop articles show their image, the row context menu has Produktseite öffnen, and the product detail edits Bild-URL and Produktlink (URL keyboard, empty = fall back to the shop article, placeholder Vom {shop}-Artikel, open-link button).

Bestellung vorbereiten sends the same localized request as the web button (agentPromptShop with the selected or default enabled shop and its agent_skill, or agentPromptNoShop). It uses the dashboard prompt path: the app switches to the chat and submits the message, and the Grocery page context (moduleSlug: grocery, groceryTab, openItemCount, nextOrderDate, the open order or product) stays attached so the agent uses the grocery tools. Shops stay web-only. Shops im Web verwalten opens the web module in an in-app sheet with the current session.