TreeMap
무료 오픈소스 디스크 사용량 시각화 도구로, 사각형 트리맵·중복 파일 탐색·디스크 가득 예측·정리 시 파일을 휴지통으로만 이동한다
인턴십 준비 학생용 개인 네트워킹 트래커, 스프레드시트식 연락처 관리와 주간 통화 예약
A private networking tracker for students recruiting for internships. Keep your contacts in a spreadsheet-style grid and book calls on a weekly calendar.
생산성TypeScript · CSSGitHub @tfinn18온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Cover your network. A private networking tracker and call calendar for students recruiting for internships.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Coverage is a list for your recruiting season: every banker, alum, and recruiter you’re talking to, where each conversation stands, and the calls on your calendar.
Cover your network. A private networking tracker for college students recruiting for internships: a spreadsheet-style contact grid plus a weekly call calendar.
Live demo: https://coverage-green.vercel.app · sign up free, then click Try with example data.
| Import from LinkedIn, with a review step | Book calls on a weekly calendar |
|---|---|
Connections.csv from LinkedIn's data export (or any tracker CSV), or paste profile URLs. Every
import goes through an editable review table that skips duplicates.All free tiers; no paid APIs.
| Layer | Choice |
|---|---|
| Framework | Next.js 16 (App Router, Cache Components) · React 19 · TypeScript |
| Styling | Tailwind CSS v4 with design tokens · IBM Plex Sans/Mono + Source Serif 4 |
| Auth + database | Supabase: Auth (email, Google OAuth) and Postgres with Row Level Security |
| Calendar | FullCalendar v6 (MIT core: timeGrid + interaction) |
| PDF parsing | pdf.js (pdfjs-dist), lazy-loaded, runs in the browser |
| Testing | Vitest (unit + RLS isolation) · Playwright + axe-core (end-to-end + accessibility) |
| CI / hosting | GitHub Actions · Vercel Hobby |
flowchart LR
subgraph Browser
UI["React UI<br/>Contacts grid · Calendar · Import"]
PDF["pdf.js<br/>(profile PDFs never leave the browser)"]
SBC["Supabase JS client<br/>(publishable key only)"]
UI --> PDF
UI --> SBC
end
subgraph Vercel["Vercel (Next.js)"]
PX["proxy.ts<br/>refresh session cookie,<br/>guard /contacts /calls /account"]
RH["Route handlers<br/>/auth/callback · /auth/confirm · /auth/signout"]
RSC["Server Components<br/>(read the signed-in user)"]
end
subgraph Supabase
AUTH["Auth<br/>email + Google"]
PG[("Postgres<br/>profiles · contacts · calls<br/>Row Level Security")]
end
Google["Google OAuth"]
UI -- page requests --> PX
PX --> RSC
SBC -- "queries with the user's JWT" --> PG
SBC --> AUTH
RH --> AUTH
AUTH <--> Google
RSC --> PG
Most reads and writes go directly from the browser to Postgres with the user's session token. There is no custom API layer to secure: the database decides what each request may see.
| Table | Columns |
|---|---|
profiles | id → auth.users, full_name, created_at (created by a trigger on sign-up) |
contacts | id, user_id, company, name, title, stage (contacted | call_scheduled | called), follow_up_date, notes, linkedin_url, is_example, created_at, updated_at |
calls | id, user_id, contact_id → contacts, starts_at, duration_minutes (default 30) |
contact_notes | contact_id → contacts (one per contact), user_id, doc (JSON notes document), updated_at |
contacts also has an optional email. contacts.notes holds a plain-text copy of the notes document so the grid
preview, search, and CSV export never need to load every document.
Every foreign key to auth.users is on delete cascade, so deleting an account removes all of its data. SQL lives in
supabase/migrations.
select / insert / update / delete policies require
auth.uid() = user_id (or = id for profiles). Logged-out requests match no policy. The calls insert/update
policies (and the same policies on contact_notes) also check that the linked contact belongs to the same user, so
you can't attach a call or notes to someone else's contact. (0002_rls.sql)security definer function that can only delete the caller's own
auth.users row. (0003_functions.sql)tests/rls/isolation.test.ts signs in as two ordinary users and
checks that user B can't read, update, delete, or insert into user A's contacts, calls, notes, or profile, and that
logged-out requests see nothing.@supabase/ssr keeps the session in HTTP cookies; proxy.ts validates the JWT (getClaims) on each
request. Server code never trusts an unverified session.?next= values pass through safeNextPath(), which rejects absolute and protocol-relative URLs
(no open redirects). Log out is POST-only.=, +, -, or @ so a malicious name can't run a spreadsheet
formula..env.local is git-ignored; documents every variable.You need Node.js 24+ and a free Supabase project.
git clone https://github.com/tfinn18/coverage.git
cd coverage
npm install
cp .env.example .env.local # add your Supabase URL and publishable key
npm run dev # http://localhost:3000
supabase/migrations/ in order (0001 → 0005).http://localhost:3000/** to Redirect URLs.https://<project-ref>.supabase.co/auth/v1/callback as an authorized redirect URI, and paste the client ID and
secret into Supabase → Authentication → Sign In / Providers → Google.| Command | What it checks |
|---|---|
npm run lint · npm run typecheck | ESLint and TypeScript |
npm test | 60 unit tests: CSV and PDF parsers (with fixture files), import mapping and duplicate detection, sort/filter, follow-up flags, scheduling helpers, Google Calendar links, the notes document (including converting the old layout), list editing, Gmail and mailto links, safe redirects |
npm run test:rls | 8 checks that one user can't touch another's rows, including notes (needs two test accounts in .env.local) |
npm run test:e2e | Playwright: sign in, add a contact, reload, see it on the calendar, delete it; write and reopen notes (Enter/Backspace lists, Copy, Open in Gmail, Escape), notes from the old layout open in the new one; the notes sheet at phone width; plus axe WCAG 2.1 AA scans of 8 pages and the open notes panel in light and dark mode. E2E_BASE_URL=https://coverage-green.vercel.app runs it against production |
GitHub Actions runs lint, typecheck, unit tests, and a production build on every push. The RLS and end-to-end suites need real credentials, so they run locally.
src/
app/ routes: landing, privacy, (auth) pages, (app) pages, auth route handlers
components/ ui primitives, contacts grid, import dialog, calls calendar
lib/ framework-free logic (unit-tested): contacts, csv, pdf, linkedin, import, calls
lib/supabase/ browser client, server client, session proxy
proxy.ts route guard (Next.js 16's name for middleware)
supabase/migrations/ schema, RLS policies, functions
tests/ unit tests, fixtures, RLS isolation test
e2e/ Playwright end-to-end and accessibility tests
A starting point for interviews and resume bullets. Edit it into your own words.
security definer SQL function instead, so no secret key exists in the codebase or on Vercel.CLAUDE.md records the
conventions it followed.Resume bullets (examples):
Supabase's free plan only allows editing the auth email templates once you configure a custom SMTP server, so Coverage uses Supabase's default confirmation and password-reset emails.
Those default emails use the PKCE flow: the link carries a one-time code that can only be exchanged
for a session in the same browser that requested the email (a secret "code verifier" is stored there).
What that means in practice:
How to remove the limitation: add custom SMTP in Supabase (Authentication → Emails → SMTP Settings;
a free tier from a provider like Resend or Brevo works), then change the two templates so their links
use a token_hash, which /auth/confirm already supports on any device:
<!-- Confirm signup -->
<a href="{{ .RedirectTo }}?token_hash={{ .TokenHash }}&type=email">Confirm your email</a>
<!-- Reset password -->
<a href="{{ .RedirectTo }}&token_hash={{ .TokenHash }}&type=recovery">Reset your password</a>
(The reset link uses & because its redirect URL already contains ?next=/reset-password.)
.env.example