This is a submission for the Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady
The Problem (a personal one)
I study systems design. I write long explanations of concepts โ cache invalidation, message queues, consensus. Then the actual work starts, and it's not thinking, it's transcription:
- The blog post wants one diagram.
- The Instagram carousel wants the same diagram, redrawn, in 1080ร1350.
- The X thread wants it again in 1200ร675, plus 4 posts under 280 weighted characters.
Same idea, three diagrams, three registers, three canvases. Every single time. That's not learning or teaching โ it's copy-paste with extra steps. So I automated the extra steps.
What I Built
FlowToFeed โ a content studio that does the boring 80%:
- Paste an explanation once (Markdown, or import a .md file).
- Highlight a passage โ get an editable diagram. Architecture, flowchart, or sequence โ generated as text (Mermaid), not baked pixels. Colors and edge labels encode the flows: cache-hit path in crimson, cache-miss in teal. You can edit the source and re-render.
- The same passage becomes three genuinely different lessons โ an Instagram carousel (caption + slides), a Facebook explainer, an X thread. Not one text cut into three: each channel gets its own structure, and every X post is counted with the real twitter-text weighted-character algorithm enforced server-side.
- Approve โ export โ hosted images. Channel-sized cards (1080ร1350 slides, 1200ร1200, 1200ร675) render in your browser, upload to Supabase Storage, and come back as public URLs ready for Buffer.
- Nothing ships silently. Every generated artifact remembers which revision of your source it came from. Edit the source and stale outputs get flagged. "Approved" is a state you set.
The human keeps judgment; the machine keeps the copy-paste.
The flow, in four screens
1 โ Paste the explanation once. Markdown in, live-rendered preview beside it. (This is a DNS-resolution walkthrough โ a concept with a genuinely branching mechanism.)
2 โ Highlight the passage that matters. One section of one explanation โ not the whole document. Pick the mode (architecture / flowchart / sequence), optionally add an instruction like "show the read path only", and generate.
3 โ A real diagram lands. Twelve components, every edge labeled and color-coded by flow โ cache checks in crimson, query paths in teal โ with the mechanism summarized underneath. Editable, exportable to SVG/PNG, and one bounded AI Repair if it ever fails to render.
4 โ And it's text, not pixels. The same diagram as editable Mermaid source. Change a label, re-render. Diff it, commit it, open it in any Mermaid tool. FlowToFeed can disappear; this survives.
- Live app: https://flowtofeed-web.onrender.com
- API health: https://flowtofeed-api.onrender.com/api/health
Code
FlowToFeed
Turn technical Markdown into editable Mermaid diagrams and channel-specific visual lessons, ready to review and share.
FlowToFeed is a single-owner content studio: paste a technical explanation once, generate architecture/flowchart/sequence diagrams, adapt the explanation into Instagram carousel lessons, Facebook cards, and X threads, then review and publish everything through Buffer.
This is the source code for a Hacktoberfest Open-Source AI Challenge Week 3 submission.
Architecture
flowchart TD
UI["React interface and visual renderer"] --> API["Express API"]
API --> DATA["Supabase Auth and database"]
API --> MEDIA["Supabase Storage"]
API --> N8N["Protected n8n webhooks"]
N8N --> GEMMA["Hosted Gemma"]
N8N --> BUFFER["Buffer API"]
BUFFER --> SOCIAL["Instagram, Facebook, X"]
N8N --> CALLBACK["Authenticated job callbacks"]
CALLBACK --> API
| Component | Technology | Responsibility |
|---|---|---|
| Frontend | React, TypeScript, Vite | Input, diagram editor, previews, browser-side image rendering |
| API | Node.js, Express, TypeScript | Auth, ownership, jobs, storage, protected n8n triggers |
| Automation | n8n Community Edition (self-hosted) | Generation and distribution workflows, bounded retries |
| AI | Gemma |
Fastest files to judge the engineering:
-
workflows/src/analysis-compiler.jsโ the deterministic Gemma-analysis โ Mermaid compiler. Pure module, unit-tested (25 tests), shared by the workflow and the test suite -
packages/shared/src/social.tsโ platform rules (twitter-text weighted length) enforced in one place for UI and API -
supabase/migrations/โ RLS-first schema; the browser key literally cannot read your private projects -
render.yamlโ the whole three-service stack as one reviewable file -
workflows/generate.jsonโ the n8n automation, exported JSON, every node readable
How I Built It
Stack: React + Vite (studio, browser-side image export) ยท Express + TypeScript (API: ownership, jobs, validation) ยท n8n Community Edition on Render (the automation engine โ exported as JSON in the repo) ยท Gemma-4 26B via Google's hosted API (all generation) ยท Supabase (Postgres + Auth + Storage) ยท Buffer (distribution) ยท Render (hosting, via a render.yaml Blueprint).
The interesting part: my model refuses to follow instructions โ and refusing made the product better
Hosted Gemma-4 would not emit JSON. Not "sometimes struggled" โ categorically. I tried, in order: JSON-only instructions, schema-first prompts, responseMimeType: application/json (silently ignored), assistant-turn prefill, few-shot as conversation turns, even the bigger 31B variant (times out). Seven structured-output contracts failed the same way: every reply came back as planning bullets.
Then the reframe: the bullets were good. Gemma's analysis correctly identified components, flows, caveats. Only the transcription failed. So I stopped asking a language model to be a serializer:
- Gemma analyzes the passage into a labeled-line contract (
Title: / Components: / Flow 1 (label): A -> B / Assumptions: / Summary:) โ its native style. - A deterministic compiler (~150 lines, unit-tested) turns that analysis into
flowchart LR/flowchart TD/sequenceDiagramwith type-shaped nodes, per-flow edge colors, deduplication, and a 12-component readability cap.
The model does the thinking, code does the formatting, neither pretends to be the other. Every result records the exact model id and prompt version (v3-labeled), and the prompts live versioned in /prompts.
The second story: the model echoes your prompt, so the compiler grew a filter
Shipped version 2, generated a diagram, and got nodes named max 3 words each, <Component>- ?, Comma-separated, None. Gemma had restated my format rules as diagram content โ and my compiler, trusting, compiled them into boxes. The fix was two-layered:
- The prompt gained an explicit "never copy this template into your answer" rule.
- The compiler gained a junk filter (echoed rule-text patterns, connector fragments like "Database, then Application", meta-titles like "(4 words).*") โ because prompt rules are suggestions, but the filter is a guarantee.
That's the honest loop of this build: ship โ screenshot the failure โ root-cause โ add a test. The junk filter's test case is literally the real failure output.
Why the diagrams look designed, not default
Mermaid's architecture-beta couldn't carry edge labels (verified limitation), so cache-hit vs cache-miss paths were invisible. Architecture mode now compiles to flowchart LR with type-shaped nodes (cylinder = database, rounded = cache, stadium = external), per-flow edge colors, and labels on each path's first hop โ same warm palette as the app, via one shared theme module. Small change, big legibility. (Screenshot 3 above is this path in production: twelve labeled, color-coded edges from one highlighted passage.)
Trust boundaries are real
- Gemma's output arrives via shared-secret callbacks and is untrusted input until validated โ render failures trigger one bounded AI repair, then stop.
- Platform rules enforced in the API, not just the UI. Over-length X posts are rejected server-side with the weighted algorithm.
- RLS-first: only the flagged sample project is public; the anon/publishable key cannot read private rows.
- Orphaned jobs (worker restart mid-generation) are detected and failed with a "generate again" message instead of hanging forever.
The whole pipeline is inspectable
The n8n workflow โ authenticated webhook โ 202 ack โ Gemma โ validate/compile โ one bounded repair โ idempotent callbacks โ is exported JSON in /workflows. You can read exactly what happens to your text, and swap the model node without touching the app.
Best Use of Render
Render is FlowToFeed's production runtime โ all three services, one Blueprint.
render.yaml defines the stack declaratively:
-
flowtofeed-api โ Node web service (Express + TypeScript), builds the shared + api workspaces, health check on
/api/health, starts the compiled ESM server. -
flowtofeed-web โ static site; the Vite build bakes
VITE_API_URLat build time; SPA rewrite fallback routes every path to the app shell. -
n8n โ the worker, on a starter instance with a persistent disk for
/home/node/.n8nand a pinnedN8N_ENCRYPTION_KEY, so workflows and credentials survive redeploys and restarts. This is the piece that makes self-hosted n8n production-shaped on Render: the automation state is durable, not ephemeral.
Deploys are main-only by design: every service pins branch: main with autoDeploy โ dev work merges to main, main is the deploy button. Secrets are dashboard-managed (sync: false in the Blueprint), and the n8n โ API trust is two shared-secret headers, so nothing authenticates by network proximity.
The Render-specific war story: n8n's production webhooks only register at boot, and boot consistently outlives naive readiness checks โ my provisioning script reported "never registered" three times while the boot was simply still finishing. The fix was to re-contract the script: import, activate, restart, short confirm-poll, then verify by probing the route (403 = registered and rejecting the probe secret, 404 = not yet). That probe now gates every deploy.
Best Use of Gemma
-
Model:
gemma-4-26b-a4b-itvia the hosted API, versioned prompts in/prompts, real generations captured inevidence/. -
The analysis-compile pattern (above) is the headline: Gemma's native analytical strength, with a deterministic compiler guaranteeing the output format. It also survives model swaps โ
GEMMA_MODELis an env value. -
Measured honesty: diagram generation runs ~2.5โ6.5 min unattended end-to-end (webhook โ analysis โ compile โ validation โ callback), measured in
evidence/measurements.md. - Failure-driven evals: the junk-filter test suite is built from real model outputs that leaked through v2 โ the echoes, the connector fragments, the meta-titles. The eval set grew every time the model surprised me.
Best Use of GitHub
GitHub runs the discipline, not just the hosting:
-
CI on every push/PR (
.github/workflows/ci.yml): builds the shared package first (the monorepo got a CI-red commit from that exact gap โ fixed at the root), then typechecks all workspaces and runs the unit suite. - CodeQL security scanning on every push to dev/main.
- Dependabot on a daily cadence (majors ignored, curated scope) โ you can watch dependency PRs recur in the tab; each one runs the full CI gate.
-
Branch discipline as history: one capability-named branch per phase (
api-core,gemma-proof,diagram-modes,social-variantsโฆ), squash-merged todevthrough PRs,devโmainto ship. The PR history reads like the build log because it is one. - Tests as memory: 25 unit tests, and the newest ones exist because the model leaked junk into diagrams โ the suite is the failure log, executable.
Why Does Open Innovation Matter?
-
The model is a config value.
GEMMA_MODELin an env file. The analysis-compile pattern doesn't care which open-weight model produces the analysis. Try that with a closed assistant baked into your pipeline at $-per-thought. - The automation is a file, not a SaaS relationship. The n8n workflow is JSON in the repo. Fork it, import it, read every node.
- The output is an open standard. Diagrams are Mermaid text โ diffable, git-versioned, editable by any Mermaid tool forever. FlowToFeed can die; your diagrams survive.
- Open weights made the failure mode interesting instead of fatal. A closed model silently mangling JSON is a support ticket. An open model whose quirks I could study became a design pattern โ analysis โ compile โ that will outlive this model version.
The Lazy Payoff
Measured on the cache-aside walkthrough (real numbers, evidence/measurements.md):
- One diagram: ~2.5โ6.5 min, unattended
- Three channel lessons: one serial pass, minutes, unattended
- Manual baseline: redraw + rewrite + resize + reformat โ 45โ60 focused minutes per lesson package
An hour of copy-paste with extra steps โ minutes of review. And the review is the part worth a human: checking the mechanism is right, the caveat survived, the example actually teaches.





Top comments (0)