Install
openclaw skills install @monikazapisek/microtypographyPolish body text before publishing — Polish or English — by fixing hanging single-letter conjunctions/prepositions, adding orphan guards, flagging widow risks, and handling ragged-edge risks without rewriting copy.
openclaw skills install @monikazapisek/microtypographyClean up a block of text at the sentence/paragraph level so it reads well when typeset — no single-letter words stranded at line end, no single-word "orphan" lines left dangling, no unreported widow risk at a column/page break, and no unmanaged ragged-edge risk.
hangingPunctuation switch for Figma display headings (see Rule 4).This skill is an audit, not an interview. Work in this order: read what the file or the input already says, report, then ask.
Questions this skill may need:
Insert a non-breaking space between a single-letter (or otherwise "small") conjunction/preposition and the word that follows it, so it can never be the last character on a line.
Polish — single-letter words that must never end a line: i, a, o, u, w, z, k (and their capitalized sentence-start forms). Also treat short two-letter forms as strong candidates when local style guides require it: by, że, aż, iż, ze, we, do, na, od, po, ku — apply these only if the project's style guide asks for it; the single-letter rule is non-negotiable, the two-letter rule is a style choice.
English — single-letter words: a, I. English typesetting is generally more lenient than Polish (no hard rule against "a" or "I" ending a line), so treat this as optional polish, not a hard fix, unless the user asks for strict typesetting.
Headings are stricter than running text. PWN's spelling rules (§54.8.1) draw the line: in narrow columns, one-letter conjunctions and prepositions may stay at the end of a line in running text, but in titles — of books, chapters, articles and the like — they must always move to the next line. For an interface this means:
Short word after a comma (opt-in, house rule). A word of up to three letters that follows a comma, semicolon or colon and opens a clause — , ale, , że, , bo, , to, , czy — is bound to the next word with NBSP, so it doesn't hang at the end of a line right after the punctuation. Off by default; apply it only when the user or the project's style guide asks. Published PWN guidance cited below covers one-letter words and says requirements for longer words are milder; it does not establish this three-letter, post-punctuation threshold. The threshold and punctuation condition are this skillset's own editorial heuristic, not a published rule. Polszczyzna na co dzień (PWN 2006, pp. 527–528) was suggested as a possible lead but was not available for verification, so it is not cited as support.
Mechanics per format:
(non-breaking space, renders as visually but blocks the line break). In markdown source this is the literal NBSP character, not an HTML entity, unless the target is HTML. between the word and what follows.<number> x <number>, e.g. 1920x1080, 3 x 4 m), replace the x/X/* with the proper multiplication sign ×. Don't touch a bare x that isn't between two numbers (variable names, "x-axis", "Grade x").100 PLN, 50 m, 16 px, 3 kg. Use the character-selection and locale rules in Rule 1d; currency symbols do not all take the same placement or spacing. Spelled-out words ("100 dolarów") follow ordinary language rules.1920x1080 in a config file are not prose).Dashes — three different characters, three different spacing rules. Don't collapse them into one "make it fancy" transform:
— (parenthetical break, US convention per The Chicago Manual of Style): no surrounding spaces — word—word. Replace a double hyphen -- used this way with a bare em dash, not a spaced one.– as a sentence-level parenthetical break (PL convention, and Bringhurst's recommendation for English, §5.2.1): surrounded by spaces — word – word — and those spaces should be NBSP if the dash sits near a line-wrap-risky position (short word before/after), otherwise a regular space is fine.– in a numeric/date range (10-15kg, 2020-2023): no spaces at all — 10–15 kg, 2020–2023. This is a different use of the same character from the sentence-dash case above; don't apply spacing rules meant for prose to a range. Add the NBSP-before-unit fix from Rule 1b where a unit follows (10–15 kg).-: never surrounded by spaces, in any context (compound words, prefixes). Don't touch hyphens that aren't standing in for an em/en dash — most hyphens in normal text are correct as-is.Quotation marks: detect the paragraph's language and convert straight quotes "..." to the typographically correct pair: Polish „...”, English “...”. Only convert quotes that wrap actual quoted/spoken text — leave straight quotes inside code, JSON, or attribute values untouched.
Both are opt-in-by-default in code-adjacent contexts (README snippets, inline code) — never touch quotes or dashes inside a code span, fenced block, or URL.
Design-tool context: if the dash/range sits inside a Figma text node (not markdown/HTML prose), the character choice above still applies, but kerning and per-range tracking around the dash are handled by text-typesetting Step 6, not here — this skill decides which character and spacing, that skill decides how it renders.
Unicode spaces encode different behaviour. Do not replace an ordinary or non-breaking space with a narrower character merely because it looks better in one font.
| Character | Use | Do not use for |
|---|---|---|
Narrow no-break space U+202F | Human-facing number–unit and number–currency-code pairs (16 px, 100 PLN), and optional groups of three digits in long numbers (12 345 678) when the font and target platform render it correctly | Currency-symbol placement that is controlled by locale ($10, 10 zł), code, data files, or identifiers |
Figure space U+2007 | A deliberate blank digit position in tabular numeric display; it has the width of a tabular digit | General word spacing or the default thousands separator; its full-digit width is usually too large |
Thin space U+2009 | Manual optical separation where a line break remains acceptable, such as nested quotation marks or a local punctuation collision | Any relationship that must stay on one line; use a no-break character instead |
Hair space U+200A | Exceptional manual optical correction at display sizes, after visual inspection | Automatic cleanup, ordinary prose, number–unit binding, or thousands grouping |
Non-breaking hyphen U+2011 | A hyphenated proper name, model, identifier, or short compound whose parts must not split across lines | Every hyphenated word; normal words should retain their usual line-break opportunities |
Rules and fallbacks:
U+202F for that digital typesetting role, with U+00A0 as the compatibility fallback.U+202F, U+2007, U+2009, U+200A, or U+2011, verify that the target font contains the glyph/advance and that the target renderer preserves the intended break behaviour. If that cannot be verified, keep ordinary spacing or use U+00A0; do not create tofu or an invisible portability bug for a smaller gap.U+00A0 or U+202F where a line break must be prohibited.Terms. The English and Polish names don't map one to one, so say which one you mean. This skill uses the English terms as defined below. In Polish typesetting: wdowa is a very short last line of a paragraph (what this rule calls an orphan), bękart is the last line of a paragraph at the top of a new column or page (what this rule calls a widow), szewc is the first line of a paragraph left at the bottom of a column, and sierotka is a short word left at the end of a line (Rule 1). In an interface there are no page breaks, so bękart and szewc appear only in multi-column or paginated layouts; the case that matters is the single word on the last line.
Split by whether the text has hard line breaks or reflows.
Case A — hard-wrapped text (terminal output, poetry, plain-text captions, README manually wrapped to N columns): this is fixable directly only when the hard line breaks are part of body prose, not code or tables. Recompute the wrap with a best-fit / minimal-raggedness algorithm (minimize the variance of line-end positions across the paragraph — a lightweight Knuth-Plass-style pass, not naive greedy wrap, since greedy wrap maximizes rag). Apply the fix; note the target column width used in the change log.
Quantified rag threshold (applies to both cases, but especially narrow containers — mobile, UI cards): measure the longest and shortest line in the paragraph. If (longest − shortest) / container_width > 20%, the rag is bad enough to act on, not just cosmetically uneven — this is the trigger point, not a vague "looks ragged" judgment call. Below 20%, leave it; natural word-length variation produces some rag and over-correcting flattens it artificially.
Case B — reflowing text (normal markdown/HTML rendered by a browser/CMS): the source doesn't control where lines actually break, so there's nothing to rewrap. Two things are still legitimate to do here, both non-destructive to wording:
­ in HTML, soft-hyphen char in markdown/plain text) on long, unbreakable words (long Polish compounds, CamelCase, long tokens) so if the renderer needs to break there, it hyphenates cleanly instead of overflowing or forcing an ugly gap. Insert at defensible syllable or morpheme boundaries only. If you cannot identify safe break points, do not guess; flag the word for manual review instead.hyphens: auto; and text-wrap: pretty; on body copy, text-wrap: balance; on headings. This is the correct fix location for rag in reflowing text — flag it as a suggested CSS change alongside the text output.Do not reword sentences to control line length in Case B — that changes content, not typesetting, and violates the "don't alter wording" rule. If reflow genuinely looks bad only after rewording could fix it, offer it as an explicit opt-in suggestion the user must accept separately, never auto-applied.
A punctuation mark sitting at the very start or end of a line of text (an opening quote, dash, bullet, or parenthesis) reads as breaking the column's optical edge, even though it's technically inside the margin — the eye aligns to the letterforms, not the punctuation. Hanging punctuation pushes that mark slightly outside the block so the letters line up cleanly.
hanging-punctuation: first last; on the paragraph/block — this is the correct, native fix and needs no text-level change. Report it as a suggestion alongside other CSS recommendations (Rule 3 Case B), don't silently add it to a stylesheet.textNode.hangingPunctuation. For a display heading (≥24px) that opens with ", “, „, «, or (, report it and, on request, set hangingPunctuation = true — a live Inter probe confirmed left overhang for those marks. The same probe did not move an opening em dash (—), so don't promise that the native switch will hang it. See Figma Node Integration. No indent value needs computing.hanging-punctuation: first last; for reflowing HTML/CSS output; in Figma, flag eligible display headings and set hangingPunctuation on request.When running with Figma access, work on the selected text nodes instead of asking the user to paste text.
textAutoResize is "HEIGHT", "NONE" or "TRUNCATE" and the content runs to more than one line. Skip them on single-line labels and on auto-width nodes ("WIDTH_AND_HEIGHT"), which never wrap.textNode.characters, textAutoResize, fontSize, textWrapStyle, hangingPunctuation. A non-breaking space in characters is the literal character U+00A0; a soft hyphen is U+00AD. HTML entities mean nothing here.U+202F, U+2007, U+2009, U+200A, U+2011), never an HTML entity. Treat every insertion as a character edit and preserve range styles with the same end-to-start deleteCharacters / insertCharacters procedure. If the font or screenshot shows tofu, a missing advance, or unexpected width, revert to U+00A0 or the original character and report the compatibility fallback.boundVariables.characters) or by a component property (componentPropertyReferences.characters). The fix belongs in the variable value or the component property, not in the layer.textNode.characters = ... on a node with mixed styles (a bold word, a link) flattens it to one style. Replace character by character instead: deleteCharacters(start, end) then insertCharacters(start, text, "BEFORE"), working from the end of the string to the start so earlier indices stay valid. Load every font the node uses first (getStyledTextSegments(["fontName"]), then figma.loadFontAsync for each).textWrapStyle = "BALANCE" — the native counterpart of text-wrap: balance. Don't insert soft hyphens unless the user asks: they stay in the copy when it is handed off. The 20% rag threshold can't be read from the node, because the Plugin API doesn't expose line boxes; judge it from a screenshot of the node, or say that it wasn't measured.", “, „, «, or (, offer hangingPunctuation = true. A live Inter probe confirmed those marks and found that — does not move. This is a native text property; no indent needs computing.text-typesetting Step 6.i, a, o, u, w, z, k) ends a line — verify by checking each occurrence got an NBSP, not just a regex pass.a/I handled per requested strictness (default: leave as-is unless asked for strict mode).x correctly converted to × only between two numbers, never touching variable/axis names.hangingPunctuation set only on request.deleteCharacters/insertCharacters, never by reassigning characters on a mixed-style node; variable- and property-driven text reported, not edited.microtype package documentation — the term of art microtypography: character/word-level spacing and hyphenation control, as distinct from macrotypography (grid, columns, page layout).hanging-punctuation property (first, last, force-end values) — native browser implementation of the same principle.TextNode.characters, TextNode.insertCharacters, TextNode.deleteCharacters, TextNode.textWrapStyle, TextNode.hangingPunctuation.U+2007 figure space has tabular digit width; U+2009 thin space and U+200A hair space are progressively narrow spaces; U+00A0 is the non-breaking counterpart of the ordinary space.U+00A0, U+202F, U+2007, and U+2011.text-typesetting — Step 6 handles kerning and local tracking around dashes in Figma; Step 7 reaches the same hanging-punctuation switch from the type-style side.