DEV Community

Matthew Gladding
Matthew Gladding

Posted on • Originally published at gladlabs.io

The Console Was Lying to Us About Blocked Approvals

What we shipped on 2026-07-13

Today's most useful fix wasn't a feature -- it was admitting the console had been quietly lying. POST /api/social/drafts/{id}/approve returned HTTP 200 even when SocialDraftsService.approve_draft() blocked the push, and the console's fetch wrapper only checks status codes. So a blocked approval -- promoted post not yet published, missing Postiz integration UUID -- painted a green "queued for Postiz" toast and the draft vanished from the queue while nothing actually reached Postiz. We confirmed it against prod data: two drafts for a specific task had sat pending since 2026-07-12 because the task was still awaiting_approval. Every approve click on those was silently swallowed. The fix is a contract match -- routes/social_routes.py now raises HTTPException(409, detail=error), matching the wrong-state-409 pattern approval_routes.py already established, and console/js/api.js's http() now folds the response body's detail/error into the thrown error so toasts show the real reason (PR #2435). CLI and MCP never had this problem -- they read the result dict directly -- which is its own small lesson about how many failure modes only exist at the HTTP boundary.

The other satisfying arc today was watching metrics go from collected-but-invisible to actually on a dashboard. plugins/scheduler.py::_runner had been discarding JobResult.metrics for every job -- 44 jobs already populate it, logging only to Loki -- while the dashboards saw nothing. _capture_job_metrics now writes an event_type='job_run' audit_log row per metrics-emitting fire, namespaced so job keys can't shadow the envelope (PR #2431). That data sat queryable but panel-less until the Job Fleet Metrics row went up on Integrations & Admin -- job-agnostic, working immediately for media_reconciliation, render_*, sync_cloudflare_analytics and friends without per-job wiring (PR #2436). Then we went further and cross-referenced every poindexter_* series against every provisioned dashboard's panels[], finding metrics that were live in Prometheus and displayed nowhere -- 13 new panels across 7 dashboards, all exprs validated against the live instance (PR #2442). Three PRs closing the same gap from different angles: emit, surface, audit for completeness.

We also shipped the foundation piece Dev.to syndication actually needed. The existing niche taxonomy couldn't separate ai-ml from gaming/hardware -- everything's glad-labs, and tags are LLM-sprawl -- so "syndicate ai-ml, exclude gaming" wasn't expressible as a rule. The new post_content_types table gives us a multi-label axis, populated by ClassifyContentTypesJob sweeping published posts every 6h through a local structured-extraction LLM against a DB-configurable label set. The guardrail we care about: only labels in the set persist, and a post the model can't confidently classify gets zero rows rather than a silent default. With that axis in place, CrosspostToDevtoJob finally gates on an operator allowlist plus a quality floor instead of syndicating every published post indiscriminately (PR #2415).

Smaller but no less real: we finally nailed down why Z-Image-Turbo keeps rendering mangled text into images. It's guidance-distilled (guidance_scale=0), which means its own image_negative_prompt -- the one that lists "text, words, letters, numbers, watermark" -- never applies, and diffusers' ZImagePipeline ignores negative_prompt outright when guidance_scale < 1. The "no text" lever isn't tuned wrong, it's structurally dead on this model. That sent us into a bake-off spec across twelve candidate text-to-image models, each scored at its own best config rather than forcing one knob onto all of them (PR #2386).

Release 0.102.0 bundled all of it plus the smaller connective tissue -- the Grafana alert sync CLI, affiliate multi-keyword matching with CSV bulk-import, footer icons for YouTube and Buy Me a Coffee, the nightly app-settings.md and CLAUDE.md doc-sync bots keeping source-of-truth stats honest without a human in the loop (PR #2445, PR #2444, PR #2443). None of it is glamorous individually. Together it's a day where the dashboards stopped lying about coverage and the console stopped lying about success -- which, on a one-person shop, is worth more than any single feature.

Auto-compiled by Poindexter from today's commits and PRs. See the work: github.com/Glad-Labs/poindexter.

Sources

Top comments (0)