尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证Cover letters that look designed
翻译准备中
取自仓库 README
正在加载完整 README。 以下为已保存的摘录。
A cover letter builder that does one thing.
Most tools I tried were either a word processor with no opinion about design, or a template gallery
with a hundred choices and no way to tell which ones were any good. Letterly picks a middle path:
you write in blocks, you pick from enumerated design tokens, and you can't accidentally make it
ugly. The whole thing runs in your browser. No account, no backend, no network.
It's not finished. The known limitations are at the bottom and they're real ones, not humblebrags.
Try it at getletterly.vercel.app.查看完整 README ↗A cover letter builder that does one thing.
Most tools I tried were either a word processor with no opinion about design, or a template gallery with a hundred choices and no way to tell which ones were any good. Letterly picks a middle path: you write in blocks, you pick from enumerated design tokens, and you can't accidentally make it ugly. The whole thing runs in your browser. No account, no backend, no network.
It's not finished. The known limitations are at the bottom and they're real ones, not humblebrags.
Try it at getletterly.vercel.app.
Pick a template, write in blocks, drag them around, tune the design, export a PDF. The preview is a real A4 page at real size — what you see is what the PDF gives you.
The PDF is the part I care most about. It's a real vector PDF: selectable text, embedded fonts,
clickable links. There is no screenshot path anywhere in the codebase, and no html2canvas. If you
open the export in a PDF reader and hit Ctrl+F, it finds your words.
| Template | What it is |
|---|---|
minimal | A classic business letter. One typeface, wide margins, quiet. |
modern | Split letterhead, rules banding the page, results as display numbers. |
creative | Editorial. Oversized serif name, tracked role line, centred pull quote. |
brief | A card grid. Centred section headings over left-aligned bulleted cards. |
profile | Two columns with a tinted sidebar for skills and results. |
timeline | A vertical spine running down the achievements. |
Switching template keeps everything you wrote. It also keeps any design choice you actually changed, and adopts the new template's default for everything you didn't touch.
You never type a colour. You pick from palettes, font pairs and spacing steps, and the panel tells you the contrast ratio of your text against the page. That's the whole design principle: make good design difficult to mess up.
The app follows your system theme, or you can force it. The letter itself never changes — it stays white paper in both, because that's what comes out of the printer.
You need Node and npm.
npm install
npm run fonts # downloads 19 TTF faces into public/fonts
npm run dev # http://localhost:3000
One rule that will save you a debugging session: run npm run fonts, don't hand-roll it. The old
Google Fonts /css?family= endpoint returns EOT files to some user agents. fontkit rejects those
with Unknown font format and every single PDF export dies. The script uses the css2 endpoint and
checks the sfnt magic number of every file before writing it — including files already on disk, so a
poisoned cache can't survive.
Production build:
npm run build && npm start
One document schema, one style resolver, two renderers.
LetterDocument (plain JSON)
|
style resolver src/lib/render/resolve.ts
(theme + template + block -> tokens, in PDF points)
|
+--------------+--------------+
v v
DOM renderer PDF renderer
src/components/blocks src/lib/pdf
(x PT_TO_PX, inline styles) (raw pt, react-pdf)
| |
v v
the A4 preview Firstname_Lastname_Cover_Letter.pdf
Both renderers are deliberately stupid. They own structure and nothing else — every colour, size, weight and spacing comes from the resolver. Neither file contains a literal hex value or font size, and that's enforced rather than trusted:
node scripts/check-gate.mjs
The resolver emits PDF points. react-pdf treats bare numbers as points, so the PDF side uses them
raw; the DOM side multiplies by 96/72. Every length goes through round2(). That rounding is the
thing that stops the two outputs drifting apart.
A template is data, not components (src/lib/templates/). There's one component per block type,
shared by all six. if (templateId === 'creative') inside a renderer is a bug.
| Path | What's in it |
|---|---|
src/lib/schema/ | LetterDocument, the block union, defaults, and 27 rules that repair anything read from storage |
src/lib/render/ | The resolver. Pure — no React, no DOM, no react-pdf |
src/lib/templates/ | The six templates, as data |
src/lib/pdf/ | PDF renderer, the pre-export checks, font registration |
src/components/blocks/ | DOM renderer. Store-free, pure props |
src/components/editor/ | Shell, canvas, block rail, inspector |
src/lib/store/ | zustand store, history, selectors |
src/lib/storage/ | localStorage repository |
Two token systems that never mix: the app chrome is Tailwind CSS variables, the document theme is
data inside document.theme applied as inline styles. A cover letter never inherits app chrome —
the page root carries .letter-canvas to enforce it, which is also why the letter stays white paper
in dark mode.
The suites are Playwright scripts. Start a production build, then:
node scripts/acceptance.mjs # the whole journey, in a real browser
node scripts/test-dnd-design.mjs # drag-and-drop and every design control
node scripts/qa-structure.mjs # block cap, deletes, rapid-fire storms
node scripts/qa-state.mjs # corrupted localStorage, quota, undo bounds
node scripts/qa-ui.mjs # breakpoints, overflow, focus rings, dead buttons
node scripts/qa-content.mjs # 15k-char pastes, unicode, XSS as literal text
node scripts/qa-theme.mjs # the landing theme stays on the landing page
node scripts/verify-parity.mjs # every word in the editor reaches the PDF
These need no server at all — they drive the module graph directly:
node scripts/check-gate.mjs # renderer purity
node scripts/verify-resolver.mjs # the resolved-style contract
node scripts/render-pdf.mjs brief out.pdf # render any template, no browser
node scripts/verify-pdf.mjs out.pdf # prove it's vector, not a picture
node scripts/pdf-geometry.mjs out.pdf --rects # where everything actually landed
pdf-geometry.mjs exists for a specific reason. verify-parity.mjs compares text, not geometry,
so the two renderers can drift several points apart per item with every word still present in both.
That happened. It took measuring filled rectangles out of the PDF content stream to catch it.
Real ones.
/dev/pdf-smoke ships in the build. It's inert and only reachable by typing the URL, but it
should come out before this is ever public.The bundled typefaces come from Google Fonts under the SIL Open Font License: Inter, DM Sans, Source Sans 3, Playfair Display and Space Grotesk.