DEV Community

Cover image for I Got Tired of Turning One Blog Post Into Five Different Tasks, So I Built FlowToFeed
Abubakar Ahmed
Abubakar Ahmed

Posted on

I Got Tired of Turning One Blog Post Into Five Different Tasks, So I Built FlowToFeed

Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady Submission ๐Ÿฆฅ

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:

  1. The blog post wants one diagram.
  2. The Instagram carousel wants the same diagram, redrawn, in 1080ร—1350.
  3. 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%:

  1. Paste an explanation once (Markdown, or import a .md file).
  2. 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.
  3. 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.
  4. 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.
  5. 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.

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
Loading
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 / sequenceDiagram with 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:

  1. The prompt gained an explicit "never copy this template into your answer" rule.
  2. 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_URL at 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/.n8n and a pinned N8N_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-it via the hosted API, versioned prompts in /prompts, real generations captured in evidence/.
  • 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_MODEL is 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 to dev through PRs, dev โ†’ main to 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?

  1. The model is a config value. GEMMA_MODEL in 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.
  2. The automation is a file, not a SaaS relationship. The n8n workflow is JSON in the repo. Fork it, import it, read every node.
  3. 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.
  4. 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)