No description
  • TypeScript 86.5%
  • CSS 9.9%
  • JavaScript 3.2%
  • HTML 0.3%
  • Shell 0.1%
Find a file
Lyra 894fd06b55 chore: remove GitHub Actions CI workflow
Inherited from original repo, not needed on Forgejo. Was triggering
failing runner jobs on every push.
2026-08-26 21:31:16 +02:00
.githooks Format pre-existing files; add prettier pre-commit hook 2026-06-27 18:56:47 +00:00
e2e Replace datalist autocomplete with in-page suggestion list 2026-07-20 21:13:32 +02:00
public docs: update README, CHANGELOG, manifest for Grocery List fork 2026-08-26 21:04:51 +02:00
scripts Rebuild as Grocery List: remove board view, add quantities, search, new icon 2026-08-26 17:38:25 +02:00
src feat: add plant-based milk keywords to Pantry & Baking 2026-08-26 18:23:27 +02:00
test feat: add plant-based milk keywords to Pantry & Baking 2026-08-26 18:23:27 +02:00
.gitignore Grocery Board: collaborative webxdc shopping list (React + Yjs) 2026-06-27 13:48:13 +00:00
.prettierignore Grocery Board: collaborative webxdc shopping list (React + Yjs) 2026-06-27 13:48:13 +00:00
AGENTS.md docs: add AGENTS.md for coding agent guidance 2026-08-26 21:07:21 +02:00
CHANGELOG.md docs: update README, CHANGELOG, manifest for Grocery List fork 2026-08-26 21:04:51 +02:00
index.html Rebuild as Grocery List: remove board view, add quantities, search, new icon 2026-08-26 17:38:25 +02:00
package-lock.json Release v0.4.0 2026-07-20 19:14:01 +00:00
package.json Release v0.4.0 2026-07-20 19:14:01 +00:00
playwright.config.ts Grocery Board: collaborative webxdc shopping list (React + Yjs) 2026-06-27 13:48:13 +00:00
README.md docs: update README, CHANGELOG, manifest for Grocery List fork 2026-08-26 21:04:51 +02:00
tsconfig.json Grocery Board: collaborative webxdc shopping list (React + Yjs) 2026-06-27 13:48:13 +00:00
vite.config.e2e.ts Grocery Board: collaborative webxdc shopping list (React + Yjs) 2026-06-27 13:48:13 +00:00
vite.config.ts Grocery Board: collaborative webxdc shopping list (React + Yjs) 2026-06-27 13:48:13 +00:00
vitest.config.ts Grocery Board: collaborative webxdc shopping list (React + Yjs) 2026-06-27 13:48:13 +00:00

Grocery List

A collaborative grocery shopping list as a webxdc mini app for Delta Chat and ArcaneChat. Items you add are auto-sorted into store-aisle sections with a clean list view. All state is shared across everyone in the chat and works fully offline.

Fork notice: This is a fork of experintellia/ordered-shopping-list (originally "Grocery Board"), reworked as "Grocery List" with a focus on list-based shopping. All credit for the original app goes to the original author. Source for this fork: git.rooti.me

Built with React on Yjs + y-webxdc (a Level-2 webxdc app) so concurrent edits from many peers converge with no lost updates.

Features

  • Auto-categorizer — type "organic whole milk", it lands in Dairy & Eggs. Offline keyword tables in English and German, including plant-based milk alternatives (oat milk, soy milk, etc.) sorted into Pantry & Baking.
  • Quantity tracking — add a quantity and unit to any item (e.g. "2", "500g", "1.5l"). Quantities are stored as strings to avoid floating-point issues and support decimal values.
  • Quick search — filter your list in real time with the search field. Includes a clear button for quick reset.
  • Quick add with quantity — optional setting (off by default) that lets you add an item directly from the quantity overlay, saving one button press per item.
  • Multilanguage — UI strings and aisle names localize to English or German (auto-detected from the browser, or chosen in Settings). Categories are stored as language-neutral keys so a shared list reads correctly for everyone.
  • No duplicates — adding a name that's already on the list won't create a second entry; if that item was checked off, re-adding it brings it back and updates the quantity. If an unchecked duplicate is added, a warning is shown. Duplicates from concurrent edits are merged deterministically across peers.
  • Autocomplete — the add field suggests names of items you've checked off. Selecting a suggestion copies the text into the input (no side effects).
  • Delete — swipe a row left to delete it, or use the long-press menu.
  • Shared learned overrides — move an item to a different aisle once and the correction is remembered for that item name for everyone.
  • Orthogonal checking — checking an item ("got it") strikes it through and sinks it; it never changes the item's aisle.
  • Completed-item visibility — Settings offers Always show, Always hide, or Hide after restart (the default): the last keeps items you just checked visible so a misclick is correctable, then hides them on reopen.
  • Custom groups — create your own aisles (e.g. "Garage") for things the auto-sorter can't place; items enter them only via a manual move. Shared across the chat. Deleting a group returns its items to Other.
  • Import / export — export the full shared state (items, checked state, quantities, custom groups, overrides) as JSON to copy or download, and import it back — applied to the shared doc for everyone.
  • Who-added — optionally show who added each item (hidden by default).
  • Manage aislesdrag and drop (react-draggable) to reorder the store layout; the order is shared so the household agrees. Empty aisles are hidden.
  • Dark mode, large tap targets, long-press menu (move / rename / delete / change quantity).

Develop

npm install
npm run dev          # Vite dev server with the mockWebxdc emulator

The dev script passes --host so the port is reachable from outside a container. In Docker, publish the port: -p 3000:3000.

Multi-peer testing

Spin up multiple isolated instances to test convergence (concurrent adds, checks, recategorizations, late joiners):

npm run test:peers   # concurrently runs the dev server + webxdc-dev

In Docker, also publish webxdc-dev's UI port (e.g. --port 7000 -> -p 7000:7000).

Unit / integration tests

npm test             # categorizer, CRDT convergence, and DOM integration tests

End-to-end tests (Playwright)

npm run test:e2e     # drives the real app in Chromium against the dev server

Covers the full UI: empty state, add + auto-categorize, tap-to-check (aisle unchanged), checked items sinking, long-press menu (move/rename/delete), the shared override being remembered, the three completed-visibility modes (incl. hide-after-reload), custom groups, import/export round-trip, autocomplete, and drag-and-drop aisle reordering. E2E uses vite.config.e2e.ts (a plain-HTTP variant with only mockWebxdc, no HTTPS) so Playwright's readiness probe works without a TLS handshake. First run needs browsers: npx playwright install --with-deps chromium.

Build

npm run build        # type-checks, bundles, and emits dist-xdc/app.xdc

@webxdc/vite-plugins produces a self-contained .xdc (~103 KB; React is bundled). webxdc.js is provided by the messenger and intentionally not bundled.

Project layout

src/
  categories.ts    canonical aisles + en/de keyword language packs
  categorizer.ts   normalize() + categorize() (override -> keyword -> Other)
  i18n.ts          UI string + aisle-name translations (en/de), language pref
  visibility.ts    completed-item visibility modes (pure, testable)
  store.ts         Y.Doc + WebxdcProvider, shared types + mutations, custom
                   groups, import/export, subscribe + version snapshot
  react-store.ts   useSyncExternalStore hook bridging the store to React
  app/
    App.tsx        root: state, header (search), add bar (autocomplete, qty),
                   overlay routing, quick-add setting
    views.tsx      ListView, rows, tap + long-press gestures, search filter
    sheets.tsx     item menu (move/rename/delete/quantity), manage
                   (react-draggable reorder + groups), settings, quantity
                   numpad, import/export sheets
    ui.tsx         language context + shared sheet primitives
  main.tsx         createRoot entry point
public/
  manifest.toml    name = "Grocery List"
  icon.png         512x512 app icon (shopping bag with list lines)

Shared data model (Yjs)

  • items: Y.Map<id, Y.Map>{ name, category, checked, addedBy, ts, qty, qty_unit }. checked is orthogonal to category. qty and qty_unit are strings (empty string = no quantity). Per-item nested maps so a concurrent check and recategorize on the same item both survive.
  • aisleOrder: Y.Array<string> — custom store layout (empty => default order).
  • overrides: Y.Map<normalizedName, category> — shared learned recategorizations.
  • customAisles: Y.Array<string> — user-created groups (shared). Categories are free strings, so an item's category may be a built-in key or a custom name.

Credits

Original app by experintellia. Forked from ordered-shopping-list and reworked as Grocery List.

License

See the original project for license information.