Oracle3
Oracle3: 예측시장 거래 엔진·MCP 서버
수입·지출·저축을 기록하고 주·월·분기·연도별로 확인하며 투자 가치를 추적하고 주의 사항을 간단히 제안합니다
Log what you earn, spend and save. See it by week, month, quarter and year, track what your investments are actually worth, and get plain suggestions on the parts that need attention.
금융JavaScript · CSSGitHub @Ark07Yad온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기CoinTrack — a local-first expense tracker. Log earnings, expenses and savings; visualise them by week, month, quarter and year; track what your investments are worth; and get plain, numeric suggestions computed from your own entries. React + Vite, nothing leaves the browser.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A local-first expense tracker. Log what you earn, spend and save; see it by week,
month, quarter and year; keep a running figure for what your investments are
worth; and get plain, numeric suggestions about the parts that need attention.
Everything lives in the browser. There is no account, no server and no sync —
the data is stored in IndexedDB with a localStorage mirror, and nothing is ever
sent anywhere.
Then open. npm run build produces a static dist/,
npm run lint runs oxlint, and npm test runs the suite.README 전문 보기 ↗Live: https://cointrack.ark07yad.workers.dev
A local-first expense tracker. Log what you earn, spend and save; see it by week, month, quarter and year; keep a running figure for what your investments are worth; and get plain, numeric suggestions about the parts that need attention.
Everything lives in the browser. There is no account, no server and no sync — the data is stored in IndexedDB with a localStorage mirror, and nothing is ever sent anywhere.
npm install && npm run dev
Then open http://localhost:5181. npm run build produces a static dist/,
npm run lint runs oxlint, and npm test runs the suite.
On first run you can either set up a profile or load five months of generated sample data — the sample set is seeded, so it comes out the same every time, and it is deliberately imperfect (one overspent month, an uneven freelance income, a holding nobody has revalued) so the charts and the advisor have something real to say.
| Home | This month at a glance: a spend dial against your budgets with a pace marker, in/out/saved with change against last month, the three most urgent suggestions, a 30-day spend trend, the category mix, budget bars and net worth. |
| Ledger | The daily log. A fourteen-day rhythm strip, one day's entries with running totals, and a searchable archive of everything grouped by date. Each entry has a title, an amount, a category, a date and an optional description. Recurring entries are offered here for confirmation, and their schedules are managed at the bottom. |
| Trends | The same four questions at four zoom levels — week, month, quarter, year — with a cash-flow chart, a running-total view against a paced budget, a stacked category-mix view, a breakdown donut, biggest movements against the previous period, a per-category table, a daily-spend heatmap and a side-by-side comparison table. A category filter narrows the whole page at once. |
| Invest | Savings goals and holdings. A goal is a target, optionally with a date, and progress comes from savings entries tagged to it — the figure that matters is what you would have to set aside per month from here, against what you have actually been putting aside. Below that: what each holding is worth and what you paid in, plotted separately so growth is distinguishable from deposits, with allocation, per-holding gain, stale-value warnings and a contribution history. |
| Advice | The suggestion box. Pick a section — the whole picture, spending, budgets, income, savings, investments, or a single category — and get rules applied to your own entries, plus a place to keep your own notes. |
| Settings | Profile, currency, budgets, and your data: export, restore, CSV import, sample data, erase. |
React 19, Vite, Tailwind v4 and Recharts. No UI kit, no icon package, no state
library — the icons are inline SVG, the motion is hand-rolled on
requestAnimationFrame and the Web Animations API, and state is one object in a
reducer behind a context.
src/
lib/
calc.js dates, period ranges, bucketing, money formatting
recurring.js schedules for repeating entries
goals.js savings targets and what they require per month
csv.js bank-statement parsing, mapping and category guessing
data.js the three ledger kinds, categories, asset classes, currencies
useFinance.js every derived number, as pure functions plus thin hooks
insights.js the rule engine behind the advisor
store.jsx state, reducer, persistence, theme
persist.js IndexedDB + localStorage, with daily snapshots
motion.js count-up, stagger, FLIP list reflow
demo.js the seeded sample data
components/ one file per screen, plus ui.jsx and EntrySheet.jsx
A few decisions worth knowing about:
Dates are strings, never Date objects. A day is 'YYYY-MM-DD' in local
time. Storing an instant means an entry logged at 11pm moves to the next day when
the machine changes timezone. Date is used only as a calculator, always
constructed at local noon so a daylight-saving shift cannot roll a date
backwards.
One period engine. Week, month, quarter and year all resolve through
periodRange() and bucketsOf(), so a boundary day cannot be counted one way in
the month view and another way in the quarter view. Quarter weeks are clipped to
the quarter, so the bars always sum to the total.
Pace, not just totals. Every budget carries the share of the period that has elapsed. On the 6th, 60% of a budget spent is alarming; on the 26th it is fine, and a bar that cannot tell the difference is not worth drawing.
Contributions are kept separate from value. Without that column every deposit into an investment reads as growth. The dashed line on the net-worth chart is what you paid in; only the gap above it is a return.
Aggregation is pure. The advisor calls the same functions the charts do, outside React. If the two computed their own totals they would eventually disagree, and the app would be quietly lying somewhere.
Recurring entries are offered, never posted. A schedule works out what it owes and puts it in a queue; you confirm or skip each one. Auto-posting is less typing, but a ledger that invents transactions is one you cannot trust, and the only thing this app has going for it is that its numbers are ones you put there. Skipping advances the schedule exactly as posting does, so a subscription you cancelled stops asking.
A foreign payment is provenance, not a second unit. The amount on an entry is always in your home currency, converted once when it is logged, with the original amount and the rate you got stored alongside it. Every total, budget, chart and rule therefore needs no knowledge of currencies at all. Making the ledger genuinely multi-unit would mean converting inside every aggregation, at the cost of a rate lookup this app has no honest way to perform offline — and storing the rate at entry time means a later change never rewrites history.
The CSV importer guesses, then shows its guesses. Bank exports agree on nothing: delimiter, date order, decimal separator, or which sign means money leaving. The two most dangerous — date order and sign convention — are surfaced as controls rather than assumed, because both fail silently. A wrong date order shifts a year of history; a wrong sign turns spending into income. Where the evidence is genuinely ambiguous the parser says so instead of picking.
Receipts live outside the state object. Entries carry only an attachment's name, type and size; the bytes go into a separate IndexedDB store. The state is serialised and mirrored to localStorage on every change, and a single phone photo would exceed that mirror's entire quota — and be re-serialised on every keystroke. Images are downscaled to 1600px before storing, and orphaned blobs are swept after the fact rather than during a delete, so deleting an entry stays synchronous and cannot half-fail.
Two tabs do not fight. Each tab holds and saves the whole state, so without
coordination the second one to write silently wipes the first one's entries. A
BroadcastChannel carries only a timestamp; a tab that hears about a newer write
re-reads storage and decides for itself. Adopting a remote state deliberately
does not trigger a save — that echo is what turns coordination into an
infinite write loop between the two tabs.
The advisor is the expensive part: it re-derives whole months to answer questions, and the dashboard asks it for four sections on every render. With two years of entries that measured 28ms, which is two dropped frames on the render path for work that had already been done.
Derived values are now cached against the identity of the state they came from.
State is replaced wholesale on every change, so object identity is data
identity, and a WeakMap means a superseded state takes its cache with it.
That took the same call to 4.4ms cold and effectively free on every render
after the first. src/lib/perf.test.js holds the budget, and deliberately
measures a cold call — a benchmark that only ever hits the cache is the cache
testing itself.
The bundle is split so the first paint carries about 106 kB gzipped instead of 234 kB. Recharts is larger than React and all of the application code together, so it is loaded after the page is usable: the four non-landing screens are route-split, and the dashboard's own charts are a lazy island behind the hero. The figures and suggestions are readable while the charts are still arriving, which is the order people read them in anyway.
Installable, and works with no network. The service worker in public/sw.js
caches only the app's own files — there is no data in it, since the ledger lives
in IndexedDB and never passes through a request. Built assets are cache-first
because their filenames contain a content hash and cannot change; navigations
are network-first so a deploy is picked up immediately, with the cached shell as
the fallback rather than the default. That distinction is what stops a service
worker stranding people on a version from March.
1–6 for screens, n for a new entry, t to jump to today, d for theme,
? for the list. Single keys with no modifiers, which is only safe because they
are ignored entirely whenever focus is in a field — otherwise typing "n" in a
description would silently open a dialog and lose the entry.
Dialogs trap Tab and return focus to whatever opened them, and toasts announce themselves through a live region.
npm test # 253 unit and component tests
npm run test:e2e # 17 journeys × chromium, firefox and webkit
npm run test:all # both
CI runs the lot on every push and pull request — lint, unit tests and a build,
then the journeys across all three engines in parallel jobs. Deployment happens
only after CI is green on main, and skips itself with a note rather than
failing if no Cloudflare credentials are configured.
253 tests. The engines run in node — dates and period boundaries, aggregation, schedules, goals, CSV parsing, the advisor's rules and a performance budget. Component tests opt into jsdom with a per-file docblock, so a unit test never pays for a document it does not use. They lean on properties rather than golden values where they can: the chart buckets must sum to the headline the screen prints, a category that vanished must still count as a movement, and a holding nobody revalued must not drag net worth to zero. The advisor suite also pins the boundary in the next section, so it cannot be eroded by accident.
The component tests deliberately cover the behaviours that fail silently: a focus trap that has stopped trapping looks identical to one that works unless you are using a keyboard; a shortcut that fires while you are typing loses the entry you were writing; a form that shows a goal selected and saves without it looks correct until you check the number weeks later. Several are regressions that actually happened here, kept as tests so they cannot happen twice.
The end-to-end tests run Playwright against the built app served by vite preview, on Chromium, Firefox and WebKit, not the dev server — the code-split chunks, the generated headers and
the service worker only exist in a production build, and two of them have
already been the subject of real bugs. There are deliberately few of them, and
each is a whole journey: setting up and logging a first entry, data surviving a
reload and a second tab, every screen and all four period levels rendering
without throwing, and the things only a real browser can show at all — an
export that produces an actual file, a CSV picked from disk, a receipt stored in
IndexedDB, and the app still running with the network switched off.
Running three engines is not ceremony. It immediately found two real bugs that
Chromium cannot show: WebKit refuses to structured-clone a Blob into
IndexedDB, so every receipt failed to save in Safari with the app reporting it
as being out of storage; and the focus trap let Tab escape to the document body
for one hop, because a date input is several tab stops in some engines and
"the last focusable element" is therefore not the last thing Tab reaches.
Every suggestion is a deterministic rule over your own entries, and each one names a figure you can go and check — there is no model involved and nothing leaves the browser. Rules stay silent until there is enough history to support them, and anything you dismiss stays dismissed by rule identity rather than reappearing next month with a new key.
The investments section describes the holdings you have recorded: how large, how concentrated, how current the figures are, whether contributions are being logged. It does not value anything for you, it has no price feed, and it will not tell you what to buy, sell or hold.
Hosted on Cloudflare as a static-assets Worker, which is the recommended path for new projects in place of Pages. It fits inside the free plan comfortably: the app is entirely client-side, so the Worker only executes on a cache miss for a client route or a 404 — every real page load is served straight from Cloudflare's asset server.
npm run deploy # build, generate dist/_headers, wrangler deploy
Two details are load-bearing:
not_found_handling = "none", with the SPA fallback done by hand. The
built-in single-page-application mode answers every miss with index.html and a
200, including a hashed bundle that is genuinely missing — which happens for
real when a client holding the previous index.html requests an asset that a
deploy has just replaced. The browser then reports a MIME-type error that says
nothing about the actual problem. worker-entry.js separates the two: a real
404 for a missing asset, the app shell for a client route.
Security headers are written twice, from one module. Cloudflare's asset
server answers requests matching a built file without invoking the Worker, so
headers set only in the Worker would cover 404s and nothing a visitor actually
loads. scripts/gen-headers.mjs writes dist/_headers at build time and
worker-entry.js applies the same set to what it still handles; both import
security-headers.js, so they cannot drift.
The policy is strict — script-src 'self', connect-src 'self', no framing.
The app keeps a complete personal financial history in IndexedDB, and refusing
to run anything but its own bundle is what makes that hard to read.
MIT.