Repository navigation
feat: founding members (numbers, Founders template, wall) - #32
Merged
Merged
Conversation
Everyone on the waitlist when billy launches becomes a founding member: a number by signup order, a personal link that unlocks a founders-only template, and a spot on a public wall if they asked for one. There is still no server, account or database. - invoice-core/founders.ts: codes look like v1.<n>.<signature>, signed offline with an Ed25519 key and checked in the browser against the public key. Nobody can mint one or move it to another number. - Editor: opening /editor#founder=<code> verifies it, remembers it in this browser, strips it from the URL and says hello. A bad link gets a clear error instead of silently doing nothing. - Founders template: billy's sky-blue gradient masthead, white type and the landing page's hand-drawn underline. Locked in the template rail for everyone else and hidden from the command palette. Exports to PDF. - /founders: the founding members wall, from content/founders.json (name, number, avatar only; never an email), with Gravatar photos or initials, and a "you're #n" line for founders. Linked in the footer. - scripts/founders: keygen (once; private key lives in ~/.billy, never in the repo) and launch (reads the Loops export, numbers everyone, signs links, writes founderNumber/founderLink back to Loops, builds the wall, keeps a private record in ~/.billy).
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
✅ Deploy Preview for trybilly ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
🚀 Deploying Preview to Cloudflare 🚀Preview Deployments by commit
|
✅ Deploy Preview for tryybilly ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
vedantlavale
added a commit
that referenced
this pull request
Sep 27, 2026
A "Get your founding member number" form under the heading. It posts straight to a Loops form (NEXT_PUBLIC_LOOPS_FORM_ID), so there is still no server or database. Once an email is typed, an optional name field appears; giving a name opts into the founding members wall (userGroup "founding-wall", otherwise "founding"). Numbers and unlock links go out on launch day (launch-alpha, PR #32).
A single reference doc so the launch-day process, file map, and email template survive even if context gets lost. Covers what's done, what's left, the exact commands to run on launch day, and the recovery notes for the signing key. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Owner
Author
|
Added |
"Founding member" read as too much. User-facing copy now says "early bird" throughout (template name, unlock toasts, the wall, docs/founders-launch-plan.md and its email template). Internal plumbing is untouched on purpose: Loops properties (founderNumber/ founderLink), the localStorage key, the #founder= URL param, file and route names, and the signing message all stay as they are — renaming those would mean redoing Loops setup and invalidating nothing-yet-issued links for no user-visible benefit. Also swap Gravatar for blobatar.dev for wall avatars: a deterministic generated avatar per email, no key, no account, no dependency on whether someone happens to have a Gravatar set up. Everyone who opts into the wall now gets a consistent on-brand avatar. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
vedantlavale
added a commit
that referenced
this pull request
Sep 27, 2026
* feat: replace the site with a "Launching soon" page The redesigned billy is launching soon, so main now serves a single page until then: the billy mark, a "Launching soon" heading with a looping blue doodle video showing through the letters (react-bits MaskedHeading), and one line of copy. - Remove every other route (editor, dashboard, login, signup, auth, api) and the Supabase proxy, plus the components and libs only they used. - Redirect the retired routes to / so old links still land somewhere. - New favicon and apple icon from the new brand; Geist for type. - Vendored MaskedHeading: mark it as a client component, set its settings ref in a layout effect instead of during render, and fix the word spacer, which Tailwind v4 rendered as the literal text "\00a0" and pushed the heading onto two misaligned lines. * feat(coming-soon): turn the page into a scene, not a plain heading The first version was a lone masked heading on white. Now the page reads as the new billy: - Dotted sketch-paper surface with soft blue glows instead of flat white. - The new landing page's doodles scattered round the edges. They draw themselves in on load, float, and drift with the pointer. - A bolder heading (800 weight, larger scale), a hand-drawn squiggle that draws in under "soon", and a "Something new is on the way" chip with a pulsing dot. - The doodle face peeking at the heading with a "psst!" bubble. - A blurred sneak peek of the new editor rising from the bottom edge. - A richer fill loop: deeper blues, a light sweep, bolder doodles. - On phones the heading wraps to two lines at a larger size. All motion is off under prefers-reduced-motion. * feat(coming-soon): declutter, letter-by-letter heading, fanned sneak peek - Drop the "Something new is on the way" chip, the dotted background and the scattered doodles. The page is white with a soft blue glow, the heading, its hand-drawn underline and one line of copy. - "Launching soon" now rises one letter at a time. MaskedHeading gains a split="chars" option that clips and animates each letter on its own. - The blurred sneak peek is the centerpiece: the new editor with two more blurred screens fanned out behind it, rising from the bottom. * feat(coming-soon): founding member signup A "Get your founding member number" form under the heading. It posts straight to a Loops form (NEXT_PUBLIC_LOOPS_FORM_ID), so there is still no server or database. Once an email is typed, an optional name field appears; giving a name opts into the founding members wall (userGroup "founding-wall", otherwise "founding"). Numbers and unlock links go out on launch day (launch-alpha, PR #32). * fix(coming-soon): wire up the real Loops form ID Default NEXT_PUBLIC_LOOPS_FORM_ID to the form the user created (cmujkeahx0k1w0j4adtzmompe), still overridable by the env var if the form is ever recreated. Also send the email under both "email" and "newsletter-form-input" (the field name Loops' own embed HTML uses), since only their embed's generated markup confirms which key the endpoint reads. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(coming-soon): strip the doodles and dot texture from the fill loop The letter-fill video had drifting doodle icons plus a dot halftone texture behind them — busy at heading size, read as clutter. Now just the billy-blue gradient with one soft light sweep. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * rename: founding member -> early bird; softer waitlist copy Match the launch-alpha rename. Also swap the form's headline from "Get your founding member number" to "Join the waitlist for a surprise at launch" -- less spoiler, more hook, consistent with not disclosing what's actually unlocked before launch. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: add the missing Cloudflare Worker config for this branch Cloudflare's Production build was pointed at this branch with nothing telling it what to deploy: no wrangler.jsonc existed here at all (only on launch-alpha), and next.config.ts wasn't even set to static export yet -- it still had redirects()/rewrites(), which Next.js refuses to build alongside output: "export". - next.config.ts: output: "export", drop redirects()/rewrites(). - public/_redirects: the retired-route redirects and the PostHog proxy, now as Cloudflare's own static-Worker redirect rules instead of server-side Next.js config. - public/_headers: the same 4 security headers launch-alpha sets. - wrangler.jsonc: same shape as launch-alpha's, assets directory ./out. - instrumentation-client.ts: api_host reads NEXT_PUBLIC_POSTHOG_HOST directly (falls back to the real ingestion host, not the now-removed proxy path) instead of the dead "/api/metrics" rewrite target. Verified locally: bun run build produces out/ with _redirects and _headers copied through, no errors. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
vedantlavale
added a commit
that referenced
this pull request
Oct 14, 2026
* remove waitlist
* feat: add new invoice templates for clean, corporate blue, minimal teal, and modern green styles
- Introduced CleanPage component for a clean invoice layout.
- Added CorporateBluePage component with a corporate blue theme.
- Created MinimalTealPage component featuring a minimalistic teal design.
- Developed ModernGreenPage component with a modern green aesthetic.
fix: update plans and access control for templates
- Updated PAID_TEMPLATES and FREE_TEMPLATES to include new templates.
- Enhanced isTemplateAccessible and canCreateInvoice functions to check paywall status.
feat: implement rate limiting functionality
- Added rate limit checks to prevent excessive requests per user.
- Implemented periodic cleanup of stale rate limit entries.
feat: integrate PostHog for analytics tracking
- Created posthog-client module for user identification and event tracking.
fix: enhance security headers in proxy responses
- Added security headers to responses in the proxy function.
chore: database schema updates for security and analytics
- Added updated_at columns and triggers to profiles and invoices tables.
- Introduced analytics tracking columns to profiles for signup source and first invoice date.
- Aligned subscriptions table with Dodo Payments identifiers.
* fix: add user_id filter to invoice list query for performance
The GET /api/invoices endpoint was fetching all invoices from all users
instead of filtering by the current user, causing the dashboard to load
1.2 MB of data and take 34+ seconds. Adding the missing .eq("user_id", user.id)
filter reduces this to kilobytes and sub-second response times.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
* feat: add new invoice templates and storage for assets
- Introduced two new invoice templates: Mono Classic and Mono Elegant.
- Updated the PDF generation logic to support the new templates.
- Added functionality to upload and manage invoice logo/stamp images in a dedicated storage bucket.
- Implemented a migration script to transfer existing base64 images to the new storage system.
- Enhanced the theme configuration to include styles for the new templates.
- Updated the plans to include the new templates as free options.
- Added security headers to the proxy response.
* add loader
* refactor: update landing page styles and layout for improved aesthetics and readability; add MorphingSquare component for enhanced UI interactions
* refactor: remove unused components and implement InvoiceMock
- Deleted DotFlowCta component and its associated logic.
- Added InvoiceMock component to display a sample invoice layout.
- Updated LandingCta component to remove InteractiveHoverButton and simplify the button structure.
- Removed FlickeringGrid and InteractiveGridPattern components to streamline UI.
- Deleted InteractiveHoverButton component as it was no longer in use.
- Introduced DodoPayments integration in a new lib/dodo.ts file.
- Updated package.json to include dodopayments dependency.
* feat: update styling and structure of invoice editor components
- Changed selection background color in globals.css for better visibility.
- Refactored invoice field styles to improve hover effects and transitions.
- Introduced new app input and button styles for a consistent UI across components.
- Replaced anchor tags with Link components for navigation in the footer.
- Enhanced OptionsPanel with accessibility attributes and improved styling.
- Added a new CustomizePanel component for invoice customization options.
- Removed deprecated currency-select and template-selector components.
- Updated toolbar to include a button for toggling the CustomizePanel.
- Cleaned up unused InvoiceSettingsPanel component and related imports.
- Adjusted editable number and text components for consistent styling and focus effects.
* Redesign the invoice editor: single page + inspector, aligned to DESIGN.md (#7)
* chore: restructure into turborepo monorepo (apps/web + packages)
Moves the Next.js app into apps/web and extracts shared code into
packages/invoice-core, packages/design and packages/typescript-config.
Adds root DESIGN.md, docs, CI workflows and migration 00009.
* feat(editor): redesign invoice editor around a single page + inspector
The editor rendered the same InvoicePage twice (editable canvas + identical
read-only "PDF Preview"), hid all settings behind a Customize drawer, and
drifted from root DESIGN.md (magenta selection, slate/indigo accents,
all-caps tracked labels, teal/rose dialog).
Layout
- One centred A4 page on a muted desk; the duplicate preview pane and the
resizable split are gone.
- Persistent left inspector: amount-due summary (total, due-in-N-days,
client, item count), visual template gallery, currency, quick-setup
presets with active state, field switches, and a tax section.
- Below lg the inspector becomes a slide-over drawer (Esc/backdrop close).
UX
- Edit / Preview segmented switch (Cmd/Ctrl+E). Preview renders the page
read-only, which is exactly what the PDF exporter rasterises.
- Floating zoom bar: -/+ steps, click % for 100%, Fit to width.
- Cmd/Ctrl+P downloads the real PDF instead of the browser print dialog;
button shows a busy state; success/failure via toasts instead of alert().
- "New" uses a confirm dialog (explains what is kept) instead of
window.confirm.
- Autosave status in the top bar ("Saved on this device").
- Editable fields show a faint dotted baseline while hovering the sheet and
a blue wash on hover; first-run "click any text to edit" tip (dismissible,
remembered). Edit-only affordances never render in preview/PDF.
- GST: CGST+SGST vs IGST is one mutually exclusive choice (matching how
totals already compute) instead of three independent toggles.
- Full-width dashed "Add line item" row.
Design system
- New .ed-* chrome layer in globals.css (@layer components): white
surfaces, #37352f text, #E5E7EB hairlines, #F3F4F6 wash, #0070D7 as the
only chroma, 7px/8px radii, Inter, natural-case labels.
- .app-option / .app-kbd selection switched from magenta to blue; field
focus ring unified on #0070D7; confirm dialog restyled.
Also fixes the dev-overlay hydration warning caused by the pre-hydration
data-js script on <html> (suppressHydrationWarning on that element only).
* fix(editor): reserve the page's full border-box height on the desk
ResizeObserver contentRect excludes the sheet's padding and border, so the
scaled wrapper was ~98px shorter than the page and the bottom of the invoice
spilled past the reserved space. Measure offsetHeight (unscaled border box)
instead.
* Redesign the landing page: bright Tally style, no gradient panels (#8)
* feat(landing): bright Tally-style redesign, drop the navy gradient panels
The landing page was built from stacked navy gradient panels whose
top-left stop (rgba(164,178,235,.5)) washed the navy toward lavender, plus
a glass orb, dark gradient bento tiles, two-tone grey headlines and
ALL-CAPS eyebrows. Rebuilt around root DESIGN.md ("Tally Minimal Forms").
- White canvas, #37352f ink everywhere (hierarchy by size/weight), Inter
700 headlines, #0070D7 only on CTAs and emphasis marks, #E5E7EB
hairlines, 7/8/12px radii, natural-case section kickers.
- Hero: centered promise with a hand-drawn blue underline that draws
itself, sparkle/coin doodles, 40/48px buttons (not pills), and the live
typing invoice as a large browser-framed product shot with hand-drawn
annotations ("Click a line and type", "Totals update themselves").
- Trades as muted chips (replaces the marquee).
- How it works: three numbered steps, each with a small proof: a focused
field with caret, the six real template palettes from invoice-core, and a
ready PDF file.
- Privacy: argument + big-numeral facts in hairline rows (replaces the dark
stats panel).
- Built for: four open columns with sketch icons and blue checks, no cards.
- FAQ: sticky heading column + hairline accordion; adds a GST answer.
- Closing: muted band with paper-plane doodle (replaces the dark CTA panel).
- 404 restyled to match.
Reuses the parked components/landing/doodles.tsx (paths opt into the
draw-on animation via pathLength). Removes the marquee, orb, tilt parallax
and dark-button tone. Reduced motion disables every draw/reveal animation.
* feat(landing): animated SVG scenes for "How it works"
Replace the three static illustration wells with looping SVG scenes built
on motion (already a dependency):
1. Click anything and type: a cursor clicks a line, the field takes a blue
focus ring, text types in behind a moving caret, then the amount lands.
2. Pick a look: a mini invoice recolours while a selection ring steps
across four real template palettes from invoice-core (Clean, whose
primary is "transparent", shows its accent).
3. Download the PDF: the sheet folds into a PDF file, the download arrow
draws into a tray, and a check confirms it is ready.
Each scene is one keyframe timeline on a shared clock, so its parts stay
in lock-step with no JS scheduler. Scenes animate only while on screen,
and rest on a static finished frame off screen and under
prefers-reduced-motion.
The Private-by-design stamp now draws when it scrolls into view
(DrawOnView). Reveal's 2.2s backstop had it finish before anyone got
there. Also fixes the pen-draw CSS: the build lowercased
path[pathLength] into a selector that never matches SVG, so drawable
paths are marked with data-draw instead.
Closes #9
* feat(landing): polish pass: measured annotations, richer scenes, micro-interactions
Hero annotations
- The arrows were placed with guessed offsets and hidden below 1320px, so
most laptops saw none and wide screens saw arrows pointing near, not at,
their targets. HeroAnnotations now measures the typed row and the total
and pins a numbered marker on the frame edge level with each. Wide
screens get a label with a hand-drawn arrow (drawn on in view) landing
on the pin. Narrower screens get a numbered legend under the frame. The
row pin pulses while typing.
Step scenes
- Pick a look: a cursor now does everything the copy promises. It picks a
template (the header band sweeps in), uploads a logo (it pops in) and
switches currency (the total rolls from $2,300 to ₹2,300).
- Download the PDF: a real flow. The cursor clicks Download, a progress
bar fills, the invoice flies into a Downloads panel and lands as a file
with a check, a "1" badge and "Saved to your device".
- Fix: several timelines ended before 1.0. Motion ends a repeat at the last
keyframe, so those elements looped early and drifted out of sync. All
timelines now span the loop, with a dev-mode warning if one doesn't.
New scene
- Private by design: data orbits inside a browser tab ("this tab only"),
and a request heading for a server bounces off a barrier. It replaces
the static stamp, which retires DrawOnView.
Micro-interactions (all off under prefers-reduced-motion)
- Hero sparkles twinkle and the coin flips. The closing paper plane
glides with a moving trail.
- Hover: chips lift, step numbers fill, use-case icons wiggle, and
buttons get a pressed state.
Closes #15
* Editor: animated beUI controls + fix summary hydration mismatch (#12)
* fix(editor): avoid hydration mismatch in the amount-due summary
/editor is statically prerendered, but the summary formatted the due date
with the visitor's locale and computed "Due in N days" from today during
render, so server and client text differed and React threw. Render those
parts only after hydration (useSyncExternalStore) and show the ISO date
until then.
Closes #11
* feat(editor): animated switches, tax radio group and Edit/Preview tabs
Replace three hand-rolled editor controls with motion components adapted
from beUI (MIT, https://beui.dev). The source is copied into
components/motion/ with the original license, restyled to root DESIGN.md
(the shadcn primary token is black here, not blue):
- Switch: spring-driven thumb for every inspector field toggle. SwitchRow is
now a <label> for the switch, so the whole row still toggles it, and
descriptions are wired up with aria-describedby.
- RadioGroup: gliding indicator dot for Single tax / CGST+SGST / IGST, with
a description per option and arrow-key navigation (roving tabindex).
- Tabs (segment): the Edit/Preview toggle becomes a gliding white pill with
a blue clip-masked active label. aria-label/title passthrough keeps the
icon-only triggers labelled on small screens.
The old .ed-segment and .ed-switch CSS is removed. Every component respects
prefers-reduced-motion. No new dependencies: motion, clsx, tailwind-merge
and lucide-react were already installed.
Sidebar/File tree were considered and left out: Billy keeps a single local
draft, so there is nothing for them to list yet.
Closes #10
* Editor: command palette, undo/redo, payment terms, invoice numbering (#14)
* feat(editor): command palette, undo/redo, payment terms, invoice numbering
Make the editor faster to drive without adding visible chrome.
- Command palette (Cmd/Ctrl+K): a searchable list of every editor action.
Add a line item (then start typing its description), download, preview,
new invoice, undo/redo, payment terms, field toggles, tax type, template
and currency. Rows show their shortcut and current value, so the palette
doubles as the shortcut reference. WAI-ARIA combobox, keyboard driven.
Field toggles keep it open so several can be flipped in a row.
- Undo/redo (Cmd+Z, Shift+Cmd+Z, Ctrl+Y): bounded history (100 steps)
wrapped around the invoice reducer. Each committed field edit is one
step. The initial load from localStorage resets history, and the
shortcuts stand aside while a text field is focused so native text undo
still works.
- Payment terms: On receipt / Net 7 / Net 15 / Net 30 quick picks in the
inspector set the due date from the issue date, and the current term is
highlighted.
- Invoice numbering: New invoice bumps the number and keeps prefix and
zero padding (INV-1024 -> INV-1025, 2026/009 -> 2026/010).
- The first-run tip mentions Cmd+K.
No new dependencies.
Closes #13
* feat(editor): make commands, undo/redo and payment terms visible
Reviewer feedback: the palette, undo/redo and payment terms were
keyboard-only or below the fold, so the editor looked unchanged.
- Toolbar: a "Commands" launcher with its Cmd+K hint (icon-only below
xl), plus undo/redo buttons that dim when there's nothing to undo or redo.
All three carry aria-keyshortcuts.
- Payment terms move up to sit directly under the amount-due summary, next
to the due date they change.
Refs #13
* style(editor): keep payment-term chips on one line
* Use Rune Icons everywhere + fix intermittent PDF export failure (#20)
* feat: use Rune Icons for every UI icon
Replace lucide-react, @hugeicons and hand-drawn inline icons with Rune Icons
(Apache 2.0, https://github.com/Nexvyn/runeicons, outline style).
- Rune isn't on npm yet, so the 28 glyphs Billy uses are vendored in
components/icons/rune.tsx with the license in components/icons/LICENSE-RUNE.
Each SVG was checked to contain only plain shapes (no scripts, links or
styles). Strokes use currentColor, and a `size` prop, overridable
strokeWidth and optional `title` are provided (aria-hidden by default).
- Swapped: the editor toolbar, canvas, inspector, command palette, line
items, logo and stamp upload, the beUI tabs' scroll chevrons, the select,
toasts, the landing CTA arrow and the use-case icons.
- Close substitutes where Rune has no exact glyph: rotate-ccw/cw for
undo/redo, refresh-cw as the spinner, cloud for "saved", file-down for
Download PDF, and shield-check for the stamp placeholder.
- Toasts: badge colours move from emerald/rose/teal to DESIGN.md (blue,
#D92D20 for errors), and the dismiss button gets an accessible name.
- Remove lucide-react, @hugeicons/core-free-icons and @hugeicons/react.
Landing doodles (sparkles, coin, arrows, plane) are illustrations and stay.
Refs #18
* fix(pdf): render the export root synchronously
React 19's createRoot().render() is concurrent, and a single animation frame
wasn't always enough for it to commit on a cold or slow page. The export then
threw "Invoice failed to render for export", which headless testing hit on
the first attempt after load. Render with flushSync so the invoice DOM exists
before measuring, then wait one frame for pagination's layout effects.
Refs #19
* Land the landing, editor and brand work on launch-alpha (#22, #24, #26) (#27)
#22, #24 and #26 were squash-merged into their own base branches
(feat/rune-icons, feat/landing-bento, feat/editor-polish) rather than into
launch-alpha, so only #20 landed here. Nothing was lost: the combined work
sits on feat/editor-polish.
This commit carries the exact tree of feat/editor-polish, verified
identical to feat/brand, so launch-alpha ends up with what the four PRs
reviewed together:
- #22 Landing: bento sections, scene cards, feature tiles, footer band,
trust chips with tooltips
- #24 Editor: discount-before-tax fix with tests, template-driven colours,
no placeholders in the preview/PDF, four new templates
- #26 Brand: mark, wordmark, favicon, OG image; every template fits one page
* build: tell Vercel and Netlify how to build the monorepo
Both hosts build from the repo root, where package.json has no "next"
dependency, so Vercel failed with "No Next.js version detected" and never
got as far as the app in apps/web.
Billy is a static export, so neither host needs a framework preset: build
from the root with turbo and publish apps/web/out.
- vercel.json: framework null, bun install, bun run build, output
apps/web/out, and the security headers from apps/web/public/_headers
(which only Cloudflare Pages reads). github.silent stops the bot
commenting on every push.
- netlify.toml: the same build and publish settings, pinned to the repo's
Bun version, with the same headers.
Verified with a clean root build: `bun install && bun run build` emits
apps/web/out with index.html, editor.html, 404.html, the brand assets and
the icons.
Refs #17
* fix(build): unbreak the build on a clean install
A fresh clone of launch-alpha fails `bun run build`: next's type check
reaches apps/web/scripts/migrate-images-to-storage.ts, which imports
@supabase/supabase-js — a package that is in no package.json and appears
zero times in bun.lock. Local checkouts only built because they still had
it in node_modules from before the backend was dropped, so the failure
only showed up on Vercel.
The script is one of the dead Supabase leftovers AGENTS.md lists, so
exclude that one file from the app's type check rather than resurrect the
dependency. download-fonts.ts stays type-checked.
Also set cleanUrls, since /editor exports as editor.html rather than
editor/index.html and vercel.json now serves apps/web/out as plain static
files.
Refs #17
* feat(landing): drop the trust pills, rebuild the trades strip
Two changes to the same two files, so they land together.
The "Free during alpha / No account / Nothing uploaded" pills under the
final CTA repeated the hero chip and three of the six FAQ answers, and the
tooltips hid the reasons behind a hover nobody has on touch. The section
closes on the two buttons now.
The trades strip was a centred cloud of grey pills that read as filler. It
is now a band: a blue wash with hairline rules top and bottom, the heading
and a line of copy on the left, and the ten trades as a numbered index on
the right. The band also gives the page its first break from white, which
is what made the old strip disappear. Two columns of five on desktop, one
column below 520px, and the heading stacks above the index below 860px.
* feat(brand): a glass tile for the "b"
The flat blue tile with a stroked stem-and-ring is now a glass tile: a
vertical gradient through #0070D7, a sheen along the top edge and a rim
light that is bright at the top and fades out. The "b" itself is solid —
a rounded stem and a filled bowl — so it still reads at 16px, and the
tile stays blue rather than near-black so it survives a dark tab strip.
Chroma stays blue-only per DESIGN.md.
In the app the gradients are CSS (.brandmark) rather than SVG: the mark
renders in the nav, the footer, the editor top bar and the 404, and
duplicated SVG gradient ids break whichever instance loses.
Standalone files keep their own gradients: app/icon.svg, brand/mark.svg
and brand/logo.svg. app/apple-icon.png is redrawn at 180x180, full-bleed
and opaque, because iOS masks the corners itself and fills transparent
ones with black. brand/og.png is regenerated with the new mark.
* feat(design): Geist for the interface
Geist replaces Inter on the landing page, the editor chrome and the
wordmark. It is loaded through next/font/google alongside Geist Mono,
which the editor already used.
Only interface declarations move. Invoice templates each name their own
family and resolve it through --invoice-font, and the Clean template
reads var(--font-sans), so none of the template variables are touched:
the document renders glyph-for-glyph as before and no page break moves.
Verified by diffing the editor canvas before and after.
DESIGN.md is the spec of record and said Inter, so it moves with the
code, with a note that templates are deliberately exempt. brand/logo.svg
and brand/og.png are regenerated in Geist.
* feat(landing): glyphs drawn for the capability tiles
The sixteen tiles used general-purpose Rune icons, and general-purpose
icons can only answer "GSTIN", "HSN / SAC codes" or "reverse charge" with
a near-miss. Sixteen near-misses in one grid is what made it read as
stock: a fingerprint for GSTIN, a laptop for "works offline", and a cloud
for autosave in a product whose whole claim is that nothing is uploaded.
components/icons/billy.tsx draws all sixteen on Rune's 24-unit grid with
the same caps and joins, so the two sets sit together: a barcode for HSN
codes, a bank front for bank details, the command loop for ⌘K, the
generic currency sign for "10 currencies", a struck-through cloud for
"works offline", an arrow into a tray for autosave. Rune stays the set
for the editor's UI verbs.
The tinted rounded square behind each icon is gone — sixteen identical
blue chips were most of the stock look — and the glyph goes from 18 to
21px now that it carries the tile on its own.
* build: wrangler config so Cloudflare can deploy the static export
Cloudflare's Workers Builds flow runs `npx wrangler deploy`, which needs a
config in the repo. There was none: the project predates the move off the
Pages flow.
Billy is a static export, so this is an assets-only Worker — no `main`,
no Worker code, Cloudflare serves apps/web/out directly.
html_handling is the part that matters: Next exports /editor as
editor.html rather than editor/index.html, so without
"auto-trailing-slash" the editor link 404s on a build that otherwise
looks healthy. out/_headers keeps working — Workers parses it the same
way Pages did.
Validated with `wrangler deploy --dry-run`: 132 files read from the
assets directory.
* docs: correct the deploy target
AGENTS.md said Cloudflare Pages. The project deploys through Workers
static assets now, configured by wrangler.jsonc at the root, from the
launch-alpha branch.
* feat(brand): Billy, a receipt with a face, replaces the "b" (#29)
The mark was a glass tile with a geometric "b". It is now Billy: a
slightly tilted torn receipt with two eyes and two total lines, on a
sky-blue tile with soft light streaks. It still reads as a face at 16px.
The tile gradient stays in CSS (.brandmark) so the mark can render
several times on a page without duplicate SVG gradient ids. The rim
light moves to ::after so it sits over the receipt.
The standalone assets match the new mark: app/icon.svg,
app/apple-icon.png (180x180, full-bleed, opaque), brand/mark.svg,
brand/logo.svg and brand/og.png.
Refs #28
* feat(editor): outline every editable field at rest
You could only tell a field was editable by hovering it: the dotted
baseline appeared on canvas hover and the tint on field hover, so a
first-time user reading the invoice saw a static document.
Every editable field now carries a visible hairline at rest, and the
computed cells — amount, subtotal, total — deliberately do not, so the
outline reads as "you can change this".
Two things make this safe for the PDF:
- The 1px border was already reserved as `solid transparent`, so giving
it a colour changes no geometry. Nothing moves, no page break shifts.
- `.invoice-field` is only emitted when the field is editable. Preview
and the export render through RenderModeProvider readOnly, which
returns plain markup without the class, so none of this can leak into
a PDF. The print rules already blank the border too.
The colour is mixed from currentColor rather than fixed: Studio,
Letterhead, Modern Green and Corporate Blue put white text on a coloured
band, where a grey or blue hairline would disappear. Their hover borders
are matched to white for the same reason.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(editor): split the inspector into two collapsible rails
One 340px rail held the template gallery and every setting, so picking a
look and filling in details competed for the same column.
Templates now sits on the left and setup on the right, each with its own
header and collapse control. Collapsed, a rail becomes a 44px strip on
the same edge with a vertical label, so the way back in stays where the
rail was instead of moving to the top bar. Both are also togglable from
the palette, under a new Panels group.
Widths are 260 and 300 so both can be open at 1440 and still leave the
desk enough room for the A4 page at 100%. At 1280 with both open the page
fits to about 0.83.
Open/closed is remembered per browser under its own localStorage keys,
never in billy_invoice_data — it is a preference about the chrome, not
invoice data. usePersistedFlag follows the canvas hint's pattern: server
and first client render both use the default and the stored value is
adopted after hydration, so this cannot reintroduce the prerender
mismatch from #11. Templates defaults closed, since you pick one once.
Below lg there is still one drawer, and it now carries the template
gallery as well as setup — on a narrow screen it is the only way to reach
it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(editor): blur behind the palette, and give every command a glyph
The palette was a flat list of labels on a plain scrim.
It now blurs what is behind it, so the invoice drops away while you are
choosing. Two notes on that one line: the value is a single filter
function because the CSS minifier joins `blur(8px) saturate(115%)` into
`blur(8px)saturate(115%)` and the declaration is then dropped — the
computed style read back `none` until this was split out. Both the
prefixed and standard properties survive minification now, confirmed in
the built stylesheet and by reading getComputedStyle in the page.
The rest is the list itself: every row carries a leading glyph on a
shared 24px axis, and template rows show their own three colours instead
of an icon, so a look is visible before you run it. Group headers are
uppercase and quieter, the active row gains an ↵, the search line shows
how many commands match, and the panel is wider with a softer corner and
a spring entrance.
use-editor-commands is now .tsx, since the icons are JSX.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(editor): drop the palette's accent bar, rebuild the collapsed rail
The active palette row carried both a blue wash and a 2px bar down its
left edge. The wash already marks it, so the bar is gone.
A collapsed rail no longer turns its name sideways. It is a slim tinted
strip with one icon button and a muted chevron pointing the way the rail
will open, with the name on the button's tooltip and label instead — a
rotated word is hard to read and made the strip look like a leftover.
While the templates rail was open it was also truncating half its names
to "Corporate Bl…" and "Mono Eleg…", because the name shared its row
with the selected tick. The tick is now a badge on the thumbnail and the
Pro chip sits beside it, so the name gets the full cell width; with the
rail at 280px all ten fit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: make the editor and landing page work on phones
Editor
- On a touch screen where the page is scaled below 75% (or a phone held
sideways), tapping a field opens a bottom edit bar: a 16px input above
the keyboard, labelled with the field, with Done and Cancel. The page
itself is untouched, so the preview and PDF render exactly as before.
- Number fields use a decimal keypad and accept "12,50" as 12.5.
- The first-run tip says "Tap any text to edit it." on touch and wraps
instead of pushing the page off screen at 320px.
- Undo and redo move into the bottom zoom bar on phones, where the top
bar has no room for them.
- The command palette hides keyboard hints on touch screens.
- The invoice number in the top bar shows from 1024px up, instead of
truncating to "I..." on tablets.
Landing
- The hero headline scales down on narrow phones instead of overflowing.
- The "Designed for you" checklists stack to one column under 560px.
- The hero legend stacks and gets room below the product shot.
- The "Invoices this month" label no longer runs out of its card.
Checked at 320, 375, 390, 768, 1024 and 1280px wide. tsc is clean, lint
still reports the same 4 errors and 12 warnings (none in these files),
and the static build passes. There is no test runner.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* chore(landing): drop open-source mentions and em-dashes for launch
Billy launches free but not open source, so the landing page no longer
links to GitHub or calls itself open source:
- Remove the "View the source" button and the footer's Open source
column (GitHub, Report a bug, Issues).
- Reword the hero note, footer blurb, Unlimited card and the "Is it
really free?" answer to say free, with every feature unlocked.
Also remove the hero chip, the numbered legend under the product shot
(and its pins on narrow screens) and the footer lock line.
Replace em-dashes in visible text: page titles use "Billy | ...", two
FAQ answers use commas, and an empty client name in the settings panel
reads "Not set".
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(editor): stop numbering every new draft INV-1024 (#30)
The default invoice hardcoded "INV-1024", so every first invoice on a new
browser, device or cleared storage got the same sample number. "New" also
fell back to INV-1024 whenever the current number had no trailing digits,
silently reusing a number.
- New drafts start at INV-<year>-001 (unique per year, as GST expects).
- "New" still bumps the trailing number and keeps prefix and padding.
- A number with no digits gets a suffix ("Draft" -> "Draft-2"); an empty
one starts a new sequence.
- Numbering moves to @billy/invoice-core (numbering.ts) with tests.
Existing saved drafts keep their current number.
* feat: founding members (numbers, Founders template, wall) (#32)
* feat: founding members (numbers, Founders template, wall)
Everyone on the waitlist when billy launches becomes a founding member:
a number by signup order, a personal link that unlocks a founders-only
template, and a spot on a public wall if they asked for one. There is
still no server, account or database.
- invoice-core/founders.ts: codes look like v1.<n>.<signature>, signed
offline with an Ed25519 key and checked in the browser against the
public key. Nobody can mint one or move it to another number.
- Editor: opening /editor#founder=<code> verifies it, remembers it in
this browser, strips it from the URL and says hello. A bad link gets
a clear error instead of silently doing nothing.
- Founders template: billy's sky-blue gradient masthead, white type and
the landing page's hand-drawn underline. Locked in the template rail
for everyone else and hidden from the command palette. Exports to PDF.
- /founders: the founding members wall, from content/founders.json
(name, number, avatar only; never an email), with Gravatar photos or
initials, and a "you're #n" line for founders. Linked in the footer.
- scripts/founders: keygen (once; private key lives in ~/.billy, never
in the repo) and launch (reads the Loops export, numbers everyone,
signs links, writes founderNumber/founderLink back to Loops, builds
the wall, keeps a private record in ~/.billy).
* docs: add the founders + launch runbook
A single reference doc so the launch-day process, file map, and email
template survive even if context gets lost. Covers what's done, what's
left, the exact commands to run on launch day, and the recovery notes
for the signing key.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* rename: founding member -> early bird; avatars via blobatar.dev
"Founding member" read as too much. User-facing copy now says
"early bird" throughout (template name, unlock toasts, the wall,
docs/founders-launch-plan.md and its email template). Internal
plumbing is untouched on purpose: Loops properties (founderNumber/
founderLink), the localStorage key, the #founder= URL param, file
and route names, and the signing message all stay as they are —
renaming those would mean redoing Loops setup and invalidating
nothing-yet-issued links for no user-visible benefit.
Also swap Gravatar for blobatar.dev for wall avatars: a deterministic
generated avatar per email, no key, no account, no dependency on
whether someone happens to have a Gravatar set up. Everyone who opts
into the wall now gets a consistent on-brand avatar.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* docs: bump status note (retrigger Cloudflare build)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* Landing: real editor screenshot in the hero, Star on GitHub (#42)
* chore: license billy under MIT
Same LICENSE as the one going onto main, so the product branch carries it too.
Refs #35
* feat(landing): real editor screenshot in the hero, Star on GitHub
The hero now shows the actual /editor (seeded with Acme Studio billing
Maple & Co.) instead of the hand-built LiveInvoice mock, as webp in the
existing browser frame. Phones get a crop of the mobile editor. The
hand-drawn pins still point at a real line item and the amount due.
The typing animation and its CSS are gone with the mock.
Nav gains a Star on GitHub button with the live count (icon only on
phones), and the footer an Open source column: GitHub, Contribute, MIT
license.
Refs #37, #38
* docs: third-party notices for launch-alpha
Refs #35
* fix(landing): recapture the hero shot without the floating zoom control
The zoom pill sat on top of "Add line item" and read as a glitch in a
still. Also measures the line-item pin instead of estimating it.
* chore: remove the dead Supabase backend, stale deploy configs and unused code (#43)
Removes what nothing references any more:
- supabase/ migrations and apps/web/scripts/migrate-images-to-storage.ts
(the backend is gone; billy is fully client-side)
- netlify.toml, posthog-setup-report.md, skills-lock.json (listed skills
this branch doesn't have)
- app/_legacy, components/demo, 11 unused components, lib/posthog-client.ts
(PostHog is called directly via posthog-js), the Stat component and
prefersReducedMotion, create-next-app SVGs in public/
- gsap and @gsap/react, used only by the deleted dot-flow
Docs now match: README describes the Cloudflare Workers deploy and the
real layout, AGENTS.md and the deep-review skill drop the Supabase and
posthog-client references, and AGENTS.md notes invoice-core's bun tests.
Refs #39
* feat(founders): let an early bird unlock the template by pasting their link
A founder link only unlocks the browser that opens it, so an early bird on a
new device saw a locked template and no way in. Tapping the locked Early Bird
template now opens a small dialog: paste the emailed link, or just its code,
and the template unlocks on that device. It is the same signature check as the
email link, in the browser, and nothing is sent anywhere.
extractFounderCode pulls the code out of whatever was pasted, including a link
with a line break in it or one a click tracker percent-encoded.
* copy(landing): drop "runs entirely in your browser" from the hero and footer
The hero note and the footer blurb no longer say it. The footer credit now
reads "Built by Vedant" with a 🫶, still linking to vedantworks.com.
* feat(early-birds): move the wall to /early-birds with mascot faces, a You card and doodles
/founders is now /early-birds. Every early bird is on the wall as their number
and a face drawn from it, with a name only for people who gave one, so nobody
needs an avatar file or a name to appear. The faces come from the receipt
mascot, with enough parts (eyes, mouths, glasses, hats, headphones and so on)
that no two numbers match.
A browser that holds an early bird's link sees a You card: their own face, a
thank you, and an Add my name or Change my name button. There is no server to
save a name to, so it opens an email to hello@trybilly.tech and names are added
by hand. The launch plan covers handling those requests.
The wall also gets doodles, cards that lean and straighten on hover, a count,
and a closing call to action.
launch.ts now writes every early bird to founders.json (name or null) instead
of fetching avatars, and gains --exclude and --only for test signups and test
sends.
* chore(editor): remove the Pro tag from the template picker
The tag was on Minimal Teal only. The template itself is unchanged.
* docs: rewrite the README with the logo and screenshots
Adds the logo (light and dark versions, rendered from the real Brand component
because the SVG wordmark depends on the Geist font), screenshots of the landing
page, editor, templates, phone layout and early birds wall, and sections for
features, privacy, how it works, running it locally, deployment and
contributing. The early birds screenshot uses placeholder names.
* chore(early-birds): add the launch wall, 49 early birds
Written by scripts/founders/launch.ts from the 2026-10-14 Loops export, with
two test and unsubscribed contacts left out. Each entry is a number plus the
name the person gave when they gave one (18 of 49). No emails.
* feat(seo): set metadataBase on www, add robots, sitemap, llms.txt and structured data
Social cards and canonical URLs now resolve to https://www.trybilly.tech. The
og:image was pointing at http://localhost:3000/brand/og.png. robots.txt and
sitemap.xml are generated at build time, llms.txt describes the product, and
the landing page carries Organization, WebSite, SoftwareApplication and FAQPage
data built from the FAQ it already renders. The landing page and the early
birds page each get their own canonical URL.
These replace the waitlist versions that only exist on main.
* docs: say Vercel in the README and AGENTS.md, add CONTRIBUTING.md
Production deploys from main on Vercel, with Cloudflare only on DNS, but the
README and AGENTS.md still described a Cloudflare Worker. The README tree and
deployment section now match vercel.json, including that the security headers
come from there. CONTRIBUTING.md comes over from main, since the landing footer
links to it, and now says to branch from main.
* feat(landing): centre the navbar links and add a sponsor ask
The nav was a flex row with space-between, so the links sat halfway between
the logo and the wider button group and drifted left of the page centre. It is
now a three-column grid with equal outer tracks, so the links are on the
centre. Below 980px, where four links no longer fit centred, they are hidden
like they already were on phones.
Adds a Sponsor link in the nav, a sponsors section before the final call to
action, and a Sponsor billy link in the footer. The button is a mailto to
hello@trybilly.tech, as it was on the waitlist site. The sponsors list is
empty for now, and logos appear above the button once one is added to
lib/sponsors.ts.
---------
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Everyone on the waitlist when billy launches becomes a founding member: a number by signup order, a personal link that unlocks a founders-only template, and (if they asked) a spot on a public wall. Still no server, account or database.
How it works
main) and goes straight into Loops. No database.LOOPS_API_KEY=… bun scripts/founders/launch.ts --csv <export.csv>(try--dry-runfirst). It:https://trybilly.tech/editor#founder=v1.<n>.<sig>;founderNumberandfounderLinkonto each Loops contact for the launch email;apps/web/content/founders.jsonplus Gravatars inapps/web/public/founders/for people who opted into the wall;~/.billy, never in the repo.invoice-core/src/founders.ts), remembers it, strips it from the URL and shows "Welcome, founding member #n". A bad link shows an error.What's in here
packages/invoice-core/src/founders.ts+ tests: code format, signing message, verification./founderswall: name, number, Gravatar or initials avatar; "You're founding member #n" for founders; empty state until launch. Linked from the landing footer.scripts/founders/keygen.ts(already run once: the private key is at~/.billy/founder-signing-key.json, back it up) andscripts/founders/launch.ts.Checks
bun testin invoice-core: 15 pass (4 new): valid code, number swap, wrong key, garbage input.tscclean,bun run buildpasses (/foundersexports statically), no new lint warnings.Before launch (you)
founderNumber(Number) andfounderLink(String), and use{founderNumber}/{founderLink}in the launch email.~/.billy/founder-signing-key.json. Losing it means no new codes; replacing it breaks every code already sent.🤖 Generated with Claude Code