<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Grzegorz Dziadkiewicz</title>
    <description>The latest articles on DEV Community by Grzegorz Dziadkiewicz (@gdziadkiewicz).</description>
    <link>https://dev.to/gdziadkiewicz</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F290139%2F5ae376e2-617b-4e9c-9c5a-18d1c52ab042.jpeg</url>
      <title>DEV Community: Grzegorz Dziadkiewicz</title>
      <link>https://dev.to/gdziadkiewicz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gdziadkiewicz"/>
    <language>en</language>
    <item>
      <title>A Polish handwriting companion for my son, powered by local AI</title>
      <dc:creator>Grzegorz Dziadkiewicz</dc:creator>
      <pubDate>Sun, 04 Oct 2026 23:02:24 +0000</pubDate>
      <link>https://dev.to/gdziadkiewicz/a-polish-handwriting-companion-for-my-son-powered-by-local-ai-4c3m</link>
      <guid>https://dev.to/gdziadkiewicz/a-polish-handwriting-companion-for-my-son-powered-by-local-ai-4c3m</guid>
      <description>&lt;p&gt;Built for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;My son is eight. He reads and types in Polish, practises joined-up handwriting on paper, and has a Windows PC and a printer. I wanted to help him practise the movement behind a letter: where the pencil starts, which way it travels, and how one letter connects to the next.&lt;/p&gt;

&lt;p&gt;I also wanted his practice tool to work without internet during use. A Polish question should not require a cloud account or a connection to a hosted chatbot.&lt;/p&gt;

&lt;p&gt;That led to &lt;strong&gt;Literkowy warsztat&lt;/strong&gt;, roughly “Letter Workshop”: a small Windows application with a Polish interface, animated movement demonstrations, printable A4 exercises, and a local AI assistant that interprets handwriting questions.&lt;/p&gt;

&lt;p&gt;The first pack contains &lt;strong&gt;a, o, m, n, l, e, ą, ma, la, and mama&lt;/strong&gt;. It is deliberately small enough to inspect. The child can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choose a letter or connected form and replay its movement.&lt;/li&gt;
&lt;li&gt;Read the corresponding Polish instructions.&lt;/li&gt;
&lt;li&gt;Print tracing, copying, and independent-practice rows.&lt;/li&gt;
&lt;li&gt;Type a question or ask for an easier exercise.&lt;/li&gt;
&lt;li&gt;Load a photo, rotate or crop it, compare it with the example, and choose what to practise next.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We did not have a worksheet from his school. The movement paths and instructions are therefore an original practice model informed by public references, including &lt;a href="https://primarium.info/countries/poland/" rel="noopener noreferrer"&gt;Primarium’s overview of Polish handwriting&lt;/a&gt;. They have been technically checked and visually inspected, but have not been approved by a teacher or verified against his school’s exact model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://gdziadkiewicz.github.io/literkowy_warsztat/" rel="noopener noreferrer"&gt;Open the interactive GitHub Pages demo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;No installation or video is needed to explore the demonstration:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Choose &lt;strong&gt;a&lt;/strong&gt; and replay its movement.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Show recorded Qwen example&lt;/strong&gt; to see an actual response captured from the local Windows app with &lt;strong&gt;m&lt;/strong&gt; selected.&lt;/li&gt;
&lt;li&gt;Prepare an A4 worksheet and print it or save it as PDF.&lt;/li&gt;
&lt;li&gt;Load a non-personal photo, try rotation/cropping, and select a guided observation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The public website is a static demonstration.&lt;/strong&gt; It uses authored hints and one clearly labelled recorded local-model response. It does not run Qwen live. The downloadable Windows application runs Qwen locally after parent setup.&lt;/p&gt;

&lt;p&gt;The recorded model-backed prompt is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Ołówek zatrzymuje mi się przy drugim garbku.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That means, approximately, “My pencil gets stuck at the second hump.” With &lt;strong&gt;m&lt;/strong&gt; selected, the local model interpreted the request as an explanation of its movement steps. The application then displayed the authored Polish instructions for that card.&lt;/p&gt;

&lt;p&gt;Photo feedback is guided by the child’s or parent’s observation. The image remains in browser memory and is not sent to the model or uploaded. A finished photo cannot reveal stroke direction, stroke order, pencil pressure, or grip. Public-demo worksheet metadata stays in this browser’s local storage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/gdziadkiewicz/literkowy_warsztat" rel="noopener noreferrer"&gt;Source repository: gdziadkiewicz/literkowy_warsztat&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The repository includes Windows setup/start scripts, the original movement pack, evaluation commands, source notices, and a sample A4 worksheet. Our application code and original content use the MIT license. Model weights and runtime binaries are downloaded separately and are not committed.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;The application is a small Python/FastAPI service with plain HTML, CSS, JavaScript, and SVG. It runs on loopback, so the browser interface and application service stay on the same computer.&lt;/p&gt;

&lt;p&gt;The movement pack is shared between the animation and printed worksheet. That matters: the starting point, sequence, and route shown on screen should agree with the paper exercise. Generating a plausible-looking letter image would not give me that consistency.&lt;/p&gt;

&lt;p&gt;For language interpretation, the app uses &lt;strong&gt;Qwen3 4B through Ollama&lt;/strong&gt;, running locally. The model selects an intent, a supported card, a topic, and a difficulty level. The application validates those choices before rendering the corresponding authored instructions.&lt;/p&gt;

&lt;p&gt;This is the actual response contract from the application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ModelDecision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StrictModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;explain&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;practice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;easier&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unsupported&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;clarify&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;break&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;card_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CardId&lt;/span&gt;
    &lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;steps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;direction&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;spacing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;joins&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;marks&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;easy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;regular&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A preliminary direct-classification probe misrouted several questions. That changed the design: common explicit requests use predictable routes to the content pack, while less explicit wording can reach the local model. The model chooses from the allowed catalog; it does not invent new letter shapes or rewrite stroke directions.&lt;/p&gt;

&lt;p&gt;The runtime configuration also stays explicit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application: 127.0.0.1:8765
Ollama:      127.0.0.1:11435
Model:       qwen3:4b
Cloud mode:  disabled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initial parent setup needs internet to download dependencies, Ollama, and the model. Ordinary use is designed to stay local. Worksheet history and selected observations are stored in local SQLite; raw questions and photo files are not stored in that history.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I checked
&lt;/h3&gt;

&lt;p&gt;The eight authored Polish evaluation cases passed: &lt;strong&gt;seven used reviewed routes, and one exercised Qwen3 locally&lt;/strong&gt;. The model-backed case took about &lt;strong&gt;4.2 seconds&lt;/strong&gt; in the final recorded run; an earlier warm-model run took about &lt;strong&gt;0.8 seconds&lt;/strong&gt;. That is a small smoke set, not an independent benchmark or proof that all Polish phrasing works.&lt;/p&gt;

&lt;p&gt;A browser smoke check generated the sample A4 PDF and exercised synthetic photo upload, rotation, and cropping without reported JavaScript errors.&lt;/p&gt;

&lt;p&gt;A deliberate whole-PC run with internet disconnected, physical print review, and parent/teacher content review remain outstanding. My son has not yet provided feedback, so I cannot claim improved handwriting or a successful child trial.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Running an open-weight model locally makes it possible to interpret Polish questions without sending them to a hosted AI provider. The family can inspect the application’s behavior, replace the model, and extend the movement pack.&lt;/p&gt;

&lt;p&gt;There are practical costs: the model download is substantial, first-response latency varies, and small-model interpretation needs testing. If the model is unavailable, movement demonstrations and worksheets remain usable.&lt;/p&gt;

&lt;p&gt;The most useful design decision was keeping flexible language interpretation connected to a finite, inspectable set of movements and instructions. That gives the assistant room to understand wording while preserving the content the child is meant to practise.&lt;/p&gt;

&lt;h3&gt;
  
  
  Community Wisdom
&lt;/h3&gt;

&lt;p&gt;Two community reports helped frame the approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/familia_sync/teaching-our-on-device-assistant-to-say-i-dont-know-66m"&gt;FamiliaSync’s “Teaching our on-device assistant to say ‘I don’t know’”&lt;/a&gt; describes real intent misrouting and the need to handle unsupported requests. It reinforced the importance of checking actual phrasing and providing constrained fallbacks.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/cfournel/memory-not-speed-is-the-hard-part-of-running-an-llm-on-a-phone-53mk"&gt;Charles-Antoine Fournel’s report on local-model memory costs&lt;/a&gt; discusses memory and download tradeoffs on Android. Those device-specific results do not establish Windows performance, but they reinforced the need to measure the actual installation and keep the model’s job small.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The next useful step is a parent-supervised paper session: inspect the letter forms, check the physical print, and see whether the instructions make sense to the person this was built for.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AI assistance disclosure: Codex helped implement the application and draft this post. The reported checks are implementation smoke checks; no recipient feedback has been invented.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
    </item>
  </channel>
</rss>
