Lattice
연구를 위한 작업공간
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗A shared grocery list, to-do list and list of dishes for a household, synced live between phones. Works on Android and iPhone as an installable web app, no accounts needed, and keeps working offline.
Live: https://germani144-home.github.io/cart-log/ (the repo kept its original name, cart-log). It moved from germani144.github.io on 2026-10-20 because Chrome on Android installs only one app per address; after the move, enter the list code once on each phone.
Groceries
2 mælk, milk, æbler). Search ignores æ/ø/å spelling differences.To-dos
- (or •, *) become to-dos, other lines become headings, and a bare - is skipped. It asks before adding.Recipes
500 g mel when flour is stored in kg gives 0,5 kg.dishes-pack.json. Dishes the list already has (same name) are skipped, and missing ingredients (halloumi, chorizo, gyoza…) are added to the item database; nothing already saved is changed, so it's safe to tap again. The button under the list disappears once the list has them all. Pantry staples (salt, pepper, oil, flour, sugar, stock, dried spices, soy sauce, mayo, mustard, peanut butter, honey) are left out of the recipes on purpose.General
?list=CODE). Anyone with the code can see and edit it.home-log-YYYY-MM-DD.json with this shared list's groceries, item database, categories, to-dos, recipes and list settings, plus this phone's private to-dos as a separate section. List codes are never in the file. The file is made in the browser, so nothing is sent anywhere.Each release bumps the version in two places, kept identical: the label next to the title in index.html and the cache name in sw.js (cart-log-X.Y). Small fixes and tweaks raise the second number (1.0 → 1.1); bigger features or redesigns raise the first (1.4 → 2.0).
Plain static files served by GitHub Pages from main: no build step, no framework, no runtime dependencies besides the pinned Firebase SDK. Sync is Firebase Cloud Firestore (free Spark plan) with offline persistence.
| File | What it does |
|---|---|
index.html | The markup: tabs, panes and dialogs. Loads the files below |
styles.css | All styles (light and dark) |
js/ios-manifest.js | Runs first, in <head>: links the manifest everywhere except iPhone/iPad (see Offline and updates) |
js/logic.js | The core rules as plain functions with no page, storage or Firestore: æ/ø/å search, amounts and units, recipe scaling, catalog matching and ranking, to-do order, paste parsing, the export format and its checks, import planning. Unit-tested |
js/core.js | Page helpers ($, esc), localStorage (load/save), the built-in item database from seed.js, and the app's state |
js/sync.js | Connecting to Firestore and writing documents; the grocery item helpers built on that |
js/groceries.js | Groceries tab: adding items, suggestions, the item dialog (categories, colours, amounts), the list, the manager |
js/tabs.js | Switching tabs, and keeping dialogs above the on-screen keyboard |
js/recipes.js | Recipes tab: dishes, the recipe dialog, adding a dish to the grocery list |
js/todos.js | To-dos tab: lists (shared and private), headings, folding, editing, hold-and-drag |
js/gestures.js | Swiping between tabs, and ignoring stray taps right after a drag |
js/settings.js | Joining, sharing and leaving a list, the ⋯ menu, private-code restore, updates, starter dishes |
js/backup.js | Export data / Import data (the rules are in logic.js) |
js/boot.js | Startup: picks up ?list=CODE, draws the app, connects, registers the service worker |
seed.js | Built-in bilingual item database and categories |
dishes-pack.json | Starter dishes with recipes, loaded by the Add starter dishes button |
The js/ files are ordinary scripts, loaded in the order above, so a name defined in one is visible in the later ones. Code that runs while a file loads only uses names from that file or earlier ones; everything else runs later, from events.
Everything for one list lives under its code:
lists/{code}/items grocery items (name, category, amount, unit, done)
lists/{code}/catalog items added by the household, shared across the list
lists/{code}/cats custom categories and their colours
lists/{code}/todos to-dos (text, order, done, head: true for headings, list: absent for Home, else the list's id)
lists/{code}/dishes saved dishes (name, serves, ingredients: [{cid, da, en, amt, unit}])
lists/{code}/meta list settings: "dishes" (title of the dish list), "tl_main" (Home's name/icon/card title),
"tl_<id>" (each extra shared to-do list: name, emoji, optional card title)
lists/{private}/todos a phone's private to-dos (same shape)
lists/{private}/meta its private lists ("tl_<id>")
Each phone also keeps a copy of everything in localStorage (keys cart-log.<name>), so the app opens instantly and works offline.
localStorage, then connect() (in sync.js) listens to each collection of the list in Firestore. Every snapshot replaces the local copy and redraws.cart-log), so its offline database and keys stay separate from other apps on the same address that use the same Firebase project.order number. A dragged row gets the number halfway between its new neighbours, so only that row changes and two phones reordering at once don't overwrite each other. A dragged heading moves its whole section, spaced evenly between the neighbours. If two neighbours ever get too close, the list is renumbered 1000, 2000, …The first private list a phone makes gives it a second, random code of its own (cart-log.me in localStorage). Private lists and their to-dos live under that code (lists/{private}/…), which is never written anywhere else: not under the shared code, not in invite links, not in exports. The app listens to it next to the shared list. The ⋯ menu shows the code so the lists can be restored on a new phone; that's the only place it appears.
Home log has its own address, but it still keeps strictly to its own cache and storage (so it stays clean if it ever shares an address with another app):
/cart-log/) plus the pinned Firebase SDK, and reads only from its own cache.cart-log-), never other apps'.boot.js asks the browser to check sw.js on every start and when the app comes back to the screen (updateViaCache: 'none'). A release changes the cache name, so the new worker downloads every file fresh (?v= so GitHub's CDN can't hand out an old copy, and cache: 'reload') into a new cache and only takes over once all of them arrived; boot.js then reloads once (not while typing or dragging). The first open after a release still shows the old version for a moment. Files that aren't precached (the starter dishes) come from the server once and are kept; the pinned Firebase SDK uses the phone's normal browser cache.localStorage keys are prefixed cart-log. (older cl_ keys are moved over automatically).?list=CODE, so Safari's "Add to Home Screen" starts the app at that address. The home-screen app has its own storage, separate from Safari's, and joins the list from the address by itself.esc() before it reaches the page, item ids included.index.html sets a Content-Security-Policy: scripts only from this site and the pinned Firebase SDK on gstatic.com (no inline scripts), and data may only be sent to this site and Firestore (firestore.googleapis.com).Dev-only, with Playwright and Node 18+:
npm install
npx playwright install chromium # once per machine
npm test # everything (about 2 minutes)
npm run test:unit # only the plain-function tests (seconds)
npm run format # Prettier
tests/unit/: logic.js function by function, plus checks that every file the page loads is precached, there are no inline scripts, the version label matches the cache name and the manifest id is unchanged.tests/e2e/: the real app in emulated Android (Pixel 7) and iPhone phones with touch, against a fake, shared Firestore (tests/fake-firestore.js), so two or three "phones" can sync with each other in a test. Covers groceries, to-dos (including drag, sections, folding, private lists), recipes, export/import, joining, the iPhone home-screen rules, updates and offline start. Every test fails on a page error or a CSP violation.tests/e2e/visual.spec.js compares screenshots of the main screens with tests/e2e/__screenshots__/. After an intended visual change, update them with npx playwright test tests/e2e/visual.spec.js --update-snapshots.Only known list codes are allowed (set 2026-10-22). The rules live only in the Firebase console, never in this public repo, because they contain the real codes:
function listOk(id) { return id in ['<shared list code>', '<private to-do code>', ...]; }
match /lists/{listId}/{sub}/{id} {
allow read, write: if listOk(listId);
}
Anyone can open the app, but with any other code nothing syncs: it can't read, write or create a list. A new list (or a new private to-do list) only works after its code is added to the rules in the Firebase console (Firestore → Rules → Publish). The same rules also cover Nap Log, which shares the Firebase project. The web config in firebase-config.js is not a secret.
main, root).firebase-config.js.Without a config the app still runs, but the list stays on that one phone ("local only").
{ "format": "home-log-export", "version": 1, "exportedAt": "…", "appVersion": "v4.1",
"shared": { "items": […], "catalog": […], "cats": […], "todos": […], "dishes": […], "meta": […] },
"private": { "todos": […], "meta": […] } | null }
Each entry is a Firestore document with its id, using the fields from Data layout. Only known fields are exported and accepted; sections may be left out.
firebase-config.js | Firebase web config for the project |
sw.js | Service worker: opens the app from its cache and updates it in the background (cache name cart-log-X.Y matches the app version) |
manifest.json | Install name, icons and colours; scope and start_url are ./, so the installed app covers only /cart-log/, not the whole site |
icon-*.png | App icons |
tests/, package.json, playwright.config.js, .prettierrc.json | Dev-only: tests and formatting. Nothing here is loaded by the app |