TreeMap
무료 오픈소스 디스크 사용량 시각화 도구로, 사각형 트리맵·중복 파일 탐색·디스크 가득 예측·정리 시 파일을 휴지통으로만 이동한다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기AI journaling app.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A private daily journal that reads what you wrote, names the patterns, and lines
them up against what you actually got done in Habit Casino.
Everything is one file — index.html. No build step, no package manager, no
server of mine. Your entries live in your browser; the only outbound calls are
to the AI provider you configure and (if you switch it on) your own Firebase
project.
Live at https://thruline-a12e4.web.app — Firebase Hosting, the only host.README 전문 보기 ↗A private daily journal that reads what you wrote, names the patterns, and lines them up against what you actually got done in Habit Casino.
Everything is one file — index.html. No build step, no package manager, no
server of mine. Your entries live in your browser; the only outbound calls are
to the AI provider you configure and (if you switch it on) your own Firebase
project.
Live at https://thruline-a12e4.web.app — Firebase Hosting, the only host.
GitHub Pages was switched off on 2026-09-08. Two hosts fed by two unlinked pipelines could drift silently, and only the Firebase domain is auto-authorized for Google sign-in, so a stale Firebase deploy was the one failure that actually mattered. The repo is still the home of the code — every change is committed and pushed — it just does not serve anything.
Any other origin you want to open the app from (a Tailscale name, a custom
domain) must be added by hand under Authentication → Settings → Authorized
domains, or sign-in fails with auth/unauthorized-domain.
python3 -m http.server 4780 --directory /Users/simonsakata/Downloads/ThruLine
Then open http://localhost:4780. (Also registered as the throughline launch
config, so Claude Code can start it directly.)
Two steps, and both are required — nothing links them, so skipping either leaves the repo and the live site disagreeing:
git add -A && git commit -m "your change" && git push
firebase deploy --only hosting
The first records the change; the second is what users actually get. Firebase uploads from this folder on disk, not from GitHub.
HTTPS is on by default, which matters here — the microphone (Web Speech
dictation) only works on a secure origin, so the deployed page can do voice
input where a plain http:// host could not. On your phone, open the URL and
use Share → Add to Home Screen; it runs full-screen like an app.
If the two-step ever becomes a nuisance, firebase init hosting:github will
generate a GitHub Actions workflow that deploys on push, collapsing it back to
one command.
Settings → Habit Casino → Connect Habit Casino cloud signs in to Habit
Casino's own Firebase project (habitcasino-5d08b) and reads its cloud save —
one document at users/{uid} holding its whole state as a JSON string —
directly. It refreshes on every load, and again when you open Patterns if the
last read is over five minutes old, so there is no export file to fetch.
Nothing in Habit Casino changes. Its rules already allow only the owner to read
that document, and this runs as a second, separate Firebase app instance
('habits'), so Throughline's own sync is untouched.
Two things have to be true. You must be signed in to cloud save inside Habit
Casino — only its habitcasino-5d08b.web.app origin can do that, since the
GitHub Pages copy is not on that project's authorized-domains list. And this
app's own domain has to be on that same list, or sign-in fails with
auth/unauthorized-domain (the card in Settings says so when it happens).
The file import and "Paste JSON" remain as fallbacks, and if Habit Casino is
ever served from this same origin, "Read from this browser" reads its
localStorage['habitCasino.v1'] with no account involved at all.
| What it does | Where it lives | |
|---|---|---|
| Reflect | two calls: a cheap one searches the journal, an expensive one reads the day. Returns a summary, what stands out, what the conversation got to, what it connects to, tags, a mood read. No conversation of its own. | the day's entry |
| ✦ Go deeper | the back-and-forth. Opens under the entry and keeps going as long as you like; depth setting controls length and effort. | the day's entry |
| Chat | its own tab, not tied to a day. Knows your profile, lessons, recent entries, and pulls up an older day or an earlier chat when what you say matches one. | state.chats |
Reflect used to hold a thread too, which made it a second, worse version of Go
deeper. It doesn't any more. The reflections and threads you'd already written
were not thrown away: on first load migrateReflections() appends each one to
the bottom of its own entry under a ✦ Reflection · <date> rule, backfills
ai.summary if it was empty, keeps the raw copy in ai.archived, and writes the
whole pre-migration blob to throughline.v1.before-reflections-moved first. It
runs once, guarded by state.meta.reflectionsInBody.
Click the day name at the top of the write view ("Today", "Tue, Sep 22") for a month grid: Monday-first, a dot under every day with something written, future days disabled, ‹ › to change month. Escape or the backdrop closes it.
Reflect is two calls, and the cheap one goes first.
The search call (searchSources, Haiku by default) gets the whole journal
as raw text and hands back at most six of your own paragraphs — quoted, dated,
one line each on why it pulled them. It is looking for connections of meaning:
the landlord you avoided emailing in July and the supplier you are not calling
today share no vocabulary, so findEchoes can never put them together, and a
model reading both does it instantly. Every quote is checked against the entry
it claims before it goes anywhere (verifyPassages, whitespace-insensitive);
anything that isn't word for word is thrown out and counted.
The reflection call then reasons over that instead of scanning for it.
What comes back is stored on the entry as e.sources and shown under "What it
read first" below the reflection, so you can check a reflection against what it
was actually looking at. It is scratch: no other entry's prompt reads it, and
nothing carries it forward. The one thing that does persist is ai.summary, so
the prompt tells that field to describe this day only — connections go in
connections, which is read and then forgotten.
Costs about a cent per reflection at Haiku's $0.10/MTok, even projected out to
a five-year journal (~81K tokens). Prompt caching doesn't help: journaling is a
once-a-day thing and the cache is cold by then. When the journal outgrows
SEARCH_CORPUS_CHARS the oldest entries fall out of the search — that's the
point to put a filter in front of it.
Underneath that, three layers reach a reflection, in ascending durability:
| Layer | What | Cost |
|---|---|---|
| Recent | last 12 days in full — context is mostly local | ~500 tokens |
| Echoes | older entries pulled in only when today's language matches them (findEchoes, local term overlap — no embeddings), plus a one-line-per-month skeleton of everything else. These arrive as Claude's summaries; the search call above is what brings back your own words | ~450 tokens |
| Standing | the profile the model keeps about you, and the lessons you wrote | grows slowly |
The echo layer is why a reflection in November can reach March. Sending the whole corpus would also fit — five years is only ~81K tokens — but it buries today under a year of one-liners; this keeps recall without the dilution.
What it knows about you (Patterns tab) is a set of notes the model maintains about how you think, rebuilt from the whole journal and loaded into every conversation. It revises rather than rewrites, is told to say when it changes its mind, keeps its last 6 versions, and is editable — anything you correct is what future reflections read.
Chat echoes work the same way in the other direction: a free chat is scored
against your entries and your earlier chats (findChatEchoes), and whatever
matches rides into the next reply. Chats are memory too — a reflection or a
going-deeper thread can quote one back.
Life lessons are the opposite direction: conclusions you wrote deliberately. They go into every call with an instruction to quote one back only when the day genuinely touches it, never to recite the list. "Save a lesson" sits on every reflection, where you tend to have the thought.
Before a reflection, a fast low-effort conceptPass returns 4–7 concepts, each
with the exact phrase in your entry it came from, plus which past themes it
echoes. Quotes that don't appear verbatim are dropped, so a highlight can never
land on the wrong text.
The animation renders that: a transparent mirror of the textarea (a textarea can't style its own contents) lights up each quoted span, a bubble rises off it, remembered themes drift in from the edges, and both converge where the reflection is about to appear. It clears the moment real prose starts arriving.
Every bubble is something actually sent to the model. It is not a decorative loader — the model's real chain of thought is never returned by the API, so animating "its thinking" would be fiction. This animates the inputs, which are real, and it covers latency the concept pass genuinely creates.
Start typing and the app fades out — header, mood scales, panels, buttons and nav all go, the sheet loses its border, and the editor fills the screen.
Getting out is deliberately hard to do by accident. Only moving the pointer
within 100px of a screen edge ends it (ZEN_EDGE) — roaming around the
middle is reading your own writing, not asking for the app back. The pointer
stays visible throughout, since you have to see it to find an edge. There is no
scroll handler at all, so you can scroll freely while staying in focus. Escape
and the low-contrast "done" chip cover touch and keyboard-only use, it stays off
while dictating so the stop button remains reachable, and leaving the Write tab
always restores the chrome.
The editor auto-grows to fit its content, so the page is the only thing that
scrolls — a textarea scrolling inside a scrolling page is a trap to get stuck
in. In focus mode sizeZenPad() adds exactly enough trailing space for the last
line of text to scroll up to the top third of the screen, no further; whatever
the faded panels below already contribute is subtracted, so it lands on the same
mark for a three-line entry and a two-hundred-line one.
Toggle it in Settings → Reflection style.
Settings → AI. Three providers:
| Provider | What you need | Default model |
|---|---|---|
| Anthropic | An API key from console.anthropic.com | claude-opus-5-5 |
| OpenAI | An API key | gpt-4.1 |
| Ollama | Ollama running locally | qwen3:8b |
Reflections run as two calls in parallel: a streaming prose reply with no
JSON schema (that constraint is what made an earlier version answer in clipped
single sentences), and a small structured tagging pass that feeds the Entries
list and the Patterns charts. Both use adaptive thinking. Settings →
"How much should it write back?" picks Brief / Conversational / Deep, which sets
the target length and the reasoning effort (medium / high / xhigh).
Talk it through (the button under the editor) opens a conversation before
or while you write, as opposed to the reflection, which comes after. It opens
by saying something real — an observation across recent entries, a guess at what
is going on — rather than firing a question and stopping, and then it is just a
chat. It sees the draft you have so far. Threads save on the entry as muse
and are separate from the reflection thread. With no API key the button falls
back to dropping a written prompt into the entry, as it used to.
You can reply to any reflection and keep talking — the entry, its context and the first reflection are replayed as a real conversation, so a follow-up continues the thread instead of starting over. Threads are saved with the entry.
The key is stored under its own localStorage entry (throughline.ai), separate
from the journal, so exporting or erasing your journal never touches it. It is
sent only to the provider, straight from your browser.
Anthropic calls go out with anthropic-dangerous-direct-browser-access: true
and the server-side refusal fallback enabled — journals go to dark places, and a
classifier declining to read one is a bad experience. If a browser ever rejects
the beta header on preflight, the app retries once without it automatically.
The config is already baked into FB_CONFIG at the top of index.html
(project thruline-a12e4), so on a new device the only step is Settings →
Sync & backup → Firebase sync → Sign in with Google. Those values are public
identifiers by design; the rules and the authorized-domain list are what protect
the data. The paste box under "Use a different Firebase project" overrides the
built-in config on one device only.
Deploy hosting and rules with:
firebase deploy
Entries merge per-entry by updatedAt, so writing on your phone and then your
laptop never silently drops one of them.
You need two things enabled in the Firebase console:
firebase deploy --only firestore:rules
to apply firestore.rules (each user can only read and write their own doc).Sync is off until you set it up; without it the app is entirely local, which is also a legitimate way to run it — just export a backup now and then.
index.htmlThe <script> is banner-commented in this order:
DATA MODEL · HELPERS · AI · SOURCE SEARCH · THINKING ANIMATION
WRITE VIEW · MEMORY · CHAT · ANALYTICS · CHARTS · ENTRIES VIEW · PATTERNS VIEW
PROFILE & LESSONS · HABIT CASINO · FIREBASE SYNC · SETTINGS
FOCUS MODE · MODAL + CALENDAR · ROUTING & INIT
Storage keys:
| Key | Holds |
|---|---|
throughline.v1 | entries (text, reflections, going-deeper threads, the passages each search pulled up), free chats, reports, prefs — the export payload |
throughline.v1.before-reflections-moved | one-time safety copy taken before reflections were folded into entry bodies |
throughline.ai | provider, key, model — never exported |
throughline.habits.v1 | the reduced Habit Casino day index |
throughline.fb | your Firebase config (not a secret) |
To syntax-check a JS edit without a test suite:
awk '/^<script>$/{f=1;next} /^<\/script>$/{f=0} f' index.html > /tmp/tl.js && node --check /tmp/tl.js
Habit Casino's export carries a history array of completions
({ts, kind, name, category, level, reward}). Throughline reduces it to one row
per local day, then for each habit compares your average mood on days you did it
against days the tracker was running and you didn't — requiring at least three
days on each side before it will show a row. "Next day" runs the same comparison
against the following day's mood, which is the more interesting question.
It is association, not cause. A good day makes habits easier just as readily as habits make a good day, and the sample sizes are printed next to every row for exactly that reason.