Changelog
All notable changes to Patens. Format loosely follows Keep a Changelog; versions are tagged as vX.Y.Z on the GitHub repo.
[Unreleased]
The OSS-readiness arc + Swiss editorial rebrand + TypeCon Portland launch prep. Accumulates since v1.5.2; will tag as v1.6.0 once the demo GIF lands + real-device responsive sweep is complete.
Security
- Share delete-tokens stored as
sha256:digests (PR #8). Previously the Vercel Blob holding the delete-token was public-readable and contained the raw token. Now we store only the SHA-256 digest with a stable prefix; the originator keeps the raw token client-side.tokenMatchesStoredfalls back to a constant-time raw compare for pre-existing shares so existing tokens stay valid until their next write (migration is automatic on re-share). - Baseline HTTP security headers on every response via the SvelteKit
handlehook:X-Content-Type-Options: nosniff,X-Frame-Options: DENY,Referrer-Policy: strict-origin-when-cross-origin,Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=(), plusStrict-Transport-Securitygated on HTTPS so local dev still works. Verified in production viacurl -I. - Production error sanitization. Stack traces and internal paths no longer leak to the client in production; the
+error.svelteboundary gets a generic "Something went wrong." Dev keeps full details. The previous dev-overlay XSS surface (it usedinnerHTMLonerror.message) is closed — overlay now uses DOM API +textContent. /api/ai/messagesAnthropic proxy hardening. Request body capped at 256 KB; max 8 messages per request; max 24 K characters per text block; max 12 K characters insystem; max 4 096max_tokens. Model allowlist enforcesclaude-opus-4-7/claude-sonnet-4-6/claude-haiku-4-5-20251001. Upstream error bodies no longer forwarded verbatim — replaced with a sanitized{ error, status }payload to prevent leaking rate-limit / quota detail./api/share/[id]/versionsno longer returns direct Vercel Blob URLs. Consumers route through/api/share/[id]?v=Nso future access controls don't get bypassed. ShareVersion type updated;/share/[id]page consumes the slimmer shape.
Added
- CLI:
npx patens audit— the 94-code audit module + preflight + compatibility checks shipped as a Node CLI. Single bundled ESM via esbuild, 3 output formats (text/json/githubfor PR annotations), exit code 2 on errors. Drop-in CI step; closes the "audit is locked inside the editor" critique. 13 unit tests cover the formatters. - 10 Architecture Decision Records under
docs/decisions/(Michael Nygard format). Documents the load-bearing choices: browser-native, MIT-forever, SSR-at-root, audit worker, share-as-capability, storage namespace staysfont-studio-*, BYOK AI, strict quality gates, demo open-by-default. - TypeDoc API reference —
pnpm run docs:apibuilds HTML docs underdocs/api/for the audit + export + family-kerning + kerning-classes + path + font-machinery modules. Output gitignored, regenerable from source. - 2 new perf benchmarks —
buildFont(50/162/500-glyph projects: 2.4ms / 19ms / 126ms per call) +expandKerningClasses(small/medium/large with up to 360k expanded pairs in 19ms). Mirror the existingaudit.benchpattern. - 30-second demo GIF storyboard at
docs/launch/demo-gif-storyboard.md— frame-by-frame pre-record blueprint: setup checklist, ffmpeg compression commands, per-surface delivery table. - Responsive audit + sign-off checklist at
docs/launch/responsive-audit.md— code-level findings plus the iPhone + iPad device-test list that requires real hardware. - AGENTS.md — Linux Foundation cross-tool AI-assistant onboarding standard.
- ARCHITECTURE.md — six-layer map + the core flows (cold-load, audit, share-write, export) + the SSR posture rationale.
- DESIGN_PHILOSOPHY.md — the why behind Patens (teaching-first, audit-as-citizen, no AI-led marketing, never paywall the editor). Referenced from PR template.
- MAINTAINERS.md — solo-maintainer response SLA, triage cadence, scope vs. out-of-scope.
docs/release-process.md— solo-maintainer release playbook: cadence, semver rules (audit-code IDs stable forever), pre-release checklist, tagging, post-release verification, hotfix flow, rollback via Vercel promote.- CodeQL SAST + OpenSSF Scorecard + Dependency-review + Release-drafter GitHub Actions workflows.
- CycloneDX SBOM via cdxgen in CI — EU CRA future-proofing.
- Devcontainer config for Codespaces + VS Code dev-container.
- OSS-readiness signal artifacts:
FUNDING.yml, RFC 9116security.txt,humans.txt,CONTRIBUTORS.md. Plus 6 paste-ready prep docs underdocs/launch/— NLnet NGI Zero Commons, OpenSSF Best Practices, USPTO trademark walkthrough, GitHub Sponsors, Polar, 3 awesome-list PRs. SUPPORT.md+.github/ISSUE_TEMPLATE/config.yml— channel routing: bugs → issues, ideas + Q&A → Discussions (now enabled on the repo), security → SECURITY.md. The chooser config exposes Discussions + /help + the GitHub Security Advisory form as contact_links above the Bug / Feature templates; the question.md template was removed since Q&A now has a dedicated home in Discussions.- GitHub social preview image —
static/github-social.png(1280×640, 30 KB) generated bypnpm build:github-social. Renders the new round O + smooth-shoulder n + HONE/TONE samples from the demo OTF + 94-rule audit-module strap-line. Drop into Settings → General → Social preview (no GitHub API exposed for this). SoftwareSourceCodeJSON-LD entity on the home page @graph — cross-references the existingWebApplicationviatargetProduct@id. Useful for AI-agent retrieval ("what's the source for this product") + SERPs that want to surface the GitHub repo alongside the live web app.- OpenSSF Best Practices Silver-tier self-cert answers appended to
docs/launch/openssf-best-practices.md. Silver adds 55 criteria over Passing; ~85% are already satisfied by the existing setup. Two SHOULDs use the rationale-acceptance escape hatch (solo-maintenance + SLSA-provenance deferred). Gold deliberately not pursued — requires bus-factor ≥ 2. - Codecov upload in CI (
codecov/codecov-action@v5, tokenless OIDC for public repos) after the existingvitest --coveragestep.fail_ci_if_error: falsekeeps merges green when Codecov has an outage. Closes OpenSSF Silver "automated test coverage tracking" gap. - Cryptographic tag signing for releases — documented setup + release flow + consumer-verification path in
docs/release-process.md. One-time setup:git config --global tag.gpgsign true(reuses the SSH key already signing commits). Closes OpenSSF Silver "cryptographic release signing" gap. fast-checkproperty tests for the audit module (9 invariants × 100 random inputs each = 900 randomized checks per run). Asserts:auditGlyphnever throws, returns an array, every issue has the full{codepoint, severity, code, message}shape, codes are kebab-case, severities are within the enum,sortBySeveritypreserves count + ordering, audit is deterministic, every issue codepoint matches the current glyph. Closes OpenSSF Silver "fuzz testing on parsers" gap.- Unit tests for the 7 pure-helper modules extracted during the editor decomposition — 145 new tests across
onboarding-targets,glyph-deriveds,path-edit-transforms,glyph-paste,glyph-export,fix-issue, andaudit-catalogue. Per-file@vitest-environment happy-domopt-in for the DOM-touching glyph-export tests; the other suites stay on the fast node env. Suite count: 528 → 672 (+144). /privacy+/securitypages — plain-English, GDPR-shaped data inventory + responsible-disclosure flow. Both prerendered with full JSON-LD./presskit — factsheet (structured-data canonical), three-length descriptions, brand assets + screenshots placeholder, milestones.- Re-share version picker UI on
/share/[id]+ multi-master axis-pair picker on/designspace. Closes the two "server-ready, UI pending" items from the v1.5 backlog. - 94 dedicated audit-rule pages at
/audit/[code]— single source-of-truth catalogue atsrc/lib/font/audit-catalogue.tsbinding code → title → category, with description sourced fromaudit.ts'sdescribeAuditCode()so editor inline help + public pages stay in sync. Each page is prerendered (94 static HTML files) and shipsDefinedTerm+TechArticle+BreadcrumbListJSON-LD cross-referenced by@id. Targets the long-tail typography queries that previously had no dedicated landing page on the open web ("what is overshoot in type design", "what is contour-winding-collision")./learn/audit-codesupdated to link each code to its dedicated page; sitemap folds in all 94 URLs at priority 0.6. /pronunciationpage withFAQPageJSON-LD — answers "how do you pronounce Patens" with IPA + the Latin etymology (patēns, "to lie open"). LLMs extract Q&A directly; niche brand names benefit measurably from a focused page per the launch AIO research. Linked fromSiteFooterunder "Company".docs/launch/typecon-runway.md— canonical 10-week launch plan that supersedes the partial calendars inmaster-plan.md § 6andapp-improvements-plan.md's P0/P1 sections. Covers ownership splits (yours / mine), week-by-week calendar, eight named risks with mitigations, six decision points each with a recommendation, and a done-definition for launch.- NEW: dedicated
/auditmarketing page — differentiator landing for the 94-code audit module. Hero + 9-family breakdown + 6 verbatim sample codes + CLI integration + 7-Q&A FAQPage + see-it-in-action CTAs. Prerendered to ~33KB. WebPage + BreadcrumbList + FAQPage JSON-LD for AI-engine citation. - Trust band on home page — 4-column Swiss-disciplined "by the numbers" row below the hero: 94 audit codes · 162 demo glyphs · 528 tests · MIT open source. Mono-uppercase labels + Hoefler-serif numerals.
- "How the audit teaches" 3-card sample on home — verbatim
describeAuditCode()prose forself-intersecting(fixable),xheight-misaligned(designer judgment),sidebearing-class-drift-lsb(designer judgment). Demonstrates the differentiator IN SITU. - SiteHeader + SiteFooter components —
src/lib/ui/SiteHeader.svelte(sticky top nav, audit-led 4-item navigation with prefix-match active state) +src/lib/ui/SiteFooter.svelte(4-column grid: Product · Learn · Company · Legal & Source). Wired into 16 marketing pages + the 404 page. - 6 dedicated Satori-rendered OG variants —
/og/audit,/og/learn,/og/compare,/og/press,/og/about, plus the existing/og/brandand/og/home. Each marketing page'sog:image+twitter:imagenow point at its route-specific card with route-specific copy + URL stamp. - Article schema per release on
/changelog— top 10 releases now ship as discrete schema-typed entities (Article + datePublished + author + publisher + description) so AI engines can cite "what changed in Patens v1.5.2" as a specific citable answer. - Public marketing surface long-scroll editorial home page (
/) — expanded the teaser into a multi-section narrative: hero waitlist + Swiss "by the numbers" trust band + annotated demo specimen + interface mock of the editing surface + audit-as-teaching 3-card sample + 94 codes by family + compact compare row (8 tools) + final waitlist. Teaser-only version retired. - Private alpha gating — editor moved to an unguessable URL behind a passcode-free invite list; public
/no longer exposes a path into the app. Waitlist persists to Vercel Blob (access: 'private', key =sha256(email)) with dedupe + export endpoint for the founder. - Show HN + TypeDrawers + r/typography launch drafts at
docs/launch/show-hn-draft.mdanddocs/launch/typedrawers-intro.md— three title variants, ~180-word body, 7 anticipated comments with draft responses, pre-flight checklist, T+24h cross-post timing. - Vercel OSS Program application copy at
docs/launch/vercel-oss-application.md— three pitch lengths (50/150/300 words), open-source bona fides checklist, credit-utilization plan, maintainer bio. Ready to paste before the June 3, 2026 deadline. - OSS programs survey at
docs/launch/oss-programs-survey.md— 20 grants/credits/funding rails ranked by urgency for Patens (NLnet, Anthropic credits, GitHub Secure Open Source Fund, FUTO, Mozilla MOSS, GitHub Sponsors, Open Collective, Polar.sh, etc.). Deferred NLnet to Aug 1, 2026 cycle (post-TypeCon traction). - Lipi.ai Font Studio + Fontish added to
/compareas the 10th and 11th tools. Comparison now spans the entire 2026 type-editor landscape — desktop pro (FontLab, Glyphs, RoboFont), browser (Fontra, Glyphr Studio, typlr, BirdFont, FontForge, Patens), and AI-first (Lipi, Fontish). Patens column stays at the same X position across all group sections; every competitor name links to its product site. - Research foundation at
docs/research/— 4 docs totaling ~1900 lines:
- canonical-library.md (526 lines, 38 sources) — RAG corpus for the citation engine. Licensing matrix per audit family. 10-source open-licensed MVP starter corpus covering all 9 families. - ai-audit-mapping.md (415 lines) — All 94 codes classified for AI augmentation: 75 algorithm-only, 11 vision-augmented, 1 vision-primary (sharp-kink), 3 LLM-augmented. Top 10 vision second-opinion candidates with prompt templates. ~$100/weekend empirical methodology. - multi-script-canon.md (691 lines) — Cyrillic, Greek, Arabic, Hebrew, Devanagari, CJK, Thai, Armenian, Georgian, Tibetan with native-tradition citations. Recommends Cyrillic + Greek + Arabic as 3-script MVP. - ai-features-roadmap.md (285 lines) — 5-feature product roadmap with launch-gate ordering, dependencies, grant-application leverage. Synthesizes the three research tracks.
- 5 paste-ready grant applications at
docs/launch/:
- anthropic-application.md (203 lines) — combined Startup + Research Credits, $50K target. Leads with the 75/11/1/3 audit classification. - github-secure-oss-application.md (232 lines) — $10K + hardening sprint. Five concrete sprint deliverables (SLSA, fuzz, reproducible builds, CLI hardening, threat model doc). - futo-microgrant-application.md (77 lines) — $5K cash, anti-establishment voice, $5K line-itemed against bespoke Cyrillic + Greek lowercase + TypeCon travel. - mozilla-moss-application.md (171 lines) — $25K–$50K cash. Five-axis mission alignment (open web, privacy, accessibility, open source, multi-script inclusivity).
- Pre-launch master runbook at
docs/launch/master-runbook.md(300 lines) — 10-week calendar from Jun 2 to Aug 8 with per-week tasks + ownership (you vs me). Critical-path items explicitly flagged. - Show HN day hourly runbook at
docs/launch/show-hn-day-runbook.md(247 lines) — sleep-deprived cockpit-checklist for Tuesday August 4, 2026. Hour-by-hour from 04:45 AM PT wake-up through 21:00 PM PT bedtime handoff. - Handoff log template at
docs/launch/handoff-template.md— copy per milestone day (handoff-YYYY-MM-DD.md) for tracking submissions, metric snapshots, conversations, questions, bugs, surprises. - TypeCon Portland 2026 intel at
docs/launch/typecon-portland-2026.md(5,150 words) — 2026 CFP CLOSED (Apr 6 extended deadline; notifications early May). Top-5 networking targets: Dave Crossland (Google Fonts), Glenda Bellarosa (Adobe Fonts / Font Bakery), Lizy Gershenzon + Travis Kochel (Future Fonts), Christopher Slye (SOTA Ex Officio), Neil Summerour (SOTA Treasurer / Positype). Three drafted talk proposals ready for 2027 CFP. Budget $3,750 mid-case all-in. - Press contacts research at
docs/launch/press-contacts.md(5,678 words) — Tier-1 lists 15 type-design venues (Typographica, ILT, Eye, AIGA, Slanted, Type.today, Alphabettes, Print, Daily Heller) with editor names + verified contact paths. Tier-2 covers design + tech pubs. Section 3 profiles 12 specific journalists. Three outreach templates + T-22 → T+62 calendar. - TypeCon Top-5 outreach emails at
docs/launch/typecon-outreach-emails.md— paste-ready emails for the 5 highest-leverage TypeCon attendees. Send target Tue Jul 15, 2026 (3 weeks pre-conference). Tracking template + follow-up policy + Tier-2 backup list. - Show HN day hourly runbook at
docs/launch/show-hn-day-runbook.md— sleep-deprived cockpit checklist for Tuesday August 4, 2026. - Vision experiment harness at
scripts/vision-experiment.mjs(353 lines) — runnable dry-run validation of the methodology fromai-audit-mapping.mdSection 3. 100 entries (10 codes × 10 fonts), $4 cost-estimate baseline. Anthropic SDK wiring stubbed; deferred to credits-landing peranthropic-application.md. - Citation engine MVP at
src/lib/citations/(5 files + 12 tests) — Feature 1 ofdocs/research/ai-features-roadmap.mdgoing live.CITATION_MAPpopulated with anchor + gist data for 91 of 101 codes drawn from the 11-source open-licensed MVP corpus (OpenType spec, TrueType ref, AGL, FEA, UFO 3, Unicode 16, Sheep CC, OT Cookbook, Klim, Knuth 1979, variablefonts.io, Frere-Jones blog). 93 of 101/audit/[code]prerendered pages ship a "Canonical references" section. Bibliographic citations only (no body-text ingest); craft-canon citations (Tracy/Smeijers/Noordzij/Cheng) flagged for license-required ingestion in Q3 2026. - Variable-fonts research foundation at
docs/research/:
- variable-fonts-deep-dive.md (571 lines, 8 parts, 50+ sources) — Origin + history of variable fonts (Multiple Master 1990s → OpenType 1.8 2016), the 5 registered axes (wght/wdth/slnt/ital/opsz), custom axes (GRAD/XOPQ/softness), the variations math (gvar/avar/STAT/HVAR/MVAR), designspace concept, VF support across 11 type editors, designer-side considerations, multi-script implications, ranked 10-feature roadmap. - variable-fonts-v2-implementation-plan.md (398 lines) — Paste-ready specs for the 6 pre-launch + 4 v2 audit codes with check/fix pseudocode, citation mappings ready for matcher.ts, STAT editor UI specification, designspace round-trip module specification, file-list-to-update for each scenario, 3-week sprint plan if all priorities ship pre-launch.
- 5 new variable-font audit codes shipped (94 → 99) + Stat type model:
- axis-range-extreme (info) — flags wght > 800, wdth > 100, opsz > 50pt, slnt > 30° threshold violations. Custom axes (uppercase tags like GRAD) intentionally excluded. - master-too-close (warn) — pairwise normalized euclidean distance between masters across shared axes. Threshold 5%. - stat-missing (warn) — variable font has axes but no familyAxes set on the project (Patens uses familyAxes to generate STAT at export-time). - stat-format-mismatch (warn) — STAT italic axis-value uses format 1 instead of format 3 (linkedValue). Format 1 breaks Windows style-name composition ("Regular Bold Italic" instead of "Bold Italic"). - stat-instance-name-mismatch (warn) — STAT-composed instance name differs from the fvar styleName for the same location. Causes inconsistent display across OS apps. - Each with full describeAuditCode prose, citation mapping (OpenType STAT chapter + variablefonts.io primer), check function in preflightProject(), and 17 new unit tests across positive/negative/edge cases. - New Stat type model in types.ts — DesignAxisRecord + 4-format AxisValue discriminated union (Format 1/2/3/4) per the OpenType STAT spec. Project gains optional stat?: Stat field; undefined = let Patens generate minimal STAT from fvar at export.
- Designspace v5 import + export at
src/lib/font/designspace.ts— round-trip XML interchange with Glyphs, FontLab, RoboFont, and Fontmake.
- parseDesignspaceXml(xml) — parses axes + sources + instances; validates 4-char axis tags, min/default/max ordering, non-empty axes. DOMParser-based; browser-only. - designspaceToProject(ds, existing?) — maps the structured shape to Patens Axis / Master / VariableInstance records. Identifies the default source (matching every axis default) and maps non-default sources to Project.masters[]. - designspaceFromProject(project) — serializes back to v5 XML. Emits default-location source plus non-default master sources, instances with familyName/styleName/postScriptName, axes. Includes XML attribute escaping. - 21 tests covering parser correctness, mapping logic, error paths, XML escaping, and the round-trip lossless guarantee. - UI wiring complete end-to-end: Studio dashboard "Import from .designspace" button creates a fresh project from the XML; /project/[id]/export Import panel merges into the current project; /project/[id]/export Export panel button (disabled when no axes) downloads back to .designspace. Dynamic imports keep the parser chunk off the cold-load path.
Changed
- Audit-led home welcome strip copy. The first-visit non-blocking strip now leads with the audit module as the differentiator: "A type design tool that teaches as you draw." Compresses cleanly to a tweet or a Show HN headline. Welcome dialog later enriched with a mono-uppercase metadata row (94 codes · 30 one-click fixes · MIT · no installs) + a new "Why audit-as-teaching →" CTA pointing at
/audit. - Swiss-influenced heading scale-up across 8 marketing pages. Hero
sm:64→sm:80, h136→48, h2 canonical20→28(incl./changelog24→28,/learn22/40→28/48). h2 vertical rhythmmt-12 mb-3→mt-16 mb-4. Body sizes unchanged — Patens's density (14-15) is deliberate. - Section rules on document-flow pages (
/about,/privacy,/security) —border-t border-border/30 pt-12above each h2 for structural skeleton. - CONTRIBUTING + MAINTAINERS cross-links to ADRs, DESIGN_PHILOSOPHY, AGENTS, release-process. Lint-gate exception rationale points at ADR-009.
- README opening blurb reworked to lead with audit-as-teaching positioning, not feature-list. Capability table reorders Audit to the top as the headline row. Closing paragraph back-links to
/auditso GitHub visitors can drill into the differentiator marketing page. - Comprehensive SEO + AIO completeness sweep. Every public route now ships: JSON-LD (WebSite + Organization + Person + WebApplication on home; CollectionPage + ItemList on /learn; FAQPage on /audit + /help; Article × 10 on /changelog; TechArticle on reference pages; HowTo on tutorials), canonical URLs, absolute
og:imageURLs,og:image:alt+twitter:image:alt,twitter:site+twitter:creator(@patenstype),meta name="author",og:localeen_US,og:image:typeimage/png. Sitemap shipped with per-route honestlastmoddates (replacing the "everything updated today" anti-pattern that lowers crawler trust). - PWA manifest enrichment — added
id,display_override(window-controls-overlay),screenshots, and 2 appshortcuts(Demo + Audit) for richer install prompts on Chrome Desktop + Android. /help+/changelogtitles carry "2026" — closing the year-in-title pass per the Leapd GEO finding (visible year in<title>≈ +30% citation rate from AI engines). The other landing routes (/,/about,/compare,/learn/*) already had it.- Demo OTF expanded 9 glyphs → 26. Was: space + H/O/T/I/E/N + o/n only. Now: A C D E G H I M N O P R S T U + a e h n o s t + 0 1 2 + . , - !. Lets the home hero, OG image, and GitHub social preview render "PATENS · STUDIO GEOMETRIC · HONE THE TONE" in the project's own font instead of "Hn" + "HONE" + "TONE". Three winding-rule artifacts fixed in the process: bowl-ring outer-left now aligns with stem-left (P/D/R/U); C/G/e mouth-cuts clipped to outer curve to prevent the +1 outer − 1 cut = filled-crescent artifact; digit 2 rebuilt as a single CCW contour. Demo OTF grew 2.3 KB → 4.4 KB.
- Demo OTF proportions refined. Bowl rx 0.32 → 0.40 of CAP_W on P/R; D bowl rx 0.4 → 0.48 (was reading as "I with a balloon"); A rebuilt as two parallelograms meeting at a STEM-wide flat apex with a properly-clamped crossbar; G's spur extended from cx → cx+rx so it visibly attaches to the right wall; lowercase a rx formula corrected (115 → 160; was rendering as a narrow oval). Kerning regression snapshot updated — values shifted with the new silhouettes but all changes typographically sensible (PA tightens with bigger P bowl; Ta loosens with wider A).
- In-editor demo project (the
/project/demo/editshipped font) now uses curved Béziers for bowl letters.demo-project.tswas using straight-line polygons only — 16-sided polygon for O, three-rect stacks for P/R/D/B/C/G/U "bowls". Now: kappa-Bézier rings via a newcurvedRinghelper +stemBowlfor the bowl-stem composites. TheBezierContourformat already supported cubic curves viaPathCommand.type === 'C'; the old code just chose to emit L-only. Editor still renders, edits, and runs boolean ops on the curves natively. /share/[id]version selector —window.location.hrefreload replaced with SvelteKitgoto(url, { invalidateAll, noScroll })for SPA-smooth version switching (no white-flash full reload). Dropdown options now show file size alongside date. New "copy permalink" button appears when a historical version is pinned, so a designer can hand around the exact-version URL without memorising the path.- Swiss editorial rebrand across the marketing surface. IBM Plex Sans (300/400/500 weights) replaces the prior Hoefler-led stack; stone palette (
stone-50/stone-950canvas,stone-900/stone-50foreground with opacity hierarchy) replaces neutral grays; Swiss Red#C8102Eis the single accent. ~90 inlinefont-family: Hoefleroverrides stripped across 40 files. CSS custom properties moved from:root.theme-darktohtml.theme-darkfor guaranteed specificity. App shell + footer now read as a Swiss editorial typeface specimen, not a generic SaaS chrome. - OG card system rewritten Swiss. Single font face (Inter-500.ttf, 60 KB Latin subset), stone palette, 56×6 Swiss Red corner mark top-right, hierarchy via size + opacity per the Swiss "no second hue for hierarchy" rule. Headline 180px for brand/project, 120px for marketing variants. 7 marketing variants + per-project specimen cards all on the unified template.
- SiteHeader + SiteFooter Swiss reorg. Header: rectilinear icon box (
rounded-lg→rounded-sm), lang switcher + theme toggle paired in a flex container outside main nav (visible on mobile). Footer: numbered Swiss columns (01 PRODUCT, 02 LEARN, 03 COMPANY, 04 LEGAL) + 40–52px HoeflerPatenswordmark in identity strip + signature beneath. - /audit + /press + /families + /help editorial passes. Each canonical reference page now carries mono-numbered section prefixes (01–0X) at the H2 spine,
rounded-noneon remaining chrome, focus-visible rings on back-links, and the editorial border-t/border-b rule pattern that /learn established. /families brought frommax-w-6xl+ inline monospace wordmark into line with the rest of the marketing surface (max-w-5xl+ Plex Sans inherit). - /learn structural Swiss pass. 12
rounded-2xl/rounded-xlcards stripped to editorialborder-t/border-b border-border/40 pt-5rules; 6 sections numbered 01–06 with mono prefixes; decorative icons (BookOpen, Compass, Pencil, Wrench) removed from section headers — kept functional ones (ExternalLink). Bottom CTArounded-md bg-accent→rounded-none bg-fg. - /es chrome cleanup.
/es/pressdescription panels +/es/pronunciationtrivia card go fromrounded-mdcards to editorial border-t rules, mirroring the EN treatment. (Deeper structural numbering deferred — different H2 scale + voseo voice constraints.) /comparetable column alignment. Patens column stays at the same X position across all group sections (table-fixed+<colgroup>with78 / competitors.length% widths). Card chrome around the tables stripped (overflow-hidden rounded-lg border bg-surface-1/40removed). Every competitor name wraps in an external link withtarget="_blank" rel="noopener noreferrer".consoleHello()Swiss rebrand — Plex Sans + Plex Mono in DevTools output, Swiss Red URL stamp, signature line in muted stone.celebrate()fallback palette switched from cobalt/sage/cream to Swiss Red + sage + stone.- WaitlistForm success-state micro-transition. Success message uses
fly+fadeSvelte transitions withcubicOuteasing — checkmark fades in after the message slides up. - README refresh for current state. "Try it" section now points at public marketing surfaces (no gated
/project/demo/editlinks during private alpha). Status reframed from "production-grade" to "private alpha by invitation, public launch at TypeCon Portland Aug 6–8, 2026." Test count 528 → 722. - Second Swiss editorial wave across the deferred surfaces.
/audit/[code](94 dynamic SEO pages — auto-fix pill + CLI code block + peer-link tiles + kbd + code chips rectilinear)./privacy+/security(5 numbered sections each + rectilinear inline<code>chips + focus-visible back-links).+error.svelte(tofu codepoint boxes go rectilinear, reinforcing the "glyph missing from typeface" metaphor)./learn/*(7 tutorial pages — step badges + code chips + bullet dots + TOC nav)./share/[id](50 chrome instances stripped on the most-public alpha surface)./studio-c104c94c+/family/[id](24 each, the alpha dashboard + family hub). /esstructural numbered prefixes. 5 multi-section Spanish pages (/es/audit,/es/press,/es/privacy,/es/security,/es/about) gain mono 01–0X H2 prefixes matching the EN pattern. Pages with 1–2 H2s skipped (no rhythm to establish). Voseo voice preserved verbatim.- Global
max-w-[60ch]constraint on body prose. One CSS rule inapp.cssapplies the Swiss-spec body-text width to every<p class="...leading-relaxed...">page-wide.:not([class*='max-w-'])guard preserves explicit per-paragraph designer intent on the rare paragraphs that need to span wider. - Plex Sans CSS async-loaded. Saves 100–300ms LCP on cold loads via the canonical Filament Group pattern (
media=print + onload=media=all); NoScript fallback preserves render-blocking behavior for non-JS users. - OG card unused color const removed. Dropped the
SUBTLEpalette constant inog.png/+server.tsleft over from the pre-Swiss palette.
Fixed
- Home page Lighthouse A11y 98 → 100. axe-core flagged
landmark-one-main: the home page lacked a<main>landmark. The outer drag-drop surface (<div role="application">) became<main>; drag-drop event handlers unchanged. - Editor mobile-gate. A phone hitting
/project/[id]/editused to get the desktop editor crammed into a 375px viewport with no warning. Non-dismissible<1024pxbanner inside the project layout now names the constraint. iPad in landscape (≈1180px) clears it. - Pin button on home cards was invisible on touch devices (
opacity-0 group-hover:). Nowopacity-60on mobile, hover-revealed onsm:+. - Stale
security.txtpointed atfont-studio.vercel.app; updated to canonicalpatens.designURLs. /families500 in production —+page.tscalledlistFamilies()(idb-keyval) in the SSR load function, whereindexedDBis undefined. Addedexport const ssr = false;matching the posture of/share/[id]and/project/[id]/*.- Dev-server cache thrash — Vite's HMR file-watcher was firing on every
.vercel/output/static/regeneration (~17 HTML files per build × 50 commits = hundreds of bogus reload cycles, corrupting the dep-optimizer cache). Watcher now ignores.vercel/,.svelte-kit/output/*,build/,dist/, all.md, and doc trees. Also addedpnpm run cleanscript for one-command cache wipes. - Bench test bounds + timeouts for CI —
buildFont500-glyph bench bound 2000ms → 8000ms (CI runners are 15-20× slower than local); vitest test timeout raised to 30s to absorb the warm-up + iter × per-call walltime. - Stale "88 glyphs" + "8 glyphs drawn" copy on home page's "See it in action" Hn section — last of the 88→162 surface sweep that earlier passes missed.
- Light/dark theme toggle now actually flips the page. Two bugs combined to silently break the toggle: (1) Tailwind v4 compiled
:root.theme-darkto.theme-dark(specificity 0,1,0 — tied with:root, only winning by source order), and (2) theapp.htmlinline<style>usedbackground: light-dark(#fafaf9, #0c0a09)which keys offcolor-scheme: light dark, letting the browser auto-pick from OS preference rather than respecting the explicit class. Fixed by moving tohtml.theme-dark(specificity 0,1,1, guaranteed winner) and replacing the inlinelight-dark()with explicit class-based rules. - Marketing page chrome no longer shifts on navigation. Three different container widths (
max-w-3xl,max-w-5xl,max-w-6xl) caused visible reflow when clicking between marketing pages. All 28 marketing files (EN + ES) normalized tomx-auto max-w-5xl px-4 sm:px-6via a single perl pass. - WaitlistForm now actually respects
prefers-reduced-motion. The comment claimed Svelte'sfly/fadetransitions auto-respect the OS setting; they don't. Svelte transitions are JS-driven (tween properties via tick callbacks) and bypass the global@media (prefers-reduced-motion: reduce)reset in app.css. Fixed by readingprefersReducedMotion()at transition time and zeroing the duration when motion is reduced. - EN home
/declared canonical but no hreflang alternate to/es. Google won't link the two locales without bidirectional declarations. Added via the existinghreflangLinks('/')helper so the pair is symmetric. - Sitemap lastmod stale post-Swiss pass. Every page that received substantive layout changes bumped to 2026-05-30 lastmod — honest crawler signal that the editorial surface actually changed.
- *10
svelte/no-at-html-tagslint errors on/es/pages.** The pages used{@html hreflangLinks(...)}without the canonical eslint-disable comment the EN counterparts have. The helper returns a static template string fromsrc/lib/i18n.ts— known-safe to inject. Added the disable comment before each of the 10 hits. - Two typographic glitches per Swiss spec.
/share/[id]input placeholder"Type something..."→"Type something…"(ellipsis character)./es/pronunciation"patent"→"patent"(curly quotes around the English loan word).
Performance
- Production cold-load multi-run baseline confirms no perf regressions. 3 cold runs against
patens.design(Chromium viascripts/profile-cold-load.mjs):/612ms FCP (warm 342ms);/project/demo/edit1056ms LCP cold (warm 658ms);/share/demo671ms FCP (warm 522ms). Zero long tasks ≥50ms anywhere. All three routes well inside Web Vitals "good" (LCP ≤ 2.5s). A single-run +346ms "editor LCP regression" turned out to be CDN cold-warming noise; streaming-SSR-on-editor demoted from must-have to nice-to-have. Numbers + verdict captured indocs/launch/lighthouse-baseline.md. - *
/project/[id]/critical path: −243 KB raw (−68 KB gzipped) by deferring opentype.js out of the project layout's eager chain.previewStore(mounted by the project layout for every sub-route) statically importedbuildFontfrom$lib/font/export.ts, which in turn statically importedopentype.js. That made the 243 KB opentype chunk land on the audit / spacing / features / release / share sub-routes — none of which immediately render the live preview. MovedbuildFont+applyColorFontTablesto dynamic imports insidePreviewStore.build(); opentype now lands ~180 ms after layout mount when the rebuild debounce fires, well after canvas paint. Static imports ofmzzzXzJM.mjsacross the manifest: 3 → 0.** - Editor cold-load: 593 KB → 486 KB (−106 KB / −17.9%). Four-stage defer pass:
- Audit module dynamically imported on requestIdleCallback; the 94-check × every-glyph badge computation no longer blocks first paint. - SettingsDialog (5KB) + ShortcutsDialog (5KB) + StatsPopover (19KB) lazy-mount on first open. - CommandPalette (15KB) lazy-mounted in the root layout — saves 15 KB on every cold load across every route, marketing pages included. - EditorTour (7KB) lazy on tour activation. - 5 right-sidebar panels (CompositeEditor 15KB + ReferenceImagePanel 11KB + RevisionsPanel 8KB + MetricsInspector 5KB + StemsPanel 3KB) batched into a single Promise.all import on requestIdleCallback ~200ms after canvas mount.
- Pyodide lazy-loaded off the home-page eager chain. UFO import now dynamic-imports
$lib/font/pythonon first use; first-visit home no longer ships the Python runtime in the preload graph.
[1.5.2] — 2026-05-25
Security
- Closed open-redirect via
?returnToon/auth/login,/auth/callback/github,/auth/logout.safeReturnTo()helper validates the redirect target resolves to the same origin; off-origin / scheme-relative /javascript:/data:URLs fall back to/. 13 unit tests cover the helper + the encode/decode/tamper paths. - Replaced
===token comparison withtimingSafeEqualin share POST + DELETE (constantTimeEqualinsrc/lib/share-blob.ts). Defense-in-depth against timing attacks.
Fixed
- Pointer-listener leak in the 2D variation explorer. Inline-template handler was recreated on every reactive update + added new listeners on every pointerdown with no cleanup if pointerup was missed (pointercancel / lostpointercapture / ESC during drag). Hoisted to script block; added re-entrancy guard + listens for all drag-end events.
Added
- Family kerning UI panel on
/family/[id]— completes the v1.5.0 Day 9 data-only work. Table of family-wide pairs (left + right + value + delete) + inline add form. Pairs apply to every sibling at resolution time viaresolveKerning(). - e2e API smoke tests (
e2e/api-smoke.spec.ts) — 12 tests covering health version, sitemap + RSS XML validity, OG byte size with PNG magic-number check, manifest PWA shape, share endpoints 503/404 paths, auth gates. Catches the regression classes thatdocs/qa-checklist.mdexists for, in CI instead of post-deploy. session.test.ts— 13 unit tests for the signed-cookie session helpers + the newsafeReturnToopen-redirect defense.
Changed
- Lint baseline ratcheted 22 → 2 (CI gate at 5). Cleaned vestigial vars across
svg-path.ts(cx/cy/startX/startY current-point trackers that were never read),audit.ts(unused depth counter),path.ts(dead pts[] + last in reverseContour),export.ts(orphan effectiveContours wrapper),GlyphBrowser.svelte(orphan handleDelete), and a few placeholder/escape-character tightenings. - Cold-load baseline refresh at
patens.design(post-rebrand + post-preload-mjs): home FCP 1057ms (was 1319ms in v1.4.0 — 20% faster), editor FCP 906ms, share FCP 1356ms. 0 long tasks anywhere.
[1.5.1] — 2026-05-25
Changed
- Rebrand: Font Studio → Patens. Canonical domain
patens.design. Latin for "lying open, accessible" — root of patent, the word for openness before lawyers got to it. Open-source positioning made explicit in the name. - Canonical-domain 301 redirects in
vercel.json—patens.app/patens.studio/font-studio.vercel.appall permanently redirect topatens.design. One source of truth for SEO + OG previews. - Storage namespaces deliberately preserved as
font-studio-*(IndexedDB, localStorage, cookies, service-worker cache, PartyKit room) — backward compatibility with existing browser data.
[1.5.0] — 2026-05-25
Added
- WCAG 2.0 + 2.1 + 2.2 A/AA test coverage. axe-core via Playwright now runs the full WCAG A/AA tag set across 19 routes. Caught + fixed: scrollable-region-focusable on /share, link-in-text-block on 6 files (13 sites), label-content-name-mismatch on 4 editor toolbar buttons, and target-size on the 9 GlyphBrowser filter chips (now
min-h-[24px]). - Cold-load profiler.
scripts/profile-cold-load.mjsdrives headless Chromium via CDP, captures the Performance trace, and digests long tasks + layout events + FCP/LCP markers. Wired aspnpm profile [url]. v1.4.0 baselines documented indocs/qa-checklist.md. - Post-deploy QA checklist.
docs/qa-checklist.md— 9 sections covering URL smoke tests, OG image byte verification, health version drift, end-to-end editor walk, share + cloud, RSS validation, perf profile, lighthouse + axe, and CI status. Triggered by 2 production bugs caught during v1.4.0 deploy that the checklist now catches in 30 seconds. - CODE_OF_CONDUCT.md — Contributor Covenant 2.1, linked from CONTRIBUTING.
Fixed
/og/[id]500 in production —@resvg/resvg-js-linux-x64-gnunative binding wasn't bundled when prebuilt-deploying from Mac. Fix:pnpm.supportedArchitecturesconfig + deploy without--prebuiltso Vercel installs the right native bindings on Linux./og/[id]500 after first fix — Lora + Inter fonts understatic/og-fonts/aren't bundled with serverless functions. Moved tosrc/lib/og-fonts/and read via$app/server'sread()so the binaries embed in the function bundle.- Lighthouse CI broken on every push — the
lighthouse:no-pwapreset's strict insight assertions were erroring onforced-reflow-insight,network-dependency-tree-insight,target-size,uses-passive-event-listeners,uses-rel-preconnect,bf-cache. Relaxed towarn; ratcheted category-score thresholds (perf 0.85, a11y 0.90) toerrorto gate on real regressions.
Changed
- Network preload strategy →
preload-mjs. Default'modulepreload'only emits inside the inline bootstrap script; switched to'preload-mjs'which puts<link rel="preload" as="script">in<head>. Home page went from 0 → 45 preload tags; the full chunk graph fetches in parallel with HTML parsing. Closesnetwork-dependency-tree-insight+ transitively eliminates the long-task waterfall behindmax-potential-fid. - EditorTour scroll listener → passive (
{ capture: true, passive: true }). Closesuses-passive-event-listeners. - Editor toolbar buttons — moved descriptive aria-label text to
title=so the visible button text becomes the accessible name. Voice-control users can now say "click Undo stroke" / "click Copy path" / etc. Closeslabel-content-name-mismatch. - describeAuditCode() — added the 43 missing descriptions; full 94-code coverage. New entries span brief completeness, coverage subsets, glyph count, UPM, naming, OS/2 metadata, vertical-metric mismatches, designspace orphans, kerning classes, and anchor coverage.
- /changelog — RSS 2.0 feed at
/changelog/rss.xml(autodiscovered + visible link); h2 deep-link anchors; visible Subscribe-via-RSS affordance. - /help — h2 deep-link anchors on each section; FAQPage schema.org JSON-LD for Google rich results; per-page OG + Twitter meta with
og:image=/og/brand. - /about — per-page OG + Twitter meta +
og:image=/og/brand. Version read frompackage.jsonso it can't drift. - /health — version read from
package.json(was hardcoded). - Home page footer — Help / Changelog / About / GitHub nav added.
- Sitemap — adds /help, /changelog, /about, /changelog/rss.xml.
docs/next-90-days.md+docs/phase-c-sprint-plan.md— frozen as archival snapshots.
Deferred
forced-reflow-insight— closed via profiler measurement (max 35ms layout on editor cold-load, all normal first-render territory).max-potential-fid— closed via profiler measurement (0 long tasks ≥50ms across all profiled routes).uses-rel-preconnect— false positive (Vercel auto-injected analytics, not site code).
[1.4.0] — 2026-05-24
Added
- PWA install support.
static/manifest.jsonwith name, short_name, description, start_url, scope, display: standalone, theme + background colors, 3 icons (svg + 192/512 PNG, "any maskable" purpose), categories. Linked fromapp.html. Installable on Chrome/Edge desktop and iOS home-screen "Add to Home Screen". - Favicon variants. Rasterized
static/icon-192.pngandicon-512.pngfrom the favicon.svg (light-mode H-on-#fafaf9), wired as<link rel="icon" type="image/png">andapple-touch-icon. - /help page. FAQ across six sections (getting started, sharing, export, editor, performance, broken). 17 questions answered. Static, prerendered.
Fixed
- Share page JSON-LD parsing. Escaped the inline
</script>literal in the{@html}block as<\/script>so the svelte-eslint-parser doesn't terminate the script context prematurely. Functionally identical at runtime. - Changelog
{@html}lint. Moved the eslint-disable-next-line comment to the same block as the actual{@html}paragraph render.
[1.2.0] — 2026-05-24
Added
- Multi-script starter set. 17 Cyrillic look-alike glyphs (А В Е К М Н О Р С Т Х uppercase + а е о р с х lowercase) and 14 Greek look-alike glyphs (Α Β Ε Ζ Η Ι Κ Μ Ν Ο Ρ Τ Υ Χ uppercase). All reuse Latin builders where the shape is identical in a geometric sans; bespoke Cyrillic/Greek shapes (Я Ж Ф + entire Greek lowercase) are explicit future work.
- 5 new audit codes:
color-layer-no-palette,color-layer-out-of-range,kerning-pair-self,master-empty,feature-kern-disabled-with-pairs. Audit code count: 88 → 93. - Demo
brief.differentiation+decisionsupdated to acknowledge the multi-script starter and frame the trade-off honestly.
[1.1.0] — 2026-05-24
Added
- Cloud share. New
POST /api/share+GET /api/share/[id]endpoints back by Vercel Blob; share URLs now work for recipients in any browser (not just the originator's). Link-as-capability auth — the share URL contains an unguessable UUID. Upload-on-share wired into the editor header + home-page card Share buttons; clear 503 fallback whenBLOB_READ_WRITE_TOKENisn't configured. - Per-project OG image.
/og/[id]route renders a 1200×630 PNG via satori + resvg-js. Family name in Lora serif, designer + version + glyph count below. Demo OG renders on the fly; uploaded projects render from cloud storage; brand variant at/og/homefor the home-page unfurl. - Launch copy drafts.
docs/launch/copy.mdwith Show HN, Twitter/Bluesky thread, Reddit, DM, and email templates. Anti-patterns section to avoid in launch copy.
Changed
- Share-404 page copy: "This link is browser-local" → "Project not found" (cloud sharing exists; the 404 now means the upload didn't happen or this deployment doesn't have cloud configured).
- README "Project status" bumped from
v1.0.0-betatov1.0.0with a "Configuring cloud share" section. - ROADMAP M3 section marked ✅ SHIPPED with the precise list of what landed.
[1.0.0] — 2026-05-24
Added
- v1.0.0 milestone. Production-grade.
Fixed
- Critical: app.html slot bug. The literal string
%sveltekit.body%inside an HTML comment was substituted by SvelteKit's templating, breaking the actual body slot. Every E2E test that interacted with the page failed because the JS bundle never loaded. - designspace infinite
$effectloop. The previewLocation initializer effect read AND wrotepreviewLocation. Wrapped the write inuntrack()so the effect tracks only the axes array. This was the root cause of the entire 16-test CI failure cliff. - Cmd+Shift+V paste-glyph regression. The bare-V key handler in the editor's keydown chain caught Cmd+Shift+V before the paste-glyph branch. Added
!metaKey && !ctrlKeyguards. Found by ESLint's no-dupe-else-if rule on first run. - Vercel Lighthouse false positives. Vercel's audit was running against per-deployment URLs behind Deployment Protection, scoring an auth wall page. Documented the fix in ROADMAP known-issues section.
Changed
- Welcome dialog → non-blocking strip. First-visit modal blocking the home page replaced with an inline strip at the top. Visitors see the actual product immediately.
- Microcopy sweep. "Copied path (1842 chars)" → "Copied SVG path for {name}"; "Clipboard write failed." → "Could not copy — try Cmd+Shift+C"; "[ ] navigation" tooltip spelled out.
- Pre-hydration shim.
app.htmlpaints html/body backgrounds in canvas-token colors before SvelteKit hydrates so the cold-load flash matches the eventual theme. - Share-404 detects share links and shows specific recovery copy (open demo + import
.font.json). - Brand serif consistency. All home-page section headers use Hoefler Text.
- LoadingPanel italics rule. Removed
italicentries from the type-flicker cycle per project's no-italics-in-UI rule.
Repository hygiene
- Real README replacing the SvelteKit boilerplate.
- LICENSE (MIT) added.
package.jsonmetadata: name →font-studio, description, author, license, homepage, repository, bugs, keywords.CONTRIBUTING.mdwith setup, branch model, commit conventions, PR flow.docs/architecture.md— one-page mental-model loading.ROADMAP.md— deferred M3 work + known issues.- GitHub
ISSUE_TEMPLATE(bug / feature / question) andpull_request_template.md.
Engineering
- ESLint flat config with typescript-eslint + eslint-plugin-svelte. Real-bug rules at error (caught the Cmd+Shift+V conflict on first run). Stylistic rules at warn (52-warning baseline).
- Vitest coverage (
@vitest/coverage-v8). Baseline: 77.58% statements, 79.16% lines, 80.46% functions, 64.56% branches. CI uploads as artifact. - Bundle analyzer (
rollup-plugin-visualizer).pnpm run analyzewritesbundle-report.htmlto repo root. - WAI-ARIA disclosure on Accordion (the right-sidebar collapsible sections). Header + region linked via aria-controls + aria-labelledby.
- Focus traps on all major modals (ShortcutsDialog, Settings, Storage, Welcome, CreateFont).
[0.6.0] — content-complete
13 new glyphs ($, ©, ®, ™, §, [, ], {, }, °, ±, ×, ÷), 10 .onum digits, 2 .case parens, g.ss02 alternate. Liga ligatures (fi, fl). Demo project at 119 glyphs.
[0.5.0] — finished
ß as drawn shape, fi/fl ligature GSUB, focus traps, accordion a11y, math glyphs, denser kerning.
[0.4.0] — share-complete
Foundry-style tester (master pills + size + tracking sliders), live OpenType feature toggles, reading specimens (Display / Body / Caption), glyph inspector with metric guides + sidebearings + anchors + master overlay, deep-linkable inspector, glyph grid filter, shareable tester URL, palette switcher, print specimen sheet, sample-text presets, OT features grouped, in-context mockups, coverage heatmap, anchor visualizer.
Earlier
See git history for prior 153 commits. The pre-0.4.0 commits built out the foundational editor (sketch + trace pipeline, opentype.js export, audit module, IndexedDB project store, COLR + variable font support, harfbuzz live shaping).