TreeMap
무료 오픈소스 디스크 사용량 시각화 도구로, 사각형 트리맵·중복 파일 탐색·디스크 가득 예측·정리 시 파일을 휴지통으로만 이동한다
myFAOS는 가계 관리 앱으로, 공유 캘린더·작업·식단·쇼핑리스트·문서·건강·선물 계획을 한 곳에 정리합니다
myFAOS is a free, open-source app that organizes your household: shared calendar, tasks, meal plans, shopping lists, document vault, health tracking, and gift planning — all in one place.
생산성JavaScript · PythonGitHub @Gnadi온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Mobile-first web app that helps families coordinate schedules, tasks, and child documentation. This repository contains the MVP build — a Shared Calendar backed by Firebase plus static UI stubs for the other planned modules (Document Vault, Gift Planner, Task Manager).
src/index.css), Inter (Google Fonts)State is managed with React Context + hooks. No Redux, Zustand, or other state libraries.
src/
├── components/ Reusable UI split by feature area
│ ├── common/ Button, Input, Modal, Spinner, EmptyState
│ ├── layout/ AppShell, TopBar, BottomNav
│ ├── landing/ FeatureCard
│ ├── dashboard/ WeeklyPreview (live), WorkloadBalance, HealthAlerts, QuickAccess
│ └── calendar/ ViewToggle, WeekView, MonthView, EventCard, EventFormModal
├── context/ AuthContext (user + family + loading)
├── hooks/ useAuth, useEvents
├── lib/ firebase.js (SDK init, persisted session)
├── pages/ One route per file
├── routes/ ProtectedRoute, FamilyGate
├── services/ auth, users, families, events, cloudinary
└── utils/ date helpers, invite-code generator
Install dependencies
npm install
Create a Firebase project
.env at the project root
(see .env.example for keys).Deploy Firestore rules (recommended)
The included firestore.rules enforces per-family access control.
firebase deploy --only firestore:rules
Once the repository secrets are in place, merges to main do this
automatically — see Firestore rules & indexes.
Cloudinary (optional)
VITE_CLOUDINARY_* env vars are reserved for future image-upload
features (e.g. Document Vault). They can be left empty for the MVP.
Run locally
npm run dev
Production build
npm run build
npm run preview
Tests
npm test # unit tests (tests/unit)
npm run test:rules # Firestore rules against the emulator (needs Java)
npm run test:all
firestore.rules and firestore.indexes.json are deployed by GitHub Actions
(.github/workflows/firebase-firestore.yml) as soon as a change to either one
lands on main — i.e. on merge. Pull requests that touch them run the
validation job only, so broken rules are caught before the merge.
Two repository settings are required (Settings → Secrets and variables → Actions):
| Name | Type | Value |
|---|---|---|
FIREBASE_SERVICE_ACCOUNT | Secret | The complete JSON key of a Google Cloud service account for the Firebase project |
FIREBASE_PROJECT_ID | Variable | The Firebase project id to deploy to |
The service account needs three roles:
| Role | Needed for |
|---|---|
Firebase Rules Admin (roles/firebaserules.admin) | Publishing firestore.rules |
Cloud Datastore Index Admin (roles/datastore.indexAdmin) | Creating and updating the indexes |
Service Usage Consumer (roles/serviceusage.serviceUsageConsumer) | The CLI checks that firestore.googleapis.com is enabled before it deploys anything |
The last one is easy to miss: without it the deploy stops at ensuring required API firestore.googleapis.com is enabled with HTTP Error: 403, Permission denied to get service, before rules or indexes are touched. The
key generated in the Firebase console (Project settings → Service accounts)
does not carry it by default — add it under IAM & Admin → IAM in the Google
Cloud console.
Create the key under IAM & Admin → Service Accounts → Keys → Add key → JSON, and paste the file's entire contents into the secret.
The deploy runs without --force: indexes are created and updated, but an
index that exists in Firebase and is missing from firestore.indexes.json is
only reported in the job log, never deleted. Removing an index stays a manual
step in the Firebase console.
.github/workflows/npm-audit.yml runs npm audit on every pull request, on
every push to main, and once a week on Mondays — the weekly run is what
catches advisories published after the last merge. It fails as soon as an
advisory of severity high or critical is open for a dependency in
package-lock.json. The run's job summary lists the packages
behind it: severity, whether the package ships to users or is only installed
for development, and whether a fix has been published.
To clear a failing run:
npm audit # the full list, lower severities included
npm audit fix # everything a compatible release fixes
npm audit fix --force # the rest, with breaking upgrades — test the app afterwards
The threshold is AUDIT_LEVEL in the workflow; lower it to moderate or
low once everything above that is cleared. A one-off run at a different
level can be started under Actions → npm audit → Run workflow.
package.json carries three overrides, each for an advisory whose fix the
package that pulls it in has not taken up yet:
@grpc/grpc-js → ^1.13.6, for @firebase/firestore, which pins ~1.9.0
and so has no fix for GHSA-m9gg-hp2v-232j. Only Firestore's Node build uses
gRPC — the browser talks WebChannel — so what it affects is the rules tests
and the build, both of which pass with it.basic-ftp → ^6.2.3. firebase-tools reaches it through proxy-agent →
get-uri, which asks for ^5; it would only ever fetch a proxy
configuration over FTP.chokidar → ^4, for firebase-tools only. chokidar 3 depends on braces,
which has an open advisory and no fixed release; chokidar 4 has dropped it.
firebase-tools uses it to watch the rules in the emulator, which works the
same with either.Remove each once the package that pulls it in depends on a fixed release
itself. braces also came in through Tailwind CSS 3; Tailwind 4 no longer
depends on it.
Two Vercel functions in api/ do what the browser cannot:
| Endpoint | Does |
|---|---|
api/cloudinary-sign.js | Signs uploads to the document vault on Cloudinary (CLOUDINARY_API_KEY, CLOUDINARY_API_SECRET) |
api/ics-fetch.js | Fetches subscribed calendar feeds, which providers do not serve to browsers |
Both answer only signed-in members of a family. The app sends the user's
Firebase ID token with every call (authorizationHeader() in
src/lib/firebase.js), and api/_lib/familyMember.js reads, with that token,
the caller's user document and then the family it names, through Firestore's
REST API. So Firestore verifies the token and firestore.rules decides, as for
every read the app makes; a family's members are its memberIds, never
users.familyId, which anyone may set on their own document. All this needs
on the server is the project id, which it takes from VITE_FIREBASE_PROJECT_ID
(or FIREBASE_PROJECT_ID) — no service account.
The feed proxy reaches only the public internet (api/_lib/publicFetch.js).
It checks the address it connects to rather than the hostname as written, and
follows redirects itself, each through the same check, so a name that resolves
to a private address or a redirect into Vercel's own network is refused —
loopback, private and link-local ranges (cloud metadata) included, for IPv4 and
IPv6.
Vercel makes nothing under api/_lib/ into an endpoint; those are modules.
users/{uid} { email, displayName, familyId | null, createdAt }
families/{id} { name, createdBy, memberIds[], encryptionKeyJwk,
activeInvites[], lastJoinToken?, createdAt }
invites/{token} { familyId, familyName, createdBy, createdByName,
revoked, expiresAt, createdAt } // doc id IS the token
events/{id} { familyId, userId, title, description?, date, createdAt, updatedAt }
shoppingItems/{id} { familyId, userId, title, quantity, icon, urgent, offer, ifConvenient,
list: 'main' | 'fresh', forMeals, proposedFor?, done, completedAt,
lastPurchase?, seeded? }
shoppingProducts/{familyId}_{product}
{ familyId, userId, key, title, purchases: [{ id, at, planned? }],
stillHaveAt?, fresh?, muted?, updatedAt }
families/{id} additionally carries household: { adults, shoppingMode, shoppingDay, mealsPerWeek } for the smart shopping list, recipes an optional servings,
and meal plan entries shopped: { recipeId, at } once their ingredients went
on the list.
users.familyId is set, the user is routed into the app shell
(Dashboard, Calendar, Tasks, Settings) with a bottom nav.browserLocalPersistence.localStorage (src/lib/authHint.js),
and a tiny inline script in index.html redirects / to /dashboard (or
/family-setup) before the marketing markup is parsed. The hint holds no
credentials — it only saves the wait for Firebase Auth to restore the
session asynchronously; a stale one lands on /login and is cleared.Invites live in their own collection and the document id is the token — a
128-bit crypto.getRandomValues value. The rules allow get on a known id but
deny list, so a token can be redeemed by whoever holds the link and cannot be
enumerated. /families is get-only for members and never listable, because
the family document holds encryptionKeyJwk, the AES key for the document
vault.
Invites expire (7 days by default) and can be revoked in Settings. There is no use counter: enforcing "single use" would need an atomic increment in the same transaction as the family update, across two collections, which security rules cannot express.
Links are /join/:token and are handled by src/pages/JoinPage.jsx.
Invite links are made to be forwarded, so /join/* is served by its own static
shells (dist/join.html and dist/join.de.html, written by
scripts/emit-app-shell.mjs and wired up in vercel.json) carrying Open
Graph/Twitter tags and a 1200x630 card (public/og-invite*.png, regenerate with
scripts/generate_invite_og_image.py). WhatsApp, Signal, iMessage and friends
never run JS, so without those static tags a pasted invite previews as the bare
domain. A static card cannot localize itself either, hence one shell per UI
language, chosen on Accept-Language. The card names no family: it is rendered
by every chat the link travels through, while reading the invite itself still
requires signing in.
+ FAB to create an event; tap an event card to edit or delete it.familyId, and
streamed in real time via onSnapshot, so every family member sees
changes instantly.+ FAB on /tasks to create a task; tap a card to edit or
delete it. Persisted in Firestore via the same per-family snapshot
pattern as events.A deliberately shapeless logger at /tracker, for the recurring "when was the
last time…?" questions — when Anna last had her medicine, when Steffi last
threw up, whether Lukas has had his vitamin D today.
trackers and trackerEntries, so a tracker's history can grow without
ever rewriting the definition.+ on a card logs "now" in one tap, with a six-second undo;
trackers that record a number open the entry sheet instead of logging blind.The Dashboard carries an Active Trackers widget listing every child/tracker pairing, ordered by what still needs doing: unmet daily goals first, then anything on a cooldown, then whatever was logged most recently. Rows log in one tap there too, and link through to the full page.
Date, status and ordering logic lives in src/utils/tracker.js and is covered
by tests/unit/tracker.spec.js.
Quick Access is stored in localStorage, so a shortcut added after a user last
touched that list would never appear for them — sanitizing only ever drops
unknown ids, it never adds new ones. That is why the Tracker shortcut was
invisible on existing installs.
A second key, familyos:quickAccessSeen, records the ids the user has already
been offered; anything in the catalogue missing from it is appended once.
A shortcut removed on purpose stays removed, because it is still in the seen
list. Installs predating that key fall back to LEGACY_QUICK_ACCESS_IDS —
the catalogue as it stood before the migration — so new entries are correctly
recognised as new. The logic is pure, in src/utils/quickAccess.js, and
covered by tests/unit/quickAccess.spec.js.
The shopping list learns what the family buys and turns it into suggestions,
and optionally into one reviewed list per week. The full concept, the
decisions behind it and the later stages (supermarket offers via
preisrunter.at, where to shop) are in
docs/smart-shopping.md.
shoppingProducts — one document per product and family, appended with
arrayUnion so it queues offline, and written separately from the item so a
failed log never fails the check-off. Re-opening an item within 15 minutes
takes the purchase back out (a mis-tap); items checked off before the log
existed are backfilled from their completedAt when they go back on the
list.src/utils/consumption.js). Irregular products are
never predicted, "still have it" snoozes, and ignored suggestions go quiet.
Purchases made only for planned meals (forMeals items) build no rhythm.servings are scaled to the household when planned meals
go on the list (scaleIngredient in src/utils/ingredients.js).planWeeklyProposal in src/utils/smartShopping.js). Each planned meal
is offered once (mealPlanEntries.shopped), and what the proposal already
bought for a trip is not offered again for it (shoppingItems.proposedFor).
At its top, meals this week: how many meals the family cooks this week
(asked anew each week, last answer as default), filled from the family's
own recipes in rotation (src/utils/mealSuggestions.js, from 7 recipes
on), each suggestion swappable, removable or replaced by a hand-picked
recipe. Confirming puts new meals on free days of the week plan.The pure logic is covered by tests/unit/smartShopping.spec.js, the
shoppingProducts rules by tests/rules/rules.spec.js.
Per the MVP spec, these are intentionally not implemented:
The Dashboard's WorkloadBalance, HealthAlerts, and QuickAccess
widgets are static placeholders and match the provided design.