TreeMap
A free, open-source disk-space visualizer. Squarified treemap, duplicate hunting, disk-full forecasting, and cleanup that only ever moves files to the Trash.
Inbox triage that keeps every team's email organised.
Translation pending
ProductivityJavaScript · TypeScriptGitHub @max-scrypticOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
A tool to help automate email organisation.
Translation pending
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
Built with Next.js App Router, Tailwind CSS v4, TypeScript, and shadcn/ui,
starting from an in-house SaaS template.
The starting point focuses on product UI and conventions rather than a
marketing site. Auth and billing arrive UI-only, so this project can pick its
providers without unpicking backend assumptions first. The rest of this file
documents what came with the template and how to wire each piece up.Read the full README ↗Built with Next.js App Router, Tailwind CSS v4, TypeScript, and shadcn/ui, starting from an in-house SaaS template.
The starting point focuses on product UI and conventions rather than a marketing site. Auth and billing arrive UI-only, so this project can pick its providers without unpicking backend assumptions first. The rest of this file documents what came with the template and how to wire each piece up.
AGENTS.md holds the repo conventions and is read by both Codex and Claude
Code (CLAUDE.md just imports it). scripts/codex-setup.sh is the setup
script for the Codex environment: it runs npm ci and seeds .env.local.
One convention there is a hard rule rather than a preference: no em dashes
anywhere, in copy, comments, docs, or model prompts. npm run lint runs
ESLint and then scripts/check-em-dashes.mjs over every tracked file, and a
PreToolUse hook blocks an agent from writing one in the first place. See
"No Em Dashes" in AGENTS.md.
Impeccable is installed at project scope, so cloning the repo is enough to get it; there is nothing to install for Claude Code. It adds design vocabulary and a detector that checks UI changes against this project's own design system:
DESIGN.md holds the visual system: palette, type ramp, radius scale,
elevation rules, component specs, and the named rules worth citing.PRODUCT.md holds durable product truth: users, purpose, positioning,
workflow vocabulary, and what is deliberately undecided..impeccable/design.json is the machine-readable sidecar the detector and the
live panel read. Regenerated with DESIGN.md, never edited alone.npx impeccable detect src/ runs the detector by hand; it exits non-zero on
findings. The same detector runs automatically after UI edits in Claude Code.Contributors using Cursor, Codex, or Copilot run npx impeccable install once
to wire the same skill into their own tool.
src/app/globals.csssrc/components/ui/plans route with selectable plan cards, upgrade/downgrade summary, and a
confirmed save flow through a swappable billing adapterDataTable with sorting, filtering, pagination, selection,
column visibility, and row actionsTemplateFormField wrapper for react-hook-form + zod validation/kitchen-sink route for visual QAInstall dependencies and run the development server:
npm install
npm run dev
No environment file is needed to start: billing falls back to a mock provider
and route guarding is disabled until Supabase is configured.
Copy .env.example to .env.local when wiring a real provider.
Open http://localhost:3000 with your browser to see the result.
Useful routes:
/ dashboard/plans/settings/auth/sign-in/kitchen-sinkSign-in is Google SSO through Supabase Auth, and it is the only way in. The
template's password, reset, and verification screens have been removed;
next.config.ts redirects their old paths to /auth/sign-in.
The same consent grants mailbox access (gmail.modify: read, draft, send), so
one Google authorization covers both identifying the user and doing the work
the product exists to do.
| Path | Role |
|---|---|
src/lib/auth/scopes.ts | Which Google scopes the consent screen asks for. |
src/lib/auth/actions.ts | signInWithGoogle and signOut server actions. |
src/lib/auth/session.ts | The Data Access Layer. getSessionUser() / requireUser(). |
src/app/(auth)/auth/callback/route.ts | OAuth callback; captures Google refresh tokens. |
src/proxy.ts | Session refresh plus an optimistic signed-out redirect. |
src/lib/supabase/ | Browser, server, service-role, and Proxy clients. |
src/lib/google/token-store.ts | Stores and refreshes Google mailbox tokens. |
supabase/migrations/ | public.users, public.google_credentials, public.workflows. |
Authorization is enforced in session.ts, not in Proxy. Proxy runs on
prefetches and only reads the session cookie, so it is a UX shortcut; every
server component or action that touches user data calls requireUser().
Setting up the Supabase project, the Google Cloud OAuth client, and the
verification Google requires for Gmail scopes is documented in
docs/google-sso-setup.md.
Nothing applies supabase/migrations/ for you: not the build, not the deploy.
A change that adds a column ships to production before the column exists there,
and the page that selects it throws until someone runs the migration by hand.
So when a pull request touches supabase/migrations/, apply it to the Supabase
project as part of shipping it.
With no .env.local the app still boots: Proxy stops guarding routes and
/auth/sign-in renders a configuration notice instead of a broken button.
Billing has two implementations behind one interface. Which one runs is decided
by NEXT_PUBLIC_BILLING_PROVIDER, so a project turns real billing on by adding
environment variables rather than by rewriting UI.
mock (default): no keys, no network. /plans and /settings show their
full loading, success, and error states out of the box.stripe: Checkout, in-place subscription updates, the customer portal, and a
signature-verified webhook.| Path | Role |
|---|---|
src/lib/billing/types.ts | Provider-neutral contracts. The only billing types the UI sees. |
src/lib/billing/config.ts | Client-safe config: which provider, which routes, which return paths. |
src/lib/billing/billing-adapter.ts | Picks the provider. What components import. |
src/lib/billing/providers/ | mock-provider.ts and stripe-provider.ts. |
src/lib/billing/server.ts | getSubscription() for server components. |
src/lib/billing/customer.ts | The auth ↔ billing seam. Wire your auth provider here. |
src/lib/billing/store.ts | The persistence seam. Replace the in-memory default. |
src/lib/billing/stripe/ | Server-only Stripe code: env, client, service, mapping. |
src/app/api/billing/ | checkout, portal, and webhook route handlers. |
Nothing under src/lib/billing/stripe/ or store.ts/customer.ts can be
imported from a client component: they start with import "server-only", which
turns that mistake into a build error rather than a leaked secret key.
Create the products and recurring prices in the Stripe dashboard, one per
self-serve plan in plans in src/lib/template-data.ts.
cp .env.example .env.local and fill in STRIPE_SECRET_KEY, the
STRIPE_PRICE_* ids, and NEXT_PUBLIC_APP_URL.
Set NEXT_PUBLIC_BILLING_PROVIDER=stripe.
Forward webhooks locally and copy the printed signing secret into
STRIPE_WEBHOOK_SECRET:
stripe listen --forward-to localhost:3000/api/billing/webhook
In production, create the endpoint in the dashboard pointing at
https://your-domain/api/billing/webhook and subscribe it to
checkout.session.completed, customer.subscription.created,
customer.subscription.updated, customer.subscription.deleted, and
invoice.payment_failed.
Test cards live at docs.stripe.com/testing;
4242 4242 4242 4242 with any future expiry completes a checkout.
A customer with no subscription goes to hosted Checkout. A customer who already
subscribes has their existing subscription item swapped in place, so an upgrade
never bounces them through a second checkout. Both directions settle on the next
invoice: an upgrade adds a charge for the remainder of the period, a downgrade
adds a credit. To hold a downgrade until the period boundary instead, replace the
proration_behavior call in src/lib/billing/stripe/service.ts with a
subscription schedule.
The browser never learns which path it took: the route returns either a URL to
redirect to or an applied result, and BillingResult.outcome tells the UI
whether the plan really changed.
Identity. resolveBillingIdentity() in src/lib/billing/customer.ts returns
the template user. Point it at your auth provider. With Supabase:
const supabase = await createServerClient();
const { data } = await supabase.auth.getUser();
if (!data.user) throw new UnauthenticatedBillingError();
return { userId: data.user.id, email: data.user.email!, name: data.user.user_metadata.name };
Persistence. billingStore in src/lib/billing/store.ts is an in-memory map.
It is enough to click through the whole flow locally, but it resets on restart
and is not shared between serverless instances. Stripe stays the source of truth
either way (a cold store falls back to looking the customer up in Stripe), but
that costs an API call on every render, so give it a real table before launch:
create table billing_customers (
user_id uuid primary key references auth.users on delete cascade,
stripe_customer_id text unique not null,
created_at timestamptz not null default now()
);
create table billing_subscriptions (
user_id uuid primary key references auth.users on delete cascade,
stripe_customer_id text not null,
stripe_subscription_id text unique not null,
plan_id text not null,
status text not null,
cancel_at_period_end boolean not null default false,
current_period_end timestamptz,
updated_at timestamptz not null default now()
);
alter table billing_customers enable row level security;
alter table billing_subscriptions enable row level security;
create policy "own subscription" on billing_subscriptions
for select using (auth.uid() = user_id);
Then implement BillingStore against those tables. Write with the service-role
key from the webhook (it runs without a user session) and keep row-level
security on so the browser can only read its own row. Never let the client write
these tables; the webhook is the only writer.
plans in src/lib/template-data.ts.priceEnvNames in src/lib/billing/stripe/env.ts
and to .env.example.Sales-led plans (contactSales: true) need no price: the checkout route rejects
them, and requestSalesContact is where the CRM or scheduling handoff goes.
NEXT_PUBLIC_APP_URL to the deployed origin, or Checkout will send
customers back to localhost.invoice.payment_failed in the webhook is
where dunning starts.src/components/ui as vendored shadcn source.src/components.AlertDialog or useConfirmDialog for destructive actions.src/lib/billing/<provider>/.import "server-only".npm run dev
npm run lint
npm run build
The product name lives in one place, appConfig in
src/lib/template-data.ts, and every surface reads it from there: page
metadata, the sidebar, auth screens, empty and error copy, and the Stripe
appInfo label. Rename the product by editing appConfig.
The logo is an orange tile with a white envelope, drawn three times from the same geometry:
src/components/brand-logo.tsx holds BrandMark and BrandLockup for
product UI. Both paint from the --brand / --brand-foreground tokens, so
they follow light and dark mode.src/app/icon.svg is the browser tab icon, picked up by the Next.js icon
file convention.public/logo.svg is the same artwork as a static asset for anything outside
React (emails, docs, an OG image).Editing the mark means editing all three: the SVG files carry literal hex because they render outside the token system.
--brand is the single source for the orange. --primary, --ring, and
--sidebar-primary all resolve to it, so buttons, links, focus rings, badges,
switches, and the chart placeholder carry the logo colour without any component
naming it directly. Text on top of the orange uses --primary-foreground, a
warm near-black: white on this orange is 2.1:1, dark text is 8.4:1. The
envelope inside the mark is the exception and stays white
(--brand-foreground), where it is artwork rather than text.
QA both themes at /kitchen-sink, where the Primitives tab renders the mark at
three sizes next to the lockup.