- TypeScript 86.5%
- CSS 9.9%
- JavaScript 3.2%
- HTML 0.3%
- Shell 0.1%
Inherited from original repo, not needed on Forgejo. Was triggering failing runner jobs on every push. |
||
|---|---|---|
| .githooks | ||
| e2e | ||
| public | ||
| scripts | ||
| src | ||
| test | ||
| .gitignore | ||
| .prettierignore | ||
| AGENTS.md | ||
| CHANGELOG.md | ||
| index.html | ||
| package-lock.json | ||
| package.json | ||
| playwright.config.ts | ||
| README.md | ||
| tsconfig.json | ||
| vite.config.e2e.ts | ||
| vite.config.ts | ||
| vitest.config.ts | ||
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 aisles — drag 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 }.checkedis orthogonal tocategory.qtyandqty_unitare 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'scategorymay 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.