Latest News

Title: I just published my first VS Code extension for TypeScript/Node.js!

Hey everyone! I was working on a Node.js project and realized I needed a better way to handle data models. I built and just published justwork-model-ts.It's built specifically for TypeScript and helps structure backend database schemas cleanly. You can grab it directly from the marketplace here: https://marketplace.visualstudio.com/items?itemName=EllaAndy.justwork-model-ts Would love any feedback, feature requests, or code reviews!

Published: Sun, 12 Jul 2026 17:19:48 +0000 | Source: DEV Community

BroncoCTF : The KeyMaster Writeup

Challenge

No file, no binary — just a URL:

https://broncosec.com/BroncoCTF

The flag format is given as bronco{XXXX...} . Nothing else. This is an
OSINT / web-recon style challenge: the flag is broken into 8 numbered
fragments, scattered across the live site using a mix of static HTML,
downloadable files, and client-side JavaScript behavior. The goal is to
find all 8 pieces and assemble them in order.

Recon

Fetching the page's rendered HTML directly gives a first look. Most of
the page is a fairly standard "CTF landing page" — hero banner, stats
counters, sponsor logo, footer credits. Scanning through it for anything
that looks out of place (odd alt text, title attributes, query
strings, filenames) turns up four fragments immediately, sitting in
plain sight in the static markup:

# Fragment Location
1 bronco{h Plain text at the very end of the page footer, after the credits
3 0und_th3 title attribute on the "Join the Competition" button/link
6 ut31y_n0 Query string on the "BroncoCTF 2026...?" repository card: href="/BroncoCTF?KEY=6-ut31y_n0"
8 _4t_411} alt text on the sixth stats card (the one using correct-flag-colorized.svg )

That's 4 of 8, found just by reading the rendered HTML carefully.

Piece 7 — a hidden download

The "About the Competition" text includes:

BroncoCTF 2026 will be our fifth CTF...

The word "2026" is a hyperlink to /7.txt , disguised as normal body
text (no obvious styling gives it away as a link in the rendered page).
Fetching it directly:

$ curl https://broncosec.com/7.txt
7 - _w0rr135

Piece 7 found.

Pieces 2, 4, 5 — hidden in client-side JavaScript

The remaining three fragments never appear in the static HTML at all —
they're generated at runtime by JavaScript event handlers, meaning a
plain curl /fetch of the page will never reveal them. The fix is to
pull down the site's actual JS bundles and grep them directly.

Step 1 — enumerate the script chunks. Looking at the <script src=...>
tags in the raw page source (Next.js app, so chunks are hashed filenames
under /_next/static/chunks/ ):

$ curl -sO https://broncosec.com/_next/static/chunks/e785679bf8074938.js
$ curl -sO https://broncosec.com/_next/static/chunks/f31cf569852813cb.js
... (and the rest of the referenced chunks)

Step 2 — grep for anything flag-shaped:

$ grep -rn "bronco{" .
$ grep -rnE "[0-9]+ - " .

This immediately surfaces three onClick handlers and one DOM-injection
trick buried in the minified React component source:

Fragment 2 — hidden behind a click handler on the word "flags":

onClick: () => {
let e = document.getElementById("addtext");
e && e.firstChild && (e.firstChild.textContent += "2 - 3y_y0u_f");
new Audio("ding.oga").play();
}

Clicking the word "flags" in the body text appends 2 - 3y_y0u_f
into a hidden <div id="addtext"> on the page and plays a sound. A
static HTML fetch will never show this — it only exists after the click
event fires and mutates the DOM.

Fragment 4 — hidden behind clicking the 🙋 emoji:

onClick: () => {
document.cookie = "KEY4=4 - m_4ll_w1; path=/";
...
new Audio("puzzle.oga").play();
}

Clicking the raised-hand emoji under "Online Bragging Rights" sets a
cookie named KEY4 containing the fragment, and appends a 🍪 emoji
next to it as visual confirmation.

Fragment 5 — hidden as a dynamically-injected HTML comment:

function a({comment: e}) {
let n = useRef(null);
useEffect(() => {
n.current && (n.current.outerHTML = `<!-- ${e} -->`);
}, [e]);
return <script ref={n} type="text/placeholder" />;
}
...
<a comment="!!! 5 - th_4b501 !!!" />

A placeholder <script> tag gets replaced, after mount, with an HTML
comment containing the fragment. This only exists in the live DOM
after React hydrates — invisible both to a static curl and to
"view source", since browsers' "view source" shows the original
server-rendered HTML, not the post-hydration DOM. Only "Inspect
Element" (which reflects the live DOM) or a JS-aware fetch would catch
it.

Assembling the flag

Ordering all 8 recovered fragments by their embedded index:

1: bronco{h
2: 3y_y0u_f
3: 0und_th3
4: m_4ll_w1
5: th_4b501
6: ut31y_n0
7: _w0rr135
8: _4t_411}

Concatenated:

bronco{h3y_y0u_f0und_th3m_4ll_w1th_4b501ut31y_n0_w0rr135_4t_411}

Leetspeak-decoded, this reads:

"hey you found them all with absolutely no worries at all"

— a fitting message for a challenge literally about finding every
scattered fragment.

Flag

bronco{h3y_y0u_f0und_th3m_4ll_w1th_4b501ut31y_n0_w0rr135_4t_411}

Key Takeaways

  • Static fetches only show you half the picture on modern JS-framework sites. Anything gated behind an onClick , a useEffect , or other client-side hydration logic is invisible to curl /basic web_fetch — you have to either pull and read the actual JS bundles, or interact with a live, JS-executing browser (DevTools/Inspect Element) to see DOM state that only exists after the page runs.
  • Downloading and grepping the full JS bundle set ( /_next/static/chunks/*.js for Next.js apps) is a fast, reliable way to recover client-side logic and embedded strings without needing to manually trigger every interaction in a browser first.
  • Minified React source is still greppable. Even heavily minified JSX compiles down to recognizable patterns ( onClick: , string literals, JSX attribute names) that survive minification well enough to grep for target patterns like flag formats or numbered fragments.
  • Distinguish "view source" vs. "Inspect Element" : view-source shows the original server response; Inspect Element (or any DOM-reading tool) shows the current, JS-mutated state. Challenges that hide data in post-hydration DOM changes require the latter.

Published: Sun, 12 Jul 2026 17:16:27 +0000 | Source: DEV Community

World Cup Rivalry Bot — AI-Voiced Hype for Every Goal

This is a submission for Weekend Challenge: Passion Edition

What I Built

I built the World Cup Rivalry Bot — a Telegram bot for fans who want their World Cup rivalry to feel alive in their own group chat. You set your team and your rival, and whenever a real goal happens in the tournament, the bot fires back an AI-generated hype line in your team's voice — followed by an imagined clapback from the rival's fans, spoken in a completely different voice.

It works in DMs and in group chats, and each person in a group can root for their own team independently. Since World Cup passion isn't just about scoring — it's about the back-and-forth banter between rival fans — I wanted the bot to capture both sides of that rivalry, not just one team's celebration.

Demo

Try it on Telegram: @worldcup_rivalry_bot

Quick flow:

  1. /start → set your team (e.g. /team Brazil )
  2. /rival Argentina
  3. /autohype → the bot starts watching real World Cup 2026 results for both your team and your rival
  4. /simulate or /simulaterival → instantly demo the celebration pipeline without waiting for a real goal

Code

GitHub: https://github.com/Gbolaww/worldcup-rivalry-bot

How I Built It

The bot is written in Go and runs as a single long-polling process against the Telegram Bot API, deployed on Railway.

A few decisions worth calling out:

  • Two voices, two AI models working together. Every goal celebration is a two-part exchange: Google's Gemini generates a short hype/trash-talk line for whichever team scored, and a second, different clapback line from the other side's perspective. Each line is converted to speech with ElevenLabs , using two distinct voice IDs — one for the "home" team's fans, one for the rival's — so it genuinely sounds like two different people talking past each other, not one narrator switching sides.
  • Real tournament data, not mocked results. The bot polls a public, free World Cup 2026 dataset to detect when a tracked team's goal total increases, and auto-fires the celebration — no manual trigger needed once /autohype is on. Since that data isn't second-by-second live, I added /simulate and /simulaterival so the exact same detection-and-celebrate code path can be demoed instantly, without waiting on an actual goal.
  • Per-user state, not per-chat. Team and rival are tracked by Telegram user ID rather than chat ID, so a group of friends can all use the bot in the same group chat, each rooting for their own team, without stepping on each other's setup.
  • Getting the AI stack working was its own adventure. Both gemini-1.5-flash and gemini-2.0-flash were already fully deprecated by the time I built this, and ElevenLabs had recently locked free-tier API access to preset "library" voices — so most of Saturday went into just finding which models and voices were actually still available before I could write a single hype line.

Prize Categories

  • Best Use of Google AI — Gemini generates both the hype line and the rival's comeback line for every goal.
  • Best Use of ElevenLabs — Two distinct ElevenLabs voices bring each side of the rivalry to life as actual voice notes in the chat.

Published: Sun, 12 Jul 2026 17:10:46 +0000 | Source: DEV Community

BroncoCTF : Negative Bread Writeup

Challenge

We're given a single ELF binary, bank :

$ file bank
bank: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically
linked, interpreter /lib64/ld-linux-x86-64.so.2, not stripped

Running it presents a menu-driven "bank" simulator:

+------------------------------------------+
| BRONCO NATIONAL BANKv3.0 |
| "We take security seriously." |
+------------------------------------------+
Welcome! Starting balance: $100

Looking for the flag? It costs $1000000.

[1] Deposit(max $10000 per txn, 3 remaining)
[2] Withdraw
[3] Dispute a Charge (up to $1000000, one-time)
[4] Invest (fixed 0.5% return)
[5] Buy Flag ($1000000)
[6] Exit

Goal: get balance to ≥ $1,000,000 starting from $100 , with deposits
capped at $10,000 × 3 = $30,000 total — nowhere near enough through
legitimate means. There must be a logic bug in one of the other options.

Recon

$ checksec --file=bank
RELRO STACK CANARYNXPIE
Partial RELRO No canary found NX disabled No PIE

No canary, NX disabled, no PIE — this binary could support classic stack
smashing / shellcode injection, but the binary is small, not stripped, and
has a suspiciously named function sitting right there:

$ nm bank | grep ' T '
0000000000401223 T main
00000000004011d6 T win

A dedicated win function that isn't called from anywhere obvious in
normal program flow is the classic signature of a "reach this function"
style challenge — no shellcode or ROP needed, just a logic bug that gets
main to call win() on our behalf.

$ objdump -d --disassemble=win bank
00000000004011d6 <win>:
4011d6: endbr64
...
call puts@plt; prints first message
...
call puts@plt; prints second message (the flag)
ret

Confirmed: win() just prints two strings — almost certainly the flag.
The only remaining question is what condition triggers a call to win .

Finding the call site

$ objdump -d --disassemble=main bank | grep -B5 'call.*win'
401713: mov-0x4(%rbp),%eax
401716: cmp$0xf423f,%eax ; 0xf423f = 999,999
40171b: jbe401731; if balance <= 999999, skip win()
401722: call 4011d6 <win>

0xf423f = 999,999 . So the check is simply:

if (balance > 999999) {
win();
}

This is the "Buy Flag" option's logic — straightforward, no funny business
here. The bug must be in how balance (stored at -0x4(%rbp) throughout
main ) can be inflated.

The vulnerable function: Dispute a Charge

Disassembling the "Dispute" branch (menu option 3):

; scanf reads dispute_amount into -0x18(%rbp)
4015a9: printf "Dispute amount: $"
4015bf: scanf "%d", &dispute_amount

4015e2: mov-0x18(%rbp), %eax ; eax = dispute_amount
4015e5: mov%eax, %edx
4015e7: neg%edx; edx = -dispute_amount
4015e9: cmovns %edx, %eax; eax = abs(dispute_amount) <-- bounds check uses ABS VALUE
4015ec: cmp$0xf423f, %eax; compare abs(amount) to 999,999
4015f1: jle401611; if abs(amount) <= 999,999 -> allowed

; 401611: this is the "allowed" path
401611: mov-0x18(%rbp), %eax ; eax = ORIGINAL dispute_amount (NOT abs!)
401614: add%eax, -0x4(%rbp); balance += dispute_amount
401617: movl $0x1, -0x8(%rbp); mark "already disputed" flag

Decompiled, the logic is equivalent to:

int dispute_amount;
scanf("%d", &dispute_amount);

if (abs(dispute_amount) <= 999999) {
balance += dispute_amount; // uses the ORIGINAL signed value, not the abs()
already_disputed = 1;
}

The bugs

  1. No verification of a prior charge. "Dispute a Charge" implies you're
    getting a refund for money you previously spent — but the function never
    checks that any such charge exists. You can "dispute" an amount you
    never paid.

  2. Bounds check vs. effect mismatch. The bounds check validates
    abs(dispute_amount) <= 999999 , correctly rejecting anything with a
    magnitude over that threshold in either direction. But the actual
    balance update uses the raw, signed dispute_amount — so a large
    positive value close to the cap sails through the check and gets
    added directly to balance, with no requirement that it be tied to
    real prior activity.

Combined, this means: enter 999999 as the dispute amount, and balance
jumps by nearly a million dollars, no strings attached.

Exploit

Starting balance is $100. A single dispute clears the flag threshold:

[3] Dispute a Charge (up to $1000000, one-time)
Dispute amount: $999999
[+] Dispute processed. Refund of $999999 applied.
New balance: $1000099

Balance is now $1,000,099 — over the $999,999 threshold checked at
the "Buy Flag" call site.

[5] Buy Flag ($1000000)

This triggers:

if (balance > 999999) {
win(); // prints the flag
}

No deposits, investing, or withdrawals are even necessary — a fresh run
straight into option 3 with 999999 is sufficient.

Root Cause Summary

Issue Detail
Missing state validation "Dispute" has no concept of a real prior charge to dispute against — it's an unconditional credit gated only by a magnitude check.
Check/effect mismatch The bounds check computes abs(amount) but the balance update uses the original signed amount , so the "safety" check doesn't actually constrain what gets added.
One-time limit is irrelevant The already_disputed flag prevents a second dispute, but a single dispute is already enough to win, so the limit provides no real protection.

Flag

Running the exploit path yields the flag output from win() .

Key Takeaways

  • When a binary is unstripped and has a suspiciously-named function like win , always check first whether it's reachable directly through some input-driven logic bug before reaching for exploitation techniques like ROP/shellcode — despite NX disabled and no canary hinting at that path, this challenge didn't need it.
  • Always check that a bounds/validation check and the operation it guards actually use the same value. Here, the check computed abs(x) but the effect used x directly — a very common class of bug where sanitization is performed on a copy/derived value instead of the value actually used downstream.
  • "Refund"/"dispute"/"credit" style features are worth extra scrutiny in any financial-logic challenge — they're an intentional design pattern for injecting value into a system, and are exactly where missing state/history validation tends to hide.

Published: Sun, 12 Jul 2026 17:10:34 +0000 | Source: DEV Community

Documenting My Journey Through Computer Science, Software Development, and Problem Solving

While learning core computer science subjects or development or any tech stack understanding algorithms, programming languages and mathematics is not the hardest part the hardest part is to stay consistent and stay sticked to the goal.

Why Am I Writing?
Like many aspiring developers, I've started countless courses, begun exciting projects, and promised myself I'd stay consistent with programming practice. But more often than I'd like to admit, I left them unfinished. I wasn't lacking interest—I was lacking consistency.

This time, I'm taking a different approach.

I've decided to document my entire learning journey publicly. Here, I'll share my progress, the lessons I learn, the mistakes I make, and the milestones I achieve. I'll also write blogs explaining computer science concepts, data structures and algorithms, mathematical theories, and the ideas behind the projects I build.

My hope is that writing about what I learn will keep me accountable, deepen my understanding, and maybe even help someone else who is on the same path.

What You Can Expect
Over the coming months, I'll be writing daily ,i will write minimum 1 blog dailway for the upcoming 200 days , about the topics I'm studying and the projects I'm building.

Some of the areas I'll cover include:

  • Data Structures and Algorithms
  • Core Computer Science subjects
  • Mathematics for Computer Science
  • Backend and Full-Stack Development
  • Data Analytics
  • Real-world development projects
  • Lessons learned from debugging and problem solving
  • Productivity, consistency, and my learning process

I'll will be using this is platform to create digital notes that will be easy to understand and will explain fundamental and complex topics of computer science.

I believe in building in public . Now, I'm choosing to learn in public.
Every concept I master, every project I build, every mistake I make, and every lesson I learn will become a part of this journey.
This is Day 1—not of becoming an expert, but of refusing to quit.

See you in the next post.
Until then, keep learning, keep building, and keep showing up.

written by - AP09 - DAY 01

Published: Sun, 12 Jul 2026 17:08:43 +0000 | Source: DEV Community

(Spanish)📝 Caso de Estudio: Si Presto! — De cuadernos de notas a una arquitectura Offline-First en Flutter

📌 Introducción: El origen del problema (De una libreta a código)

El software más útil nace de resolver problemas reales cotidianos. Este proyecto comenzó cuando observé a un familiar que prestaba dinero de manera informal. Su sistema de gestión consistía en una libreta física y hojas de cálculo en Excel.

El resultado era caótico:

  • Olvidos frecuentes en las fechas de cobro.

  • Falta de claridad al calcular abonos parciales o intereses acumulados.

  • Horas perdidas redactando manualmente estados de cuenta por WhatsApp.

Para solucionar esto, decidí diseñar e implementar Si Presto!, una aplicación móvil nativa construida con Flutter y Firebase enfocada en el control de préstamos informales, optimizada para contextos de baja conectividad y con un modelo híbrido de monetización.

🏗️ La Arquitectura: Diseño de Software y Flujo de Datos

Para asegurar un rendimiento óptimo en dispositivos de cualquier gama, estructuré la aplicación bajo una Arquitectura Limpia (Clean Architecture) organizada en un Monorepo móvil con separación estricta de responsabilidades:

lib/
├── core/# Temas, PDF engines, notificaciones locales
├── domain/# Entidades inmutables (Freezed) e interfaces
├── data/# Fuentes de datos (Isar NoSQL, Firebase Auth) y modelos
└── presentation/# Gestión de estado (Riverpod) y páginas UI

El Flujo de Datos Híbrido:

UI ➔ Riverpod StateNotifiers ➔ Repositorios ➔ Isar DB (Local) ➔ Firebase Cloud Backup (Premium).

Toda la generación de código inmutable y reactivo se automatizó utilizando build_runner con herramientas como @freezed , @riverpod e @Isar .

🛡️ Los 4 Grandes Retos Técnicos de Ingeniería

1. Sincronización Offline-First y Consistencia de Datos en Venezuela

El Problema: La aplicación debe funcionar sin fricciones en zonas con conectividad intermitente o nula. Si el cobrador registra un abono en la calle sin señal, los saldos locales no pueden duplicarse ni corromperse al recuperar la red.

La Solución: Implementé un modelo Offline-First utilizando Isar Database como almacenamiento NoSQL local de alto rendimiento. La aplicación realiza todas las operaciones CRUD de forma síncrona en la base de datos local y, mediante un servicio de sincronización en segundo plano, respalda la información en la nube con Cloud Firestore únicamente si el usuario cuenta con conectividad y posee el plan Premium.

2. Generación Dinámica de PDF Nativos en Dispositivos de Gama Baja

El Problema: Generar reportes financieros detallados consume memoria. Si el renderizado se realiza de forma ineficiente, los teléfonos de gama baja pueden sufrir cierres inesperados (crashes por OOM).

La Solución: Diseñé un motor de renderizado transaccional utilizando los paquetes nativos pdf y printing . El motor procesa las colecciones inmutables de transacciones en bloques (chunks) directamente en la memoria caché del dispositivo, ensamblando el PDF en tiempo real para que el cobrador pueda exportarlo y compartirlo inmediatamente por WhatsApp sin sobrecargar el procesador del teléfono.

3. Modelo de Monetización Híbrido (AdMob + RevenueCat)

El Problema: Mantener la aplicación requiere rentabilidad, pero saturar una herramienta financiera de cobros con anuncios intrusivos arruina por completo la experiencia de usuario (UX).

La Solución: Implementé un esquema Freemium controlado minuciosamente desde la capa de datos:

  • Free Tier: Límites lógicos quemados en el repositorio (Máximo 3 deudores y 4 pagos por préstamo) financiados mediante banners publicitarios no intrusivos usando Google Mobile Ads.

  • Premium Tier: Integrado de manera limpia con el SDK de RevenueCat. Al detectar la compra activa mediante el singleton PurchaseService , la aplicación remueve por completo los anuncios, desbloquea deudores/pagos ilimitados, habilita la exportación a PDF y activa el respaldo en la nube con Cloud Firestore.

4. Automatización de Alertas y Notificaciones Locales

Para eliminar la dependencia constante de servidores externos (Push Notifications) que pueden fallar si el dispositivo no tiene datos móviles, configuré un servicio autónomo basado en flutter_local_notifications . La aplicación programa internamente recordatorios de cobro locales basados en las fechas límite de las entidades del dominio de datos, manteniendo al cobrador alerta sin consumir datos de red.

🎨 Identidad Visual: Estética Neobrutalista Oscura

Para mantener la coherencia con mi marca personal, la interfaz de Si Presto! adopta un estilo Neobrutalista Oscuro caracterizado por:

Un fondo oscuro profundo de alto contraste combinado con un verde neón eléctrico ( #00FF99 ) para destacar los saldos positivos.

Tarjetas de resumen financiero con bordes marcados y sombras sólidas planas desplazadas, rompiendo con los diseños tradicionales y planos de las aplicaciones bancarias comunes.

🧠 Lecciones de Ingeniería de Producto

Construir Si Presto ! partiendo de una necesidad familiar real me enseñó que la arquitectura de software senior no se mide por la cantidad de servicios en la nube que utilices, sino por saber delegar eficientemente el cómputo. Al elegir almacenar datos localmente con Isar, renderizar PDFs nativos en el cliente y programar notificaciones locales, reduje los costes de infraestructura de la aplicación a prácticamente cero dólares para el nivel gratuito, asegurando un producto escalable y altamente rentable.

Link: https://portfolio-three-topaz-81.vercel.app/es/projects/si-presto

Published: Sun, 12 Jul 2026 17:04:34 +0000 | Source: DEV Community

BronoCTF : LEts a GO Writeup

Category: Forensics / Misc
Difficulty: Easy
Flag: bronco{3ve4yth1ng_1s_aw3s0me}

Challenge

We're given lego_bricks_challenge.zip , a 63 KB archive.

$ zipinfo lego_bricks_challenge.zip
Archive:lego_bricks_challenge.zip
Zip file size: 63307 bytes, number of entries: 333
-rw----...lego_bricks_challenge\.DS_Store
-rw----...lego_bricks_challenge\.cache\sessions\.npmrc
-rw----...lego_bricks_challenge\.git\objects\pack\docker-compose.yml
-rw----...lego_bricks_challenge\src\modules\network\protocols\.solver.py
... (329 more decoy entries) ...
333 files, 4434 bytes uncompressed, 4627 bytes compressed

The listing reveals 333 files buried in a deep, decoy-heavy directory tree ( .cache , .config , .git , .local , data , docs , src , tmp , etc.) — a classic haystack pattern. Almost every file is tiny (0–4 bytes) and named either generically ( Dockerfile , go.sum , config.ini , package.json ) or as .part_NN , with NN ranging well past 250. The file sizes and naming make it clear most of this is padding meant to bury the real content.

Recon

Sorting the extracted tree by file size immediately isolates the outlier:

$ find lego_bricks_challenge/ -type f -printf '%s %p\n' | sort -n | tail -20
...
58 lego_bricks_challenge/src/modules/network/protocols/.legacy/Dockerfile
59 lego_bricks_challenge/assets/images/sprites/.sheets/README.md
59 lego_bricks_challenge/src/modules/package.json
60 lego_bricks_challenge/src/modules/.legacy/config.ini
61 lego_bricks_challenge/data/raw/.staging/robots.txt
61 lego_bricks_challenge/src/modules/network/protocols/.legacy/requirements.txt
61 lego_bricks_challenge/tmp/system_logs/kernel/.audit/.DS_Store
62 lego_bricks_challenge/docs/.drafts/app.py
906 lego_bricks_challenge/src/modules/network/protocols/.solver.py

Every other file is under 62 bytes; .solver.py at 906 bytes stands out by more than an order of magnitude — and it's hidden (dot-prefixed) inside an otherwise-decoy path.

Reading the solver

$ cat "lego_bricks_challenge/src/modules/network/protocols/.solver.py"
#!/usr/bin/env python3
"""Hidden solver for: LEt's a GO! concatenate .part_00 .. .part_49"""
import os, re, sys

def solve(start_dir: str) -> str:
pattern = re.compile(r"^\.part_(\d+)$")
parts: dict[int, str] = {}
for dirpath, _, filenames in os.walk(start_dir):
for fn in filenames:
m = pattern.match(fn)
if m:
idx = int(m.group(1))
if 0 <= idx <= 49:
with open(os.path.join(dirpath, fn)) as fh:
parts[idx] = fh.read()
flag = "".join(parts[i] for i in sorted(parts))
return flag
...

The script's docstring spells out the trick: "Hidden solver for: LEt's a GO! concatenate .part_00 .. .part_49 " .

Its logic:

  1. Walk the entire extracted tree.
  2. Collect every file matching ^\.part_(\d+)$ whose index is between 0 and 49 inclusive (ignoring the hundreds of .part_NNN decoys with indices outside that range or non-matching names).
  3. Concatenate their contents in ascending numeric order — the file's location in the directory tree is irrelevant, only the number in its name matters.

So the real flag is fragmented one character (or a few bytes) at a time across 50 files scattered throughout the tree, hidden among ~280 similarly-named but irrelevant decoy files.

Solving

The provided .solver.py itself couldn't be executed directly — it has a stray non-UTF-8 byte (a mangled emoji) in its docstring, which Python's parser chokes on:

SyntaxError: Non-UTF-8 code starting with '\x97' in file .../.solver.py on line 2

Rather than fix the encoding, we reimplemented the same logic cleanly, reading each part in binary mode to sidestep any encoding issues in the data itself:

#!/usr/bin/env python3
import os, re, sys

def solve(start_dir: str) -> bytes:
pattern = re.compile(r"^\.part_(\d+)$")
parts = {}
for dirpath, _, filenames in os.walk(start_dir):
for fn in filenames:
m = pattern.match(fn)
if m:
idx = int(m.group(1))
if 0 <= idx <= 49:
with open(os.path.join(dirpath, fn), "rb") as fh:
parts[idx] = fh.read()
missing = [i for i in range(50) if i not in parts]
if missing:
print(f"WARNING: missing indices {missing}", file=sys.stderr)
return b"".join(parts[i] for i in sorted(parts))

if __name__ == "__main__":
start = sys.argv[1] if len(sys.argv) > 1 else "lego_bricks_challenge"
flag = solve(start)
print(flag.decode("utf-8", errors="replace"))

Before running it, we verified all 50 required indices (0–49) existed somewhere in the tree with a quick scan — confirmed Missing indices: [] , Found count: 50 .

Running the reconstruction script:

python3 reconstruct_flag.py lego_bricks_challenge
bronco{3ve4yth1ng_1s_aw3s0me}

Flag

bronco{3ve4yth1ng_1s_aw3s0me}

Takeaways

  • Sort by size, not by name. In a haystack-style challenge, the real content is usually one clear statistical outlier once you strip away the noise ( find ... -printf '%s %p\n' | sort -n ).
  • Hidden/dot-prefixed files deserve a look .solver.py was intentionally invisible to a plain ls .
  • Read the source before running it. The script was self-documenting (docstring told us exactly what to do), which made writing an independent implementation trivial once the provided script hit an encoding snag.
  • Don't trust file content blindly. A stray non-UTF-8 byte broke the original script's execution but not its usefulness as a spec — worth distinguishing "this script is broken" from "this script's logic is wrong."

Published: Sun, 12 Jul 2026 16:59:15 +0000 | Source: DEV Community

PassionCast: I Built an AI Hype Man for World Cup Fans Using Gemini + ElevenLabs

This is a submission for Weekend Challenge: Passion Edition

What I Built

The World Cup quarter-finals are on right now. I'm watching matches, yelling at my screen, and I thought: what if I could bottle that feeling into an audio clip?

PassionCast lets you pick your team, choose a passion mode (hype speech, glory moment, rivalry fire, heartbreak, fan anthem, or custom), and generates a 60-second AI commentary clip. Not the generic "your team is amazing" stuff. Actual references to your team's players, history, and rivals.

Pick India, select "Rivalry Fire," hit generate. You get a breathless commentator talking about the India-Pakistan cricket-meets-football tension, with real names and real context. Try Argentina and you get Maradona, La Albiceleste, the Diego legacy. That's what I was going for.

Demo

👉 Try PassionCast Live

Three pre-generated sample clips are on the page. Hit play, no keys needed. You'll hear:

  • Argentina rivalry fire (Match Commentator voice)
  • India pre-match hype (Epic Narrator voice)
  • England heartbreak (Epic Narrator voice)

To generate your own, you'll need free API keys (30 seconds each):

The flow:

Code

🔥 PassionCast

Your personal AI hype man for the World Cup 2026.

Pick your team. Choose a moment. Get a passionate, AI-generated audio commentary that captures the fire of football fandom.

👉 Try PassionCast Live

PassionCast Landing

How It Works

  1. Pick your team from 48 World Cup 2026 nations
  2. Choose your passion — hype speech, glory moment, rivalry fire, heartbreak, or fan anthem
  3. Select a voice — epic narrator, match commentator, or poetic storyteller
  4. Generate — Gemini writes the script, ElevenLabs brings it to life as audio

Team Selection Flow

Tech Stack

Layer Technology Why
Script generation Google AI (Gemini 2.0 Flash) High temperature (0.9) + cultural context = scripts that feel personal, not generic
Voice synthesis ElevenLabs (Multilingual v2) Low stability (0.4) for emotional range, high style (0.6) for expressiveness
Frontend Vanilla JS + Vite Fast, no framework overhead, deploys anywhere
Hosting GitHub Pages Free, automatic deploys via GitHub Actions

No backend. No database…

How I Built It

No backend. No framework. One page, vanilla JS, two API calls. Vite for bundling, GitHub Pages for hosting.

Browser → Gemini 2.0 Flash (writes script) → ElevenLabs (speaks it) → Audio playback

I specifically didn't want a backend here. The whole point is that someone can fork this, drop in their own keys, and have it running in 30 seconds.

The Gemini Part (where I spent most of my time)

Getting Gemini to write passionate content was harder than I expected. At temperature 0.7, everything came out bland. "Your team has a proud history. The fans are excited." Useless.

At 0.9 , things got interesting. Combined with a detailed system prompt that includes the team name, their rivals, their football culture, and the specific style I want (narrator vs commentator vs poet), the output started feeling real.

The trick was constraining length. I needed 150-200 words, the sweet spot for 45-60 seconds of audio. Too short and it feels empty. Too long and ElevenLabs starts rushing or the clip drags.

// Note: ${apiKey} is a template literal variable, not a real key. 
// Users provide their own free key from aistudio.google.com/apikey
const response = await fetch(
`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
contents: [{ parts: [{ text: prompt }] }],
generationConfig: { temperature: 0.9, maxOutputTokens: 500 }
})
}
);

The ElevenLabs Part (where the magic happens)

I tested default voice settings first. Sounded flat. Like a GPS reading football commentary.

The fix: crank down stability to 0.4 (default is higher). This adds emotional variation. The voice wavers, speeds up, gets louder on key phrases. Exactly what you want for sports commentary.

Then style to 0.6 for extra expressiveness, and speaker boost on for clarity.

Three voices, matched to content:

  • Brian (dramatic narrator) for glory moments
  • Liam (energetic) for rivalry and hype
  • George (poetic storyteller) for heartbreak and anthems
body: JSON.stringify({
text: script,
model_id: 'eleven_multilingual_v2',
voice_settings: {
stability: 0.4,
similarity_boost: 0.8,
style: 0.6,
use_speaker_boost: true,
}
})

Other Decisions

I used flag images from flagcdn.com instead of emoji because emoji flags don't render on Windows for England, Wales, and Scotland (they show as black rectangles). Learned that the hard way after building the whole grid with emoji first.

Dark theme with gold/fire accents because... it's a passion app. Light mode would feel wrong.

What I'd Add

If I had another weekend: social sharing (let people post their clips), match-day mode that checks today's fixtures and auto-suggests hype content, and a gallery so you can hear what other fans generated. PRs welcome if you want to tackle any of these.

Prize Categories

  • Best Use of Google AI : Gemini 2.0 Flash with temperature 0.9 and detailed cultural prompting. Produces scripts with real player names, real rivalry history, and team-specific fan culture for 48 nations. Three distinct writing styles (narrator, commentator, poet) via style instructions in the prompt.

  • Best Use of ElevenLabs : Multilingual v2 model with deliberately tuned settings. Stability at 0.4 for emotional range (default sounds robotic for sports content). Style at 0.6 for expressiveness. Three voices paired to content types. The output sounds like broadcast commentary, not text-to-speech.

Published: Sun, 12 Jul 2026 16:57:52 +0000 | Source: DEV Community

BroncoCTF : Atomic Substitution Theory Writeup

Challenge

We're given secret.txt , a single line of comma-separated tuples wrapped
in what looks like braces and underscores:

(4, 17), (2, 16), (2, 15), (4, 9), { , (3, 2, 1), (5, 3), _ , (2, 17), ...
$ file secret.txt
secret.txt: ASCII text, with very long lines (479), with no line terminators

No further context is given — the challenge is to figure out the encoding
scheme from the data alone.

Recon

The tuples are almost all 2- or 3-element groups of small integers, plus
three literal tokens scattered through the stream: { , } , and _ .

Small integer pairs, a { / } wrapper, and an obvious "flag format" hint
( bronco{...} ) strongly suggested a coordinate lookup cipher — each
tuple indexes into some 2D reference grid, and the underscore/braces are
literal characters meant to survive the decode untouched.

The first four tuples are the key to spotting the scheme:

(4, 17), (2, 16), (2, 15), (4, 9)

Reading these as (period, group) coordinates on the periodic table :

Tuple Period Group Element Symbol
(4, 17) 4 17 Bromine Br
(2, 16) 2 16 Oxygen O
(2, 15) 2 15 Nitrogen N
(4, 9) 4 9 Cobalt Co

Concatenating the symbols: Br + O + N + Co = BrONCo
"Bronco" — matching the expected flag prefix exactly. Scheme confirmed.

Refining the scheme

Most tuples are 2-element (period, group) pairs and decode to a full
element symbol (1 or 2 letters). But a number of tuples have a third
element, e.g. (4, 17, 2) . Decoding these as full symbols produced
garbled output, so the third number was tested as a letter index into
the 2-letter symbol:

(4, 17, 2)->Br, take letter #2->"r"
(2, 1, 2) ->Li, take letter #2->"i"
(3, 13, 1)->Al, take letter #1->"A"

This let single letters be pulled out of two-letter element symbols —
necessary because plain English text needs individual letters, not
2-character blocks, at most positions.

The literal tokens map directly:

  • _ → space
  • { / } → literal brace (flag wrapper)

Decoding

Running the full tuple stream through this scheme (see decode.py )
produces:

BrONCo{MY FAVOriTe MeSSAGeS HAVe AT eleMeNT OF S[?9,6?]PriSe}

Nearly the entire message resolves cleanly into a coherent, on-theme
sentence — fitting, since the challenge itself hinges on the "surprise"
of finding a message hidden in periodic table coordinates:

MY FAVORITE MESSAGES HAVE AN ELEMENT OF SURPRISE

Two positions didn't resolve automatically:

  1. (9, 6) — there is no period 9 on the periodic table (periods only go 1–7), so this tuple has no valid lookup. Context makes the intended letter obvious: the word is S_PRISE , which can only sensibly be SURPRISE , so the missing letter is U .
  2. The word decoded as AT reads awkwardly; grammatically the sentence wants AN ("have an element of surprise"). This points to a likely transcription slip in the source tuple for that word (a digit that should differ from what was copied), rather than an error in the decoding scheme itself — every other tuple in the file resolves without issue.

Both anomalies are localized to specific known tuples and don't affect
confidence in the rest of the decode, which is fully self-consistent
across ~50 other tuples.

Final Flag

bronco{MY_FAVORITE_MESSAGES_HAVE_AN_ELEMENT_OF_SURPRISE}

(Spaces converted to underscores to match standard flag formatting
conventions.)

Tools

decode.py — standalone Python decoder implementing the scheme above.

Usage:

python3 decode.py secret.txt

Full source:

#!/usr/bin/env python3
"""
Periodic Table Cipher Decoder
==============================

Encoding scheme discovered from secret.txt:

- Each token is either:
* A literal character: { } _
* A tuple (period, group)-> full element symbol (1 or 2 letters)
* A tuple (period, group, index) -> a single letter from that symbol,
 where index is 1-based (1st or 2nd letter)

- "_" represents a space / underscore separator in the final message
- "{" and "}" are literal brace characters (used for the flag wrapper)

Example:
(4,17) (2,16) (2,15) (4,9)->Br O N Co->"BrONCo" -> "Bronco"

Usage:
python3 decode.py secret.txt
"""

import re
import sys

# Standard periodic table symbols by (period, group), IUPAC 1-18 group numbering.
# Only positions that are actually populated on a real periodic table are included.
PERIODIC_TABLE = {
(1, 1): 'H',(1, 18): 'He',

(2, 1): 'Li', (2, 2): 'Be', (2, 13): 'B',(2, 14): 'C',(2, 15): 'N',
(2, 16): 'O', (2, 17): 'F', (2, 18): 'Ne',

(3, 1): 'Na', (3, 2): 'Mg', (3, 13): 'Al', (3, 14): 'Si', (3, 15): 'P',
(3, 16): 'S', (3, 17): 'Cl', (3, 18): 'Ar',

(4, 1): 'K',(4, 2): 'Ca', (4, 3): 'Sc', (4, 4): 'Ti', (4, 5): 'V',
(4, 6): 'Cr', (4, 7): 'Mn', (4, 8): 'Fe', (4, 9): 'Co', (4, 10): 'Ni',
(4, 11): 'Cu', (4, 12): 'Zn', (4, 13): 'Ga', (4, 14): 'Ge', (4, 15): 'As',
(4, 16): 'Se', (4, 17): 'Br', (4, 18): 'Kr',

(5, 1): 'Rb', (5, 2): 'Sr', (5, 3): 'Y',(5, 4): 'Zr', (5, 5): 'Nb',
(5, 6): 'Mo', (5, 7): 'Tc', (5, 8): 'Ru', (5, 9): 'Rh', (5, 10): 'Pd',
(5, 11): 'Ag', (5, 12): 'Cd', (5, 13): 'In', (5, 14): 'Sn', (5, 15): 'Sb',
(5, 16): 'Te', (5, 17): 'I', (5, 18): 'Xe',

(6, 1): 'Cs', (6, 2): 'Ba', (6, 3): 'La', (6, 4): 'Hf', (6, 5): 'Ta',
(6, 6): 'W',(6, 7): 'Re', (6, 8): 'Os', (6, 9): 'Ir', (6, 10): 'Pt',
(6, 11): 'Au', (6, 12): 'Hg', (6, 13): 'Tl', (6, 14): 'Pb', (6, 15): 'Bi',
(6, 16): 'Po', (6, 17): 'At', (6, 18): 'Rn',

(7, 1): 'Fr', (7, 2): 'Ra', (7, 3): 'Ac', (7, 4): 'Rf', (7, 5): 'Db',
(7, 6): 'Sg', (7, 7): 'Bh', (7, 8): 'Hs', (7, 9): 'Mt', (7, 10): 'Ds',
(7, 11): 'Rg', (7, 12): 'Cn', (7, 13): 'Nh', (7, 14): 'Fl', (7, 15): 'Mc',
(7, 16): 'Lv', (7, 17): 'Ts', (7, 18): 'Og',
}

# Matches: {, }, _, or a tuple like (4, 17) / (4, 17, 2)
TOKEN_RE = re.compile(r'\{|\}|_|\(\s*\d+\s*,\s*\d+\s*(?:,\s*\d+\s*)?\)')
TUPLE_RE = re.compile(r'\(\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d+)\s*)?\)')


def decode(text: str) -> str:
output = []
for match in TOKEN_RE.finditer(text):
token = match.group(0)

if token in ('{', '}'):
output.append(token)
continue
if token == '_':
output.append(' ')
continue

tm = TUPLE_RE.match(token)
period, group, index = tm.groups()
period, group = int(period), int(group)
symbol = PERIODIC_TABLE.get((period, group))

if symbol is None:
output.append(f'[?{period},{group}?]')
continue

if index is None:
output.append(symbol) # full symbol
else:
idx = int(index)
if 1 <= idx <= len(symbol):
output.append(symbol[idx - 1])# single letter
else:
output.append(f'[?{symbol}#{idx}?]')

return ''.join(output)


def main():
if len(sys.argv) != 2:
print(f"Usage: {sys.argv[0]} <secret.txt>")
sys.exit(1)

with open(sys.argv[1], 'r') as f:
raw = f.read()

result = decode(raw)
print("Decoded message:")
print(result)


if __name__ == '__main__':
main()

Key Takeaways

  • When a cipher's tuples don't line up with an obvious 1:1 substitution, try mapping them onto a real-world reference table (periodic table, keyboard layout, ASCII table, book/page/line, etc.) rather than assuming a purely mathematical transform.
  • Confirming a scheme against a small, checkable prefix (here, the bronco{ flag wrapper) before decoding the whole message saves a lot of wasted effort chasing the wrong theory.
  • When a decode is 95% clean and self-consistent, isolated garbage characters are usually a transcription/OCR artifact in the source data rather than a flaw in the scheme — worth verifying against the raw file before assuming the cipher logic itself is wrong.

Published: Sun, 12 Jul 2026 16:56:25 +0000 | Source: DEV Community

NGB Platform v1.3.1: CRM Demo Built with NuGet and npm Packages

NGB Platform v1.3.1 is released.

This release is an important milestone for the project because it shows something I wanted to prove from the beginning:

NGB is not a single vertical application.

NGB is a platform for building vertical business systems.

The new CRM demo is not intended to be a Salesforce replacement or a full CRM product.

It is a demonstration of how a completely different business vertical can be built on top of NGB Platform using published packages.

Live demo:

https://crm-demo.ngbplatform.com

Release:

https://github.com/ngbplatform/NGB/releases/tag/v1.3.1

GitHub repository:

https://github.com/ngbplatform/NGB

What changed in v1.3.1

The main highlight of this release is the new NGB CRM demo .

But the more important part is how it was built.

The CRM demo consumes NGB Platform as packages:

  • NGB.Platform.* NuGet packages
  • @ngbplatform/ui npm package

That means the CRM demo does not need to clone the platform source projects directly.

This is a major step for NGB because it moves the project closer to a real platform model:

  • platform core
  • published backend packages
  • published UI package
  • vertical applications built on top

Why CRM?

Previous NGB demos were closer to traditional business and ERP-like domains:

  • Property Management
  • Trade
  • Agency Billing
  • Accounting and reporting scenarios

Those demos are useful, but they can create the wrong impression.

It may look like NGB is only about accounting-heavy systems.

That is not the goal.

NGB is designed for business applications in general:

  • catalogs
  • documents
  • lifecycle actions
  • posting semantics
  • reference registers
  • operational registers
  • accounting registers
  • reports
  • auditability
  • metadata-driven UI

Some verticals need accounting.

Some do not.

CRM is a good example of a vertical that can use the NGB model without using the accounting engine at all.

CRM demo without accounting

The CRM demo does not use the accounting engine.

It also does not use operational accounting-style movements.

That is the point.

The demo is built around CRM concepts:

  • accounts
  • contacts
  • products
  • opportunity stages
  • lead intake
  • lead qualification
  • lead conversion
  • opportunity updates
  • quotes
  • activity logs
  • sales pipeline reporting
  • activity reporting
  • quote register reporting
  • lead conversion funnel

The CRM flow is document-driven.

A lead is captured as a document.

Qualification is a document.

Lead conversion is a document.

Opportunity updates are documents.

Quotes are documents with reusable product lines.

Activities are documents.

When documents are posted, they update read-side projections through reference registers.

Reports are built from those projections.

So even without accounting, the same NGB platform ideas still apply:

source documents → posting boundary → register projections → reports

Why packages matter

Before this release, it was possible to build verticals inside the repository.

That is useful for development, but it is not enough to prove a platform.

A real platform should be usable from the outside.

With v1.3.1, NGB now has public package families:

  • NuGet packages for backend platform capabilities
  • npm package for the UI framework

This makes the platform boundary much clearer.

A vertical solution can depend on NGB Platform packages instead of directly depending on platform source projects.

That is important for several reasons.

First, it validates the public API surface.

If a vertical can only work by referencing internal source projects, the platform boundary is not real yet.

Second, it makes versioning more meaningful.

A vertical can target a specific platform version.

Third, it makes the project easier to understand.

Developers can look at CRM as an example of how to consume NGB Platform rather than digging through the entire platform implementation first.

Current live demos

NGB now has multiple live demos:

Each demo shows a different type of vertical application built on the same platform foundation.

Final thoughts

The CRM demo is intentionally not a full CRM product.

That is not the purpose.

The purpose is to show that NGB Platform can be used to build vertical business applications of different types and different complexity levels.

With v1.3.1, NGB takes an important step toward that platform model:

published packages, reusable UI, and a new vertical built as an external consumer.

Release:

https://github.com/ngbplatform/NGB/releases/tag/v1.3.1

Repository:

https://github.com/ngbplatform/NGB

Published: Sun, 12 Jul 2026 16:54:47 +0000 | Source: DEV Community

TOON: the JSON trick that cut my LLM prompt tokens by half

I run a small developer tools site, and the whole thing started with one annoyance: sending JSON arrays to GPT or Claude wastes a ridiculous amount of tokens on repeated keys.

Look at a typical API response:

[

{ "id": 1, "name": "Alice", "role": "admin", "active": true },

{ "id": 2, "name": "Bob", "role": "editor", "active": true },

{ "id": 3, "name": "Carol", "role": "viewer", "active": false }

]

Three records, and the keys id, name, role, and active appear three times each. Now imagine 500 records. You are paying your LLM provider to read the same four words 500 times.

TOON is a compact format that declares the keys once, then lists values row by row:

users[3]{id,name,role,active}:

1,Alice,admin,true

2,Bob,editor,true

3,Carol,viewer,false

Same data. On my test sets this cuts tokens by 30 to 60 percent depending on how repetitive the data is. The bigger the array and the shorter the values, the bigger the saving, because keys make up a larger share of the payload.

The honest limitations

It only helps with arrays of similar objects. Deeply nested or mixed-shape data saves little or nothing.

The model needs one instruction line, something like: the following block is a compact table representation of JSON. Modern models handle it fine, but do not skip the hint.

If your prompt is mostly instructions and only a little data, the saving will not matter. This is for people stuffing hundreds of records into context.

The math

Say you push 10 million input tokens a month through GPT-4o at 2.50 dollars per million. If 60 percent of that is tabular JSON and TOON cuts it by 45 percent, you save around 6.75 dollars per million on that share, roughly 480 dollars a year. Not life changing at small scale, but it compounds fast at higher volume, and it also frees context window, which is sometimes worth more than the money.

I built a free converter that does JSON to TOON and back, shows a token estimate for both formats, and runs entirely in the browser: jsontoonpro.com. While building it I ended up adding about 50 other client-side tools (formatters, Base64, hash generators), but the TOON converter is still the reason the site exists.

Happy to answer questions about the format or share the test data I used for the percentages.
#llm #json #ai #webdev

Published: Sun, 12 Jul 2026 16:54:40 +0000 | Source: DEV Community

Building KunciMasuk, a Privacy-First SSH Launcher

KunciMasuk Sulaiman Misri

Hi Everyone. Approximately a month ago, I developed a Bash script to streamline server access for myself and my colleagues. The feedback was constructive and encouraging, and it motivated me to refine the concept into something more robust.

Today, I am releasing KunciMasuk, a free desktop application that reimagines that Bash script as a polished, user-friendly tool.

My dream

I have long been interested in desktop application development, but I wanted to build something simple yet purposeful. I deliberately avoided the ubiquitous to-do application, which felt cliché, and briefly considered a Point-of-Sale (POS) system before concluding that its scope was too ambitious for a side project.

As with any project, I began with system design and architecture planning. I initially evaluated Ionic, but it did not fit the use case.

I then considered Flutter, yet my lack of production experience with it made it impractical. After exploring discussions on Reddit and Stack Overflow, I noted the strong enthusiasm for Rust and Tauri.

While I am eager to explore both in the future, the learning curve was too steep for this release cycle. I ultimately selected Electron.js, capitalizing on my existing knowledge with React.

Database

For data persistence, I initially considered SQLite for its reliability and minimal footprint. However, for a lightweight SSH launcher, it felt like overkill.

I opted for a local JSON-based datastore instead. When a friend asked why KunciMasuk does not use cloud storage, the answer was straightforward: privacy. This application is designed to be local-first. All credentials and configuration data remain on the user's machine, ensuring complete confidentiality and zero external dependencies.

Move from React to TypeScript

With the architecture settled, I began development using VS Code, React.js, and Tailwind CSS.

Approximately twenty percent into the build, I decided to treat this project as an opportunity to learn TypeScript.

My approach was deliberate: I built each component in React, then consulted my AI mentor to explain TypeScript patterns and refactor the code accordingly. This method accelerated my learning without surrendering ownership of the codebase.

How I use AI to teach me about TypeScript

Had I fully automated the development with AI, I could have shipped within two days. Instead, I spent three weeks coding intentionally, debugging manually, and internalizing every architectural decision.

KunciMasuk may not be the most feature-rich SSH launcher available, but it is a product I built with full understanding of its internals , and that is a milestone I am proud of.

Following the release of version 0.1.0 to a trusted circle of peers and colleagues, I incorporated their feedback and iterated rapidly. I am now pleased to open version 0.1.4 to the public.

You can download KunciMasuk free of charge here:
Sulaiman KunciMasuk

** My goal is to make this project open source. But I don't have enough courage to publish it due to my TypeScript skills. I will open it later or sooner ;)

Published: Sun, 12 Jul 2026 16:53:41 +0000 | Source: DEV Community

Integrating .NET GC in your C++ application

.NET appears as large monolith where everything is working like a magic. But it is not, and even GC can be used outside of .NET runtime (obviously with caveat). When I was a kid, I always love disassemble things, to see how they works. Not always toys survive that exercise. But thanks to source control, .NET GC is safe from my hands. Hopefully lot of people like me, and will enjoy tearing down large system into pieces.

submitted by /u/kant2002
[link] [comments]

Published: 2026-07-12T16:25:04+00:00 | Source: Reddit - r/programming

WSJ Reports on 'Hard-line Activists Ramping Up for the War With AI'

The Wall Street Journal says "an intense 27-year-old activist who had been leading sit-ins at OpenAI to protest the dangers of AI" was just part of a larger movement. "The Bay Area's AI boom is drawing young disillusioned men and women to join the fight against it. They are upending their lives and leaving behind careers for think tanks, nonprofits and street protest groups." Their cause is now riding a surge of anti-AI backlash. Many Americans are souring on the technology amid mass layoffs, data center sprawl, reports of chatbot-fueled attacks by unstable users and hacking tools that have panicked cybersecurity professionals. Seventy percent of U.S. adults believe AI will cost jobs, and 55% believe it will do more harm than good in their daily lives, according to a recent Quinnipiac University poll. But for activists on the front lines, the driving fear is often more dramatic: human extinction. They cling to dire predictions, like Geoffrey Hinton's. The Nobel laureate, dubbed the "godfather of AI" for his work on artificial neural networks, warns of a 10% to 20% chance AI will wipe out humans. At its most extreme and troubling end, some believe they must stop an AI apocalypse by any means necessary. In April, an unknown assailant fired 13 shots at the home of an Indianapolis councilman, leaving a note: "no data centers." That same month, authorities arrested a 20-year-old Texas college student for an attack on OpenAI CEO Sam Altman's home in San Francisco, and charged him with attempted murder and arson. The student was carrying an anti-AI document with a section on "our impending extinction," according to a federal criminal complaint. He has pleaded not guilty and his lawyers have said his actions appear to have been driven by an "acute mental-health crisis, not a desire to harm."

Read more of this story at Slashdot.

Published: 2026-07-12T16:34:00+00:00 | Source: Slashdot