TreeMap
무료 오픈소스 디스크 사용량 시각화 도구로, 사각형 트리맵·중복 파일 탐색·디스크 가득 예측·정리 시 파일을 휴지통으로만 이동한다
노트와 체크리스트 작성, 다중통화·가족 건강 관리, 종단암호화로 선택한 사람과 전화·컴퓨터에서 공유
Write notes, keep checklists, track money in any currency and your family's health, end-to-end encrypted, and share them with the people you choose, on your phone and your computer. In English and Bah
생산성TypeScript · JavaScriptGitHub @ardeman온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Catat Saja: notes, checklists, finances and health logs in one place. End-to-end encrypted, shareable, installable, in English and Bahasa Indonesia.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Catat Saja ("just write it down") is a personal productivity web app for notes, tasks, finances and health, in English and Bahasa Indonesia. Live at catatsaja.ardeman.com.
One simple, private place to write things down and keep track of them:
Notes, tasks, finances, health logs, sharing, accounts and currency settings are done and live, and the app installs as a standalone app. See the Roadmap for what's next.
Done
/ from anywhere); "See all" filters a list.Upcoming (dated)
Planned
Revenue
An optional Catat Saja Plus subscription pays for what costs money to run; the core stays free (docs/decisions.md). In order:
utm_campaign per story: rina, couple, adit, maya, nadia, wulan, hendra) and sign-ups by source in Analytics.plan and planEndsAt on the profile, set in the Firebase console) until payments start.Technical
tests/e2e/sharing.test.mjs ("month documents"). Known: the months are read, then nothing is written: no write is queued (Firestore's IndexedDB shows one primary tab, network on, no pending writes), no toast, no browser error; it didn't happen with Firestore's debug log on. Next: log each step of handleDuplicateFinance → createFinance → createShared on a draft pull request (checked by CI, not deployed).@eslint/compat wrapper once eslint-plugin-react, -import and -jsx-a11y support ESLint 10.The app is a single-page app (ssr: false) on Firebase Auth and Cloud Firestore, hosted on Firebase Hosting. The public pages (landing, about, help, privacy, terms, changelog) are also rendered to HTML at build time (prerender in react-router.config.ts) so search engines and link previews can read them.
| Item | Value |
|---|---|
| Node.js | 24 LTS (.nvmrc) |
| Package manager | pnpm 12 through Corepack (packageManager in package.json) |
| Framework | React 19, React Router 8, Vite 8, TypeScript 6 |
| UI | Tailwind CSS 4, shadcn/ui (new-york, Radix), lucide icons, Milkdown |
| Forms | react-hook-form + Zod 4 |
| i18n | i18next / react-i18next, languages en and id |
| Firebase project | catat-saja |
Prerequisites: Node.js 24 with Corepack enabled (corepack enable, which provides the pinned pnpm), and firebase-tools.
git clone https://github.com/ardeman/project-catatsaja-reactrouter.git
cd project-catatsaja-reactrouter
pnpm install # also installs the Husky Git hooks
cp .env.example .env # then fill in the values below
pnpm dlx [email protected] login
pnpm dlx [email protected] deploy --only firestore # rules and indexes
pnpm dlx [email protected] deploy --only functions # push notifications (Blaze plan)
pnpm dev # http://localhost:5173
Separate projects (the promo video project) are found through external-projects.json: copy external-projects.example.json to it and point each section's folder, readme and rules at your checkouts. Like .env, the copy is not committed. The promo and stories videos are made in the video project (video_marketing); no media or promo scripts go in this repository.
pnpm refuses package versions published less than a day ago (its minimumReleaseAge default), so a brand-new release can only be installed the next day. Dependency install scripts run only when allowed in pnpm-workspace.yaml (allowBuilds).
.env is git-ignored. In CI the same names come from repository secrets.
| Variable | Used for |
|---|---|
VITE_FIREBASE_* | Firebase web app config (Project settings → Your apps) |
VITE_GRAVATAR_API_KEY | Profile avatars from Gravatar |
VITE_FCM_VAPID_KEY | Optional. Public key of the web push key pair (Project settings → Cloud Messaging → Web Push certificates); push notifications are off without it |
VITE_APP_CHECK_SITE_KEY | Optional. reCAPTCHA Enterprise site key for Firebase App Check; App Check is off without it |
VITE_APP_CHECK_DEBUG_TOKEN | Development only. A registered App Check debug token, kept in the ignored .env; ignored in production |
VITE_USE_EMULATORS | Development only (not set in the workflow): true runs pnpm dev against the local Auth, Firestore and Functions emulators (pnpm emulators, needs Java 21) |
Variables are read in one place, app/lib/utils/environment.ts.
When developing against Firebase with App Check enabled, create a debug token in Firebase console → App Check → Apps → the web app's menu → Manage debug tokens, and save it as VITE_APP_CHECK_DEBUG_TOKEN in .env. Restart pnpm dev after changing it. This lets local browsers use the same registered token instead of generating unregistered ones that receive HTTP 403. Without that variable, register each browser's token printed in its console. Keep debug tokens private and revoke them in Firebase if exposed. The emulator mode needs no token. See Firebase's debug provider guide.
| Task | Command |
|---|---|
| Install dependencies | pnpm install |
| Dev server | pnpm dev |
| Lint | pnpm lint |
| Typecheck the app and the Cloud Functions (generates route types first) | pnpm typecheck |
| Unused files, exports and dependencies | pnpm knip |
| Format | pnpm format |
Regenerate public/site.webmanifest from scripts/generate-manifest.mjs and app/lib/constants/metadata.ts | pnpm generate:manifest |
Redraw every icon in public/ (PNGs, favicon.ico, logo.svg) from scripts/icon/logo.svg, with Chrome through Playwright | pnpm generate:icons |
| Production build (includes the manifest) | pnpm build |
| Preview the production build | pnpm preview |
Build the Cloud Functions (functions/src to functions/lib) | pnpm build:functions |
Rewrite functions/package-lock.json after changing functions/package.json (npm, releases at least a day old) | pnpm lock:functions |
Run the Auth, Firestore and Functions emulators (with VITE_USE_EMULATORS=true; Java 21; builds the functions first) | pnpm emulators |
| Deploy Firestore rules and indexes (when pnpm asks which packages may build, choose none: firebase-tools doesn't need them) | pnpm dlx [email protected] deploy --only firestore |
| Deploy the Cloud Functions (checks the lockfile and builds them first) | pnpm dlx [email protected] deploy --only functions |
| Maintenance mode and the oldest app allowed, with your gcloud sign-in (README → Maintenance mode) | pnpm maintenance status|on|off |
| Reinstall Git hooks | pnpm prepare |
Checks — run before every commit and pull request. It must pass with no errors:
pnpm validate # lint + typecheck + knip + unit tests
Rules tests — pnpm test:rules runs tests/firestore-rules.test.mjs against the Firestore emulator (it needs Java 21). The deploy workflow runs them before every deploy, so they don't have to pass locally.
End-to-end tests — pnpm test:e2e runs four suites sequentially against the Auth and Firestore emulators, using Playwright and the installed Google Chrome (needs Java 21 and port 5199 free):
tests/e2e/encryption.test.mjs: the dependent encryption lifecycle with Ana, Budi and Cici.tests/e2e/sharing.test.mjs: independent accounts, sharing suggestions and invitations (with the title sealed for the invited person's push devices), read-only note editing, archiving, blocking, encrypted finance month storage, a book's groups of categories, budgets, savings goals and keywords (Plus, encrypted, seen by a reader), recurring entries (made from an entry, added and skipped when due, encrypted), and a free writer using a Plus owner's book to set goals and link an expense to one.tests/e2e/ai.test.mjs: receipt scanning, quick add, note and task list helpers, dictation (with Chrome's fake microphone) and taking back consent, with Gemini replaced by fixed answers, plus health reminders and medicines.tests/e2e/ui.test.mjs: interface behaviour only: adding, editing and reordering tasks and keeping unfinished task input, subtasks (indenting, checking with their item, kept after a reload), touch cursor placement on mobile, note keyboard saving with and without encryption, button and right-click formatting (touch and hold left to the phone), undo and redo in task lists, books and health logs, and the one-time notifications step after encryption (as an iPhone outside the installed app, since the emulators have no push service).The deploy workflow runs the four files in parallel on separate runners, each with its own app and emulators, and runs a failed file once more before it counts as failed. Every suite except ui must pass before a deploy: they guard what is stored, shared and encrypted. ui runs in its own job beside the deploy; when it fails twice, the job still passes with a warning annotation, so neither the deploy nor the run's status is held back. The system packages Chrome needs are cached per runner image, since Ubuntu's mirror is sometimes slow. A push or pull request that changes only Markdown or docs/ skips them. Scenarios within each file run in order; the local command stays sequential to avoid port and database conflicts. For adding or splitting suites, follow AGENTS.md.
Locally, run pnpm validate and only the E2E file or scenario affected by a change. The full E2E suite is not required locally before each push; CI runs it before deployment. For example, with Java 21 on your path, check task editing with:
pnpm dlx [email protected] emulators:exec --only auth,firestore --project demo-catatsaja 'node --test --test-concurrency=1 --test-name-pattern="task input" tests/e2e/ui.test.mjs'
For documentation and static crawler files, no local E2E run is needed. Reuse passing checks when the relevant code is unchanged. Run the full local suite when requested or when investigating a failure that targeted tests cannot resolve.
Husky installs two hooks:
pre-commit runs lint-staged (.lintstagedrc.cjs): eslint --fix and prettier --write on staged code, prettier --write on CSS, JSON and Markdown.commit-msg runs commitlint with @commitlint/config-conventional.Commit subjects use type(scope)!: description, with optional scope and ! for breaking changes, for example feat(finances): add currency preview. Common types are feat, fix, docs, style, refactor, perf, test, build, ci and chore.
.github/workflows/firebase-remix.yml runs checks on every push to main, pull request, every Monday (to catch a runner, Chrome or emulator change before it blocks a deploy) and by hand. validation installs dependencies, runs pnpm validate and the Firestore rules tests (pnpm test:rules, with Java). changes looks at what a push or pull request changed; when it is only Markdown or docs/, the end-to-end tests are skipped. Otherwise, in parallel, the e2e matrix runs each end-to-end test file on its own runner, with its own app, Auth and Firestore emulators, and Chrome installed by Playwright, retrying a failed file once. All four suites finish even if one fails. This shortens the wait at the cost of more runner minutes. checks keeps the existing status name for branch protection and fails unless validation and every E2E suite succeed (the ui suite ends as a warning on a passing job rather than failing; skipped suites pass only for docs-only changes). On a push to main or a run by hand on main, build installs dependencies and builds with the secrets above while the tests run, and keeps build/client as an artifact for a day; deploy runs after checks and build, and only uploads that artifact to the live channel of Firebase Hosting, one deploy at a time, so a slow install never holds back a deploy that has passed its checks. Nothing comes from the runner image (Node from .nvmrc, pnpm from packageManager, Java, Chrome and the Firebase CLI pinned or installed), so moving ubuntu-latest to a new Ubuntu doesn't change the build; Dependabot (.github/dependabot.yml) proposes action updates monthly. Firestore rules and indexes are not deployed by the workflow; deploy them with the command above when firestore.rules or firestore.indexes.json change.
The Cloud Functions (functions/, push notifications) aren't deployed by the workflow either, so its service account keeps only its Hosting roles: deploy them with deploy --only functions when functions/ changes, after the rules they rely on and before the app that writes for them. Cloud Build installs them with npm, outside the pnpm workspace, from functions/package-lock.json: run pnpm lock:functions after changing functions/package.json (it resolves only releases at least a day old, like pnpm), and pnpm validate and the deploy refuse a lockfile that no longer matches. Each deploy leaves a container image in Artifact Registry; the first functions deploy asks how many days to keep them (1 is set), and pnpm dlx [email protected] functions:artifacts:setpolicy --location asia-southeast2 --days 1 changes it later (it fails before that first deploy, which creates the repository). On a project's first 2nd-gen deploy, Firebase may ask for IAM roles for Google's service agents (run the gcloud commands it prints, as an owner), and the Firestore trigger can fail while Eventarc's permissions spread: retry it after a few minutes. The functions run on the Blaze plan; a budget alert in Google Cloud Billing warns before costs grow.
The Gemini helpers call the Agent Platform Gemini API (formerly Vertex AI) through Firebase AI Logic, billed to the project's Cloud Billing account; the Gemini Developer API is no longer used (its prepaid credit in AI Studio is separate from Cloud Billing). In Firebase console → AI Logic → Settings, the Agent Platform Gemini API is enabled, Enforce authenticated-users mode is on (only signed-in people can call Gemini) and AI monitoring is on (token use and errors per feature). Firebase console → Usage and billing → Service-level spend caps holds a monthly cap on the Agent Platform API that pauses Gemini until the next month when reached (change it there as use grows); Google's per-user limit of 100 requests a minute can't be changed.
Hosting (firebase.json) serves the prerendered pages as files and every other path __spa-fallback.html, without trailing slashes. Pages are sent with Cache-Control: no-cache and the hashed files in /assets/ are cached for a year, so a deploy never leaves browsers with pages that point at deleted files.
The service worker (public/sw.js, registered in production builds only) caches the app shell, the public pages and the files they load, so the installed app starts offline. Pages are fetched from the network first, so an online visit always gets the latest deploy; Firebase and Firestore requests are not touched. To try the production build with these rules locally: pnpm build && npx firebase-tools emulators:start --only hosting (port 5000 is often taken on macOS; it moves to the next free one).
For a deploy that could break the app while it runs (a data change, or rules that refuse what the running app writes), close the app first. In the Firebase console, open the config collection and the app document (create them the first time) and set the fields below, or use pnpm maintenance (scripts/maintenance.mjs), which writes only the fields you name through Firestore's REST API with your gcloud auth login:
| Field | Type | What it does |
|---|---|---|
maintenance | boolean | true shows the maintenance screen instead of the signed-in app and sign-in pages. |
message | string | Optional: shown on that screen, under the standard text (in the language you write). |
until | timestamp | Optional: when it is expected to end, shown in each person's time. |
minAppVersion | number | Optional: apps older than this (appVersion in app/lib/constants/app-version.ts) show "A new version is ready" with a Reload button instead. |
pnpm maintenance status
pnpm maintenance on --min-app-version 2 --message "Back in a few minutes" --until 2026-10-14T21:00+07:00
pnpm maintenance off
For a change to how data is stored, raise appVersion in the release and set minAppVersion to it when you turn maintenance off: maintenance alone lets a tab left open on the old app resume and write the old format.
Open apps switch at once, without a reload; the public pages (landing, About, How to use and the rest) stay up. Deploy, check the app, then set maintenance to false. Nobody can change config from the app (firestore.rules).
The production build groups shared icons, UI libraries and the small components used by public pages in vite.config.ts to reduce script requests. Feature modules keep their separate dynamic imports; avoid merging all shared code into one bundle, which pulls unused app features onto public pages.
public/sitemap.xml lists the six prerendered public pages. Keep it in step with prerender in react-router.config.ts when adding or removing a public page. public/robots.txt points crawlers to the sitemap and excludes sign-in and app paths from crawling.
In Google Search Console, the ardeman.com Domain property covers this app's subdomain; verify it with Google's DNS TXT record. A URL-prefix property for https://catatsaja.ardeman.com/ is also an option. After deploying these files, submit https://catatsaja.ardeman.com/sitemap.xml in Sitemaps. Use URL Inspection to check the homepage and request indexing. See Google's setup guide.
app/
root.tsx App shell: Firebase and theme providers, root layout, hydrate fallback
routes.ts Flat file routes plus a catch-all (catch-all.tsx)
routes/ Route modules (thin: each renders one page component)
_layout._home.* public pages (about, help, privacy, terms, changelog)
_layout.auth.* sign in, sign up, forgot password
_layout._main.* signed-in app (notes, tasks, finances, health, settings)
apis/firestore/ Firestore reads and writes per collection (note, task, finance, health-log, user, currency)
apis/ai/ Gemini calls through Firebase AI Logic (receipt, typed entries, note and task list helpers, dictation)
apis/push.ts This device's push subscription (Firebase Cloud Messaging)
components/
ui/ shadcn/ui primitives (generated)
base/ Shared app components built on ui/
layouts/ Navbar, sidebar, footer, root layout
pages/ One folder per page: index.tsx, context, parts, type.ts
lib/
configs/ Firebase and page config
constants/ Metadata (also the source of the web manifest), Firebase constants
contexts/ Firebase and theme React contexts
hooks/ One hook per action (use-get-*, use-create-*, use-update-*, ...)
types/ Request/response types per domain
validations/ zod schemas per domain
utils/ Environment, parsers, auth helpers, cn()
content/ changelog.ts: release notes for /changelog, in both languages
localization/ i18next setup; locales/<en|id>/common.json and zod.json
styles/ tailwind.css (Tailwind 4 config: theme, plugins, colours), Milkdown editor theme
public/ Icons (generated), site.webmanifest (generated), sw.js (offline start, push notifications), robots.txt, sitemap.xml
functions/ Cloud Functions (own pnpm workspace package, Node 24, asia-southeast2 beside Firestore) that send push notifications
scripts/ generate-manifest.mjs, generate-icons.mjs and the logo's vector master (icon/logo.svg)
firestore.rules Security rules (owner and read/write permission lists)
firestore.indexes.json
Each topic has one home. Update that file instead of copying its content somewhere else.
| File | Audience | Owns |
|---|---|---|
README.md | Everyone | Objective, features, roadmap, setup, commands, deployment, layout |
AGENTS.md | AI coding agents (and humans who want the rules) | Definition of done, general conventions, guardrails |
docs/sharing-and-encryption.md, docs/ui-patterns.md, docs/installed-app.md, docs/ai-features.md | AI coding agents (and humans who want the rules) | Conventions for one area each, linked from AGENTS.md |
docs/decisions.md | Everyone | Architectural decisions, one line each with its reason |
docs/marketing-videos.md | Whoever makes the promo videos | Rules for working with the video project from this repo |
CLAUDE.md, GEMINI.md | Claude Code, Gemini CLI | Only an import of AGENTS.md |
LICENSE | Everyone | Terms for using the code (MIT) |
video_marketing.readme in external-projects.json (separate repo) | Whoever makes the promo videos | Promo and stories videos, their scripts and uploads |
Codex, Cursor, GitHub Copilot and other agents that follow the AGENTS.md convention read AGENTS.md directly.
Feel free to fork this repository. If you do, please give credit by linking back to ardeman.com. Thank you!
MIT. Third-party packages keep their own licenses.