<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title>Data tools, automation, and custom software on Tiny Gods</title>
		<link>https://tinygods.dev/</link>
		<description>Recent content in Data tools, automation, and custom software on Tiny Gods</description>
		<generator>Hugo</generator>
		<language>en-us</language>
		
		
		
		
			<lastBuildDate>Fri, 07 Aug 2026 10:00:00 +0300</lastBuildDate>
		
			<atom:link href="https://tinygods.dev/index.xml" rel="self" type="application/rss+xml" />
			<item>
				<title>Giving a Telegram bot&#39;s photos a real social preview card</title>
				<link>https://tinygods.dev/blog/social-preview-cards-for-a-bot/</link>
				<pubDate>Fri, 07 Aug 2026 10:00:00 +0300</pubDate>
				<guid>https://tinygods.dev/blog/social-preview-cards-for-a-bot/</guid>
				<description>&lt;p&gt;Kitty Space started as a Telegram bot. People send photos of street cats, the bot saves them, and it sends a link back. The next thing everyone wanted was to share that cat on Twitter or in a chat. Paste the link, though, and no preview showed up: a bare URL, or at best a card with no image. Here is what we found, and the small server built around it.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Adding a graph interface to your app with React Flow and Dagre</title>
				<link>https://tinygods.dev/blog/graph-interface-with-react-flow/</link>
				<pubDate>Fri, 31 Jul 2026 10:00:00 +0300</pubDate>
				<guid>https://tinygods.dev/blog/graph-interface-with-react-flow/</guid>
				<description>&lt;p&gt;When an app&amp;rsquo;s data is a set of things that point at each other, a node-and-edge graph is often the clearest way to show it. We needed exactly that for &lt;a href=&#34;https://grammar.tinygods.dev&#34;&gt;Generative Grammar Engine&lt;/a&gt;, which displays a generative grammar as a graph: each rule is a node, and a &lt;code&gt;#reference#&lt;/code&gt; from one rule to another is an edge. The graph is rendered with &lt;a href=&#34;https://reactflow.dev&#34;&gt;React Flow&lt;/a&gt; and laid out with Dagre. Neither part is a novel algorithm. The work that took thought was the wiring between the data and those two libraries, so this is a write-up of the three pieces we ended up supplying ourselves, in case the same pieces come up when you add a graph view of your own.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Seven Minutes</title>
				<link>https://tinygods.dev/projects/7min/</link>
				<pubDate>Mon, 27 Jul 2026 02:00:00 +0300</pubDate>
				<guid>https://tinygods.dev/projects/7min/</guid>
				<description>&lt;h1 id=&#34;the-seven-minute-workout-in-your-order&#34;&gt;The seven-minute workout, in your order&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://7min.tinygods.dev/&#34;&gt;Open Seven Minutes&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;One button starts the session. The app keeps time and moves you through each exercise with rests in between.&lt;/p&gt;&#xA;&lt;p&gt;Change the exercises or reorder them to match what you want to train. Settings and history stay on your device, so there&amp;rsquo;s no account to create. Install it once and it works offline.&lt;/p&gt;&#xA;&lt;p&gt;Available in English and Russian.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Parsing #placeholder# templates when a regex is not enough</title>
				<link>https://tinygods.dev/blog/parsing-templates-with-escapes/</link>
				<pubDate>Fri, 24 Jul 2026 10:00:00 +0300</pubDate>
				<guid>https://tinygods.dev/blog/parsing-templates-with-escapes/</guid>
				<description>&lt;p&gt;&lt;a href=&#34;https://grammar.tinygods.dev&#34;&gt;Generative Grammar Engine&lt;/a&gt; writes rules in Tracery&amp;rsquo;s notation: &lt;code&gt;#name#&lt;/code&gt; marks a placeholder, the rest is literal text. To run a rule, the engine first splits a string like &lt;code&gt;#greeting#, #name#!&lt;/code&gt; into placeholders and literals. The obvious tool is a regular expression, and it works until someone needs a literal &lt;code&gt;#&lt;/code&gt; in their text.&lt;/p&gt;&#xA;&lt;h2 id=&#34;where-the-regex-breaks&#34;&gt;Where the regex breaks&lt;/h2&gt;&#xA;&lt;p&gt;The common pattern is &lt;code&gt;/#([^#]+)#/g&lt;/code&gt;: a hash, some non-hash characters, a closing hash. It knows nothing about escaping. Once you let &lt;code&gt;\#&lt;/code&gt; mean a literal hash, the regex cannot tell these two apart:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Counting and sampling a combinatorial space without enumerating it</title>
				<link>https://tinygods.dev/blog/counting-and-sampling-combinatorial-spaces/</link>
				<pubDate>Fri, 17 Jul 2026 10:00:00 +0300</pubDate>
				<guid>https://tinygods.dev/blog/counting-and-sampling-combinatorial-spaces/</guid>
				<description>&lt;p&gt;Plenty of features hide a space of combinations too large to write out: product configurations, generated test inputs, procedural levels, expression generators. We ran into this while building &lt;a href=&#34;https://grammar.tinygods.dev&#34;&gt;Generative Grammar Engine&lt;/a&gt;, where three questions kept coming up. None of them should need the whole space built first:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;how many possibilities exist,&lt;/li&gt;&#xA;&lt;li&gt;draw a random one,&lt;/li&gt;&#xA;&lt;li&gt;list a few distinct ones.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;A generative grammar is a compact example of such a space, and it is what the engine works with. It runs Tracery-style grammars: a rule is a list of alternatives, and &lt;code&gt;#name#&lt;/code&gt; inside an alternative pulls in another rule by name.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Measuring how much text fits before the browser renders it</title>
				<link>https://tinygods.dev/blog/measuring-text-before-render/</link>
				<pubDate>Fri, 10 Jul 2026 10:00:00 +0300</pubDate>
				<guid>https://tinygods.dev/blog/measuring-text-before-render/</guid>
				<description>&lt;p&gt;To split text into &lt;a href=&#34;https://cards.tinygods.dev&#34;&gt;Cards&lt;/a&gt; slides, the splitter has to know how much text fits on a slide before anything is drawn. It cannot render each candidate layout to the page and measure it. That would be slow, and it would fight React for control of the DOM on every keystroke. So the measurement happens off to the side, against a canvas, and the numbers feed the splitter.&lt;/p&gt;&#xA;&lt;h2 id=&#34;measuring-against-a-canvas&#34;&gt;Measuring against a canvas&lt;/h2&gt;&#xA;&lt;p&gt;The browser will measure a string without laying it out. A 2D canvas context has &lt;code&gt;measureText&lt;/code&gt;, which returns the width a string would take in a given font. One hidden canvas, reused for the life of the page, answers every query:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Splitting text into balanced carousel slides without AI</title>
				<link>https://tinygods.dev/blog/splitting-text-into-balanced-slides/</link>
				<pubDate>Fri, 03 Jul 2026 10:00:00 +0300</pubDate>
				<guid>https://tinygods.dev/blog/splitting-text-into-balanced-slides/</guid>
				<description>&lt;p&gt;&lt;a href=&#34;https://cards.tinygods.dev&#34;&gt;Cards&lt;/a&gt; takes a block of writing and turns it into a set of carousel slides. The hard question is where the breaks go. Put a fixed number of characters on each slide and you cut sentences in half, strand a single line on the last slide, and end up with slides that look unevenly full next to each other.&lt;/p&gt;&#xA;&lt;p&gt;We wanted the split to do three things: fill slides to about the same level, break on sentence or paragraph boundaries, and avoid an orphan slide holding one stray line. Those goals pull against each other, so the split is an optimization rather than a loop that fills each slide to the brim.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Why HTML-to-image export uses the wrong font, and how HarfBuzz fixes it</title>
				<link>https://tinygods.dev/blog/html-to-image-export-wrong-font/</link>
				<pubDate>Fri, 26 Jun 2026 00:00:00 +0300</pubDate>
				<guid>https://tinygods.dev/blog/html-to-image-export-wrong-font/</guid>
				<description>&lt;p&gt;&lt;a href=&#34;https://aligner.tinygods.dev&#34;&gt;Word-by-word Aligner&lt;/a&gt; lets each line use its own font, including one you upload, and then export the diagram as PNG or PDF. The export has to match the preview down to the glyph. Getting there, we ran into a problem that shows up in any HTML-to-image or SVG-to-canvas pipeline that leans on custom fonts, so it is worth writing down even if your app is nothing like this one.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-bug-exports-in-a-fallback-font&#34;&gt;The bug: exports in a fallback font&lt;/h2&gt;&#xA;&lt;p&gt;The export starts from the SVG the preview already produces. To turn that into a PNG, the standard move is to load the SVG into an &lt;code&gt;Image&lt;/code&gt; and draw it onto a canvas:&lt;/p&gt;</description>
			</item>
			<item>
				<title>Drawing connector lines between words in wrapping text</title>
				<link>https://tinygods.dev/blog/svg-connectors-over-wrapping-text/</link>
				<pubDate>Thu, 25 Jun 2026 10:00:00 +0300</pubDate>
				<guid>https://tinygods.dev/blog/svg-connectors-over-wrapping-text/</guid>
				<description>&lt;p&gt;While building &lt;a href=&#34;https://aligner.tinygods.dev&#34;&gt;Word-by-word Aligner&lt;/a&gt;, we hit a layout problem worth writing down. The tool stacks a sentence over its translation and draws curved lines from each word to its match on the line below. The text is ordinary HTML, so the browser wraps it, resizes it, and picks line breaks on its own. The connectors have to find those words and stay attached to them through all of that.&lt;/p&gt;&#xA;&lt;p&gt;The naive approach is to position the lines manually and recompute everything by hand, which means fighting the browser at every turn. The approach that worked leans on the browser for layout and adds a single measurement step on top.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Storing your whole app state in a URL, with no backend</title>
				<link>https://tinygods.dev/blog/store-app-state-in-a-url/</link>
				<pubDate>Wed, 24 Jun 2026 10:00:00 +0300</pubDate>
				<guid>https://tinygods.dev/blog/store-app-state-in-a-url/</guid>
				<description>&lt;p&gt;&lt;a href=&#34;https://aligner.tinygods.dev&#34;&gt;Word-by-word Aligner&lt;/a&gt; has a Share button and no database behind it. The link you copy carries the whole diagram: the text of every line, the links between words, the colors, the fonts, and the visual settings. Open it on another machine and the exact alignment comes back. Here is how the state fits into a URL, and how one is read back without trusting it.&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-not-raw-json&#34;&gt;Why not raw JSON&lt;/h2&gt;&#xA;&lt;p&gt;The app state is a plain object, so the first instinct is to JSON-encode it and drop it in a query parameter. That produces a long, fragile link. Every default value rides along, every key is spelled out in full, and a modest alignment turns into a wall of percent-encoded text. Three steps bring it down to size: trim the object, compress the result, and encode it for a URL.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Splitting text into word boxes when splitting on spaces is not enough</title>
				<link>https://tinygods.dev/blog/tokenizing-text-into-word-boxes/</link>
				<pubDate>Tue, 23 Jun 2026 10:00:00 +0300</pubDate>
				<guid>https://tinygods.dev/blog/tokenizing-text-into-word-boxes/</guid>
				<description>&lt;p&gt;In &lt;a href=&#34;https://aligner.tinygods.dev&#34;&gt;Word-by-word Aligner&lt;/a&gt; every word is a box you can click and link to its translation. Turning a line of text into those boxes is tokenization, and it is the step where a quick solution falls apart fast.&lt;/p&gt;&#xA;&lt;p&gt;The obvious version is &lt;code&gt;text.split(&#39; &#39;)&lt;/code&gt;. It breaks on the first real sentence. &amp;ldquo;world!&amp;rdquo; arrives with the exclamation mark glued on. &amp;ldquo;don&amp;rsquo;t&amp;rdquo; either stays whole or loses its apostrophe, depending on how you strip punctuation. A fixed phrase like &amp;ldquo;New York&amp;rdquo; splits into two boxes when it should be one. And someone working with a compound, or a script they segment differently, has no way to tell the tool what counts as a word. We hit all four of these, so the tokenizer grew into a small function with a few options rather than a one-liner.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Cards</title>
				<link>https://tinygods.dev/projects/cards/</link>
				<pubDate>Tue, 14 Apr 2026 00:00:00 +0000</pubDate>
				<guid>https://tinygods.dev/projects/cards/</guid>
				<description>&lt;p&gt;The words are usually finished long before the images are. Then comes the part that has nothing to do with writing: split the paragraphs across slides, match the type, align the padding, check that the byline on slide seven sits where it sat on slide one, export everything at a size the platform accepts. That is half an hour per post, and most of it repeats what you already did last time.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Kitty Space</title>
				<link>https://tinygods.dev/projects/kitty/</link>
				<pubDate>Mon, 13 Apr 2026 00:00:00 +0000</pubDate>
				<guid>https://tinygods.dev/projects/kitty/</guid>
				<description>&lt;p&gt;&lt;a href=&#34;https://kitty.tinygods.dev/&#34;&gt;Kitty.TinyGods.dev&lt;/a&gt; - lightweight bots to track and map street cats with community help. Part curiosity, part utility for cat lovers in cities that care about their strays.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Generative Grammar Engine</title>
				<link>https://tinygods.dev/projects/grammar/</link>
				<pubDate>Sat, 10 Jan 2026 00:00:00 +0000</pubDate>
				<guid>https://tinygods.dev/projects/grammar/</guid>
				<description>&lt;p&gt;&lt;a href=&#34;https://grammar.tinygods.dev/&#34;&gt;Grammar.TinyGods.dev&lt;/a&gt; - a visual editor for generative grammars. Define rules, see structure, and iterate on formal descriptions without fighting plain-text tooling alone.&lt;/p&gt;&#xA;&lt;p&gt;Good when you are sketching a grammar for a conlang, a course, or a paper and want the diagram to keep up with your thinking.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Word-by-word Aligner</title>
				<link>https://tinygods.dev/projects/aligner/</link>
				<pubDate>Sat, 10 Jan 2026 00:00:00 +0000</pubDate>
				<guid>https://tinygods.dev/projects/aligner/</guid>
				<description>&lt;p&gt;A word-by-word line printed under the original breaks on the first sentence that is not trivial. While the translation runs word for word, the columns line up. Change the word order and they drift apart. One source word turns into three, three words collapse into a single morpheme, and the table starts lying: the reader sees pairs that were never there.&lt;/p&gt;&#xA;&lt;p&gt;Word Aligner answers that with lines. Rows sit one under another, and the correspondences are drawn as connectors that cross, fan out from one word to several, and converge back. You see the structure, not only the meaning. Linguists call this word alignment, or bitext alignment; teachers and learners usually reach for a different phrase: so that is where that word came from.&lt;/p&gt;</description>
			</item>
			<item>
				<title>PurrMacka Shelter Ops</title>
				<link>https://tinygods.dev/projects/purrmacka-shelter-ops/</link>
				<pubDate>Mon, 25 Aug 2025 00:00:00 +0000</pubDate>
				<guid>https://tinygods.dev/projects/purrmacka-shelter-ops/</guid>
				<description>&lt;p&gt;Client: &lt;a href=&#34;https://purrmacka.org/&#34;&gt;PurrMacka&lt;/a&gt; - a charity project and cat coworking space in Belgrade helping homeless cats.&lt;/p&gt;&#xA;&lt;h2 id=&#34;situation&#34;&gt;Situation&lt;/h2&gt;&#xA;&lt;p&gt;Staff and volunteers come to the shelter twice a day. During every shift they need to record the animals&amp;rsquo; condition: food, health, behavior, medical notes, household tasks, supplies, and what needs to be done next. Before the project, all of this lived in ordinary Telegram chat messages.&lt;/p&gt;&#xA;&lt;p&gt;That setup quickly became hard to manage. Data was unstructured, important details disappeared in the message stream, admins could not see the overall state of the shelter, and workers had no reliable way to hand tasks over between shifts.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Apinu Atelier Production</title>
				<link>https://tinygods.dev/projects/apinu-atelier-production/</link>
				<pubDate>Fri, 20 Jun 2025 00:00:00 +0000</pubDate>
				<guid>https://tinygods.dev/projects/apinu-atelier-production/</guid>
				<description>&lt;p&gt;Apinu Atelier produces small batches of craft decorative goods: candles, painted jars, soap, and other handmade items.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Disclaimer: Apinu Atelier is affiliated with Tiny Gods studio, but it is not a division of the studio.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;h2 id=&#34;task&#34;&gt;Task&lt;/h2&gt;&#xA;&lt;p&gt;The team needed a system for controlling production stages: what is currently in progress, how much time each stage takes, which tasks have been completed, what needs to be planned next, and where delays appear.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Design section prototypes</title>
				<link>https://tinygods.dev/design-sections/</link>
				<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
				<guid>https://tinygods.dev/design-sections/</guid>
				<description>&lt;p&gt;These blocks use a layout-only &lt;code&gt;tg-section&lt;/code&gt; with the copy and &lt;strong&gt;child&lt;/strong&gt; shortcodes (&lt;code&gt;tg-card&lt;/code&gt;, &lt;code&gt;tg-step&lt;/code&gt;, &lt;code&gt;tg-spark&lt;/code&gt;, &lt;code&gt;tg-connector&lt;/code&gt;, &lt;code&gt;tg-flow-step&lt;/code&gt;, &lt;code&gt;tg-pillar&lt;/code&gt;, optionally &lt;strong&gt;&lt;code&gt;content-card&lt;/code&gt;&lt;/strong&gt; inside &lt;code&gt;split-teasers&lt;/code&gt;, and for &lt;strong&gt;&lt;code&gt;info-split&lt;/code&gt;&lt;/strong&gt; - &lt;strong&gt;&lt;code&gt;tg-info-left&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;tg-info-card&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;tg-info-list&lt;/code&gt;&lt;/strong&gt;) in the same file, so you can translate per language and reuse a layout with different content.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Use &lt;strong&gt;&lt;code&gt;%BR%&lt;/code&gt; in the &lt;code&gt;title&lt;/code&gt; or &lt;code&gt;lead&lt;/code&gt; params&lt;/strong&gt; where you need a line break (raw &lt;code&gt;&amp;lt;br&amp;gt;&lt;/code&gt; in attributes breaks the shortcode parser).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Self-closing&lt;/strong&gt; child shortcodes: space around the slash, then close - &lt;code&gt; /&lt;/code&gt; then &lt;code&gt; &amp;gt;}}&lt;/code&gt; on the same line as the tag.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;services-line--bglight&#34;&gt;&lt;code&gt;services-line&lt;/code&gt; / &lt;code&gt;bg=&amp;quot;light&amp;quot;&lt;/code&gt;&lt;/h2&gt;&#xA;&lt;section class=&#34;tg-section tg-section--services-line tg-section--light&#34;&gt;&#xA;  &lt;div class=&#34;tg-section__head tg-section__head--split&#34;&gt;&#xA;    &#xA;      &lt;p class=&#34;tg-section__eyebrow&#34;&gt;&#xA;        &lt;span class=&#34;tg-section__dot&#34; aria-hidden=&#34;true&#34;&gt;&lt;/span&gt;&#xA;        &lt;span&gt;Services&lt;/span&gt;&#xA;      &lt;/p&gt;</description>
			</item>
			<item>
				<title>Privacy policy</title>
				<link>https://tinygods.dev/privacy/</link>
				<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
				<guid>https://tinygods.dev/privacy/</guid>
				<description>&lt;h1 id=&#34;privacy-policy&#34;&gt;Privacy policy&lt;/h1&gt;&#xA;&lt;p&gt;Last updated: April 26, 2026&lt;/p&gt;&#xA;&lt;p&gt;Tiny Gods respects your privacy. This policy explains what personal data we may collect when you use tinygods.dev, contact us, or send a project request, and how we use that data.&lt;/p&gt;&#xA;&lt;h2 id=&#34;who-we-are&#34;&gt;Who we are&lt;/h2&gt;&#xA;&lt;p&gt;Tiny Gods is a small development and automation team. For privacy questions, write to us at &lt;a href=&#34;mailto:tinygodsdev@gmail.com&#34;&gt;tinygodsdev@gmail.com&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-we-collect&#34;&gt;What we collect&lt;/h2&gt;&#xA;&lt;p&gt;We collect only the information we need to run the website, answer requests, and work with clients.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Programmers are tiny gods</title>
				<link>https://tinygods.dev/about/</link>
				<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
				<guid>https://tinygods.dev/about/</guid>
				<description></description>
			</item>
	</channel>
</rss>
