尚未偵測到更新。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.