#H0Hackathon
a duopreneurship built
dekawowow.com in a weekend w
@v0.dev design
@vercel.com hosting
@awscloud.bsky.social databases+ai

90+ builds only 2 @typescriptlang.org errors, both inherited from human contributed code + fixed quickly by v0

builder.aws.com/content/3FpK...

#H0Hackathon #listentotheheroes
July 1, 2026 at 7:15 PM
Built on:
— AWS DynamoDB + Streams + Lambda
— Vercel + Next.js 16
— Anthropic Claude
— Epicure Core embeddings

26 days until the deadline.

A lot more coming.

#H0Hackathon #buildinpublic #AWS #Vercel #Claude #MCAS
June 3, 2026 at 10:48 PM
Building Fable for #H0Hackathon 🌿

An allergen-aware recipe app for the 250M+ people food apps forget about.
Epicure, AWS DynamoDB, Claude
48 hours in and it's already generating restaurant-quality allergen-safe recipes 👇

🔗 v0-allergen-recipe-app.vercel.app

#buildinpublic #AWS #Vercel
Fable - Allergen-Aware Recipe Discovery
Find safe, flavour-matched recipes tailored to your dietary needs. Fable helps people with food allergies discover delicious meals with confidence.
v0-allergen-recipe-app.vercel.app
May 29, 2026 at 11:46 AM
3 years ago my MSc flagged this exact problem.

Last week someone else solved half of it.

So I built the other half. 🧵

#H0Hackathon #buildinpublic
June 3, 2026 at 10:48 PM
Keyless by Default: Securing FarmOps Desk without a Single Static Secret Part of the H0: Hack the Zero Stack submission. See the project on Devpost . Every hackathon submission that uses AWS from V...

#h0hackathon #aws #vercel #security

Origin | Interest | Match
Keyless by Default: Securing FarmOps Desk without a Single Static Secret
Part of the H0: Hack the Zero Stack submission. See the project on Devpost. Every hackathon...
dev.to
June 28, 2026 at 12:38 AM
"Why I chose DynamoDB for a Global Gotham Community racer | H0 Hackathon" by Mark Ramrattan

#amazon-dynamodb #vercel #nextjs #aws-sdk #hackathon
Why I chose DynamoDB for a Global Gotham Community racer | H0 Hackathon
Gotham Knights: building real leaderboards with DynamoDB on Vercel | #H0Hackathon
community.aws
June 21, 2026 at 9:00 AM
It's done. Fable is submitted. From 512 tests and 2 Lambdas to 867 tests, 7 tables, 4 Lambdas, 7 languages, and Safe Foods Mode for MCAS users who every other app fails. Try it: v0-allergen-recipe-app.vercel.app I created this content for the purposes of entering the H0 Hackathon. #H0Hackathon
Building Fable for #H0Hackathon 🌿

An allergen-aware recipe app for the 250M+ people food apps forget about.
Epicure, AWS DynamoDB, Claude
48 hours in and it's already generating restaurant-quality allergen-safe recipes 👇

🔗 v0-allergen-recipe-app.vercel.app

#buildinpublic #AWS #Vercel
Fable - Allergen-Aware Recipe Discovery
Find safe, flavour-matched recipes tailored to your dietary needs. Fable helps people with food allergies discover delicious meals with confidence.
v0-allergen-recipe-app.vercel.app
June 15, 2026 at 10:12 PM
"Never oversell, anywhere on Earth — building a global drop platform on Amazon Aurora DSQL" by Yuuki Yamashita

#amazon-aurora #vercel
Never oversell, anywhere on Earth — building a global drop platform on Amazon Aurora DSQL
Built for the H0 hackathon (Hack the Zero Stack with Vercel v0 and AWS Databases). #H0Hackathon
community.aws
June 2, 2026 at 8:00 AM
"Can retrieval agents like ChatGPT and Perplexity read your website? Agentis Lux sees what they see." by La Shara Cordero

#agentic-ai #vercel #amazon-dynamodb #learn #aws-community-builders
Can retrieval agents like ChatGPT and Perplexity read your website? Agentis Lux sees what they see.
I created Agentis Lux for the purposes of entering H0 Hackathon (Vercel + AWS Databases). #H0Hackathon See Agentis Lux's Devpost.com entry.
community.aws
July 3, 2026 at 8:00 PM
"Building CropChain OS: An AI-Powered Financial Operating System for India's FPOs with AWS Aurora DSQL & Vercel" by Naren Yashwanth N

#vercel #amazon-aurora #react
Building CropChain OS: An AI-Powered Financial Operating System for India's FPOs with AWS Aurora DSQL & Vercel
Discover how we designed and built a cloud-native platform that helps Farmer Producer Organizations (FPOs) optimize mandi decisions, automate post-harvest operations, and deliver transparent farmer settlements #H0Hackathon
community.aws
June 29, 2026 at 7:30 AM
3 years ago my MSc flagged this exact problem. Last week someone else solved half of it. So I built the other half. 🧵 #H0Hackathon
June 3, 2026 at 10:47 PM
# Treat: A Global Gifting Platform Empowering Local Businesses This project was built for the purposes of entering the H0 Hackathon. 🚀 Live at: https://www.sendatreat.app/ The Problem Gifting is...

#h0hackathon

Origin | Interest | Match
# Treat: A Global Gifting Platform Empowering Local Businesses
This project was built for the purposes of entering the H0 Hackathon. 🚀 Live at:...
dev.to
June 26, 2026 at 5:13 AM
✍️ New blog post by Seth David Gyimah

# Treat: A Global Gifting Platform Empowering Local Businesses

#h0hackathon
# Treat: A Global Gifting Platform Empowering Local Businesses
This project was built for the purposes of entering the H0 Hackathon. 🚀 Live at:...
dev.to
June 26, 2026 at 4:54 AM
Building a passwordless, Gemini-advised dashboard on the "zero stack"
_I built Kajota Pulse and wrote this article as my entry for the **AWS × Vercel "H0: Hack the Zero Stack"_ * hackathon (**#H0Hackathon**). Live app: kajota-pulse.vercel.app · Code: github.com/KaJota-inc/kajota-pulse* ## The problem nobody builds for Across African micro-commerce, "co-sellers" buy stock from wholesalers and resell to their network for a markup. There's a whole industry of tools for _writing the listing_. There's almost nothing for the question that actually decides whether a co-seller makes money: **what should I stock this week?** So we built **Kajota Pulse** — a Bloomberg-terminal-style dashboard that watches the marketplace and, in one click, tells a seller what to buy and why. It's the "monitor" pillar of a three-app stack: **Coach** drafts the listing, **Pulse** says what to stock, **Mesh** settles the deal on-chain. The hackathon constraint was the fun part: build it on the **zero stack** — Vercel for compute, an AWS database for state, no servers to manage. Here's what that actually took. ## Architecture in one breath Next.js 16 (App Router) on Vercel → **AWS Aurora Serverless v2 (PostgreSQL)** for every number on the dashboard → **Gemini 2.5 Flash** for the advice → **MongoDB Atlas Database Triggers** streaming the real Kajota catalogue in. Five Postgres tables, two SQL views, two Gemini endpoints, one ingest endpoint. No VPC, no connection pooler, no server. The interesting engineering wasn't the UI. It was three things that don't show up in tutorials. ## Gotcha 1 — Serverless + Aurora forces _passwordless_ auth (and that's a feature) We provisioned Aurora Serverless v2 with the new internet-access-gateway networking model so Vercel could reach it without VPC plumbing. Then every password connection failed with `PAM authentication failed`. The new model **mandates IAM database authentication** — and as a bonus, it doesn't support the RDS Data API either. So instead of a stored password, every connection mints a short-lived (15-minute) IAM auth token: const signer = new Signer({ hostname, port, username, region, credentials }); pool = new Pool({ host, port, user, database, password: () => signer.getAuthToken(), // fresh token at each handshake ssl: { rejectUnauthorized: false }, }); `pg` supports an async `password` callback, so this is clean. And the security property is genuinely nice: **there is no long-lived database password anywhere** — not in Vercel, not in the repo, not in a secret manager. ## Gotcha 2 — Vercel's Lambda shadows your AWS credentials This one cost an hour. The IAM signer needs AWS credentials to sign the token. We set `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` in Vercel… and it still failed. Vercel functions run on Lambda, and **the Lambda runtime injects its _own_ execution-role `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`**, which shadow yours. The signer was minting tokens with the wrong (no-`rds-db:connect`) identity. The fix: use custom env names and pass them explicitly to the signer. function signerCredentials() { const accessKeyId = process.env.PULSE_AWS_ACCESS_KEY_ID; const secretAccessKey = process.env.PULSE_AWS_SECRET_ACCESS_KEY; return accessKeyId && secretAccessKey ? { accessKeyId, secretAccessKey } : undefined; } A dedicated IAM user with _only_ `rds-db:connect` on the cluster's dbuser resource, surfaced under `PULSE_AWS_*`, and the shadowing problem disappears. ## Gotcha 3 — Real change-streams are messier than seed data The dashboard is only as good as its data, so we wired **three MongoDB Atlas Database Triggers** on the real Kajota collections (`products`, `cosell_products`, `orders`). Each trigger POSTs its change event to `/api/ingest`, which upserts into Aurora. Hooking this to _production_ data immediately surfaced three bugs that a seed file would never reveal: 1. **Extended JSON.** Atlas serializes change events as EJSON, so a Mongo `_id` arrives as `{"$oid":"…"}` and a price as `{"$numberInt":"9500"}` — not as a string and a number. Without decoders you get `id="[object Object]"` and `price=NaN` in your database. Two small helpers (`ejsonId`, `ejsonNum`) fixed it. 2. **Collection naming.** The real collection is `cosell_products` (underscore), but our router matched `cosellproducts`. Events silently fell through as "ignored." Now the router normalizes names. 3. **Foreign keys vs. event ordering.** Change-stream events arrive _out of order_ — a co-sell listing can land before the product it references. Our FK constraints silently dropped those rows. The fix is counterintuitive but correct for event-streamed ingestion: **drop the FKs** and treat each table as an independent projection. None of these reproduce against fixtures. They only appear when real production writes hit your pipeline — which is exactly why we wired it to live data instead of demoing on a seed. ## The feature that makes it an advisor, not a dashboard A dashboard shows you numbers and makes you do the synthesis. We wanted Pulse to _answer the question_. So `/api/recommend` pulls the live signals — trending demand, category margins, competitor stock-outs, price position — and hands them to Gemini 2.5 Flash with a **structured-output schema** : > **Organic Shea Butter** → _Stock 10–15 units before the weekend._ > _"+27 favorites, sits in the high-margin Beauty category (18%), and a competitor just ran out of a similar cream."_ Two details that matter for a demo that can't break: * **Structured JSON output** (`responseMimeType: "application/json"` + a `responseSchema`) means we render a clean ranked list, not parse prose. * **A deterministic fallback.** If Gemini is ever unavailable, a heuristic ranking (demand × margin × opportunity) runs instead, so the card is never empty in front of a judge — or a customer. ## What "zero stack" actually bought us * **No servers.** Vercel functions for the API, Aurora for state. Nothing to patch or scale. * **Scale to zero.** Aurora Serverless v2 idles to zero ACUs; the cold-start (~8s) is the only tax, and it's easy to pre-warm. * **One-command verification.** `node scripts/verify-live.mjs` checks the live landing page, the Aurora badge, both Gemini endpoints, the ingest auth gate, and a real IAM-authenticated row count — 5/5. Anyone can run it. ## Takeaways 1. The new Aurora networking model _forces_ passwordless IAM auth. Lean into it — it's a better security posture than a stored password, and once you handle the Lambda credential-shadowing quirk, it's a few lines. 2. If you want to know whether your data pipeline works, point it at _real_ data, not a seed. The three ingestion bugs we found were all invisible until production writes hit them. 3. For an LLM feature in a live demo, use structured output and always ship a deterministic fallback. "Never empty" beats "usually impressive." **Live:** kajota-pulse.vercel.app · **Code:** github.com/KaJota-inc/kajota-pulse · Built on Next.js 16 (Vercel) + Aurora Serverless v2 + Gemini 2.5 Flash. ### Adapting this post * **Dev.to / Hashnode / Medium:** publish as-is (add a cover image — the demo GIF works). * **LinkedIn:** lead with "The new Aurora model won't let you use a password — here's why that's a good thing," then the three gotchas, then the link. * **X/Bluesky thread:** one gotcha per post (passwordless IAM → Lambda shadowing → EJSON/FK), close with the live link + GIF.
dev.to
June 29, 2026 at 11:54 PM
Building Innward: A B2B Hospitality Operating System with Vercel and Amazon Aurora
This blog post is created for the purposes of entering the **Hack the Zero Stack with Vercel v0 and AWS Databases** hackathon. #H0Hackathon # Building Innward: A B2B Hospitality Operating System with Vercel and Amazon Aurora The hospitality industry is notorious for relying on "legacy" software—clunky, slow, and disconnected. For the **Hack the Zero Stack** hackathon, I set out to build **Innward** , a modern, AI-ready Property Management System (PMS) that proves you can build enterprise-grade B2B tools in record time using Vercel v0 and AWS Databases. ## The Vision: Moving Beyond the Spreadsheet Hotel managers don't just need a place to store "Room 101: Occupied." They need to solve the **"Hidden Math"** of revenue management. This means: 1. **Relational Complexity:** Linking dates, room groups, and individual stays. 2. **Dynamic Pricing:** Deriving rates based on occupancy and logic-based rules. 3. **Market Intelligence:** Real-time benchmarking against competitors. ## The "Zero Stack": Vercel + Amazon Aurora To handle this complexity, I chose **Amazon Aurora PostgreSQL (Serverless v2)**. ### Why Aurora for B2B? In a B2B SaaS environment, data isolation and relational integrity are non-negotiable. Aurora provided the robust relational power needed to join complex pricing tables while scaling automatically as more hotels (tenants) join the platform. ### The Zero-Secret Architecture One of the most rewarding parts of this build was implementing the **AWS RDS Signer**. Following the "Zero Stack" philosophy, I moved away from static database passwords. Innward uses IAM-based authentication to communicate between Vercel and AWS. By utilizing the `@aws-sdk/rds-signer`, the application generates short-lived tokens on the fly. This means even if an environment variable were leaked, the database remains locked tight. // lib/db.ts snippet const signer = new Signer({ credentials: awsCredentialsProvider({ roleArn: process.env.AWS_ROLE_ARN!, clientConfig: { region: process.env.AWS_REGION }, }), region: process.env.AWS_REGION, hostname: process.env.PGHOST!, username: process.env.PGUSER || "postgres", port: 5432, }); const pool = new Pool({ password: () => signer.getAuthToken(), ssl: { rejectUnauthorized: false }, }); ## Scaffolding with v0: Designing for Information Density B2B users don't want "simple"—they want **clarity**. I used **v0** to scaffold a high-information-density UI using Next.js 15 and Shadcn. ### The Custom Gantt Grid A highlight of the project is the **Reservation Timeline**. Most calendar libraries fail at showing "Half-Day" turnovers (where one guest leaves at 11 AM and another arrives at 3 PM). I used v0 to build a custom CSS Grid implementation with horizontal insets, accurately reflecting the physical reality of hotel room turnovers. ### Financial Volatility with Candlestick Charts Innward includes a background worker built with **Playwright** that scrapes competitor rates. I visualized this data using **Recharts Candlestick components**. This gives revenue managers a "Volatility Pulse," showing the spread between the cheapest and most expensive rooms in their city. ## Overcoming the "Serverless Ceiling" During development, I hit a major hurdle: the **Vercel 300s timeout**. Scraping market data for multiple cities across a 14-day window was a heavy task. I solved this by building a **"Timeout-Aware" algorithm**. The sync loop monitors its own execution time. If it reaches 280 seconds, it stops gracefully, saves a "checkpoint" to Aurora, and returns a partial success response. The next Vercel Cron run simply picks up where it left off. ## B2B Granular IAM via JSONB Security is the heart of B2B software. I leveraged PostgreSQL’s **JSONB** capabilities to store a map of 15+ granular permission keys per staff member. This allowed for a highly flexible "Manage Access" UI where hotel owners can toggle specific permissions (like `revenue.kpis` or `stays.check_in`) without needing a new database column for every feature. Building with the Zero Stack has been an eye-opener. The speed of Vercel combined with the enterprise reliability of AWS Databases allowed me to focus 100% on solving the business logic of hospitality. **Check out the project:** https://aurastay-pms-build-five.vercel.app/ **Built with:** Next.js 15, Vercel v0, Amazon Aurora PostgreSQL. #H0Hackathon #Vercel #AWS #FullStack #NextJS #PostgreSQL
dev.to
June 29, 2026 at 9:55 PM
How I Built a Civic Traffic App on DynamoDB + Vercel — ChowkChakra (#H0Hackathon)
_I built this project as my submission for the H0 Hackathon — Hack the Zero Stack with Vercel v0 and AWS Databases. This post covers how I designed and built ChowkChakra using Amazon DynamoDB and Vercel. #H0Hackathon_ ## The Problem I Was Trying to Solve Pune has a traffic problem, and it's not just congestion — it's a communication gap. Citizens spot issues all the time: a signal that's been stuck on red for 20 minutes, a pothole causing lane merges at a busy junction, waterlogging after rain that backs up an entire ward. But they have nowhere to formally report it. WhatsApp groups, Twitter complaints that go nowhere, maybe a helpline call that logs nothing. On the other side, traffic officers are managing dozens of junctions with zero live signal about what's actually failing _right now_. They're reactive by default — they find out about problems when someone calls, or when they drive past. I wanted to close that loop. ChowkChakra is the result: a two-interface system where citizens report problems in real time, officers act on them through a command center, and citizens get notified when the issue is actually resolved — with a verification mechanism to confirm it. The name: _Chowk_ = intersection in Marathi/Hindi. _Chakra_ = cycle. The cycle from report → officer action → citizen verification → resolution. ## The Stack * **Frontend/backend** : Next.js 15 (App Router), deployed on Vercel * **Database** : Amazon DynamoDB — the only data store, 7 tables * **Storage** : AWS S3 for photos/videos from citizens * **AI** : Google Gemini 1.5 Pro (function calling, vision) + Gemini Live API (streaming audio) * **Maps** : Mapbox GL JS * **Live data** : HERE Traffic API (jam factor), OpenWeatherMap (weather penalty) * **Email** : SendGrid * **Push notifications** : Web Push API / VAPID ## Why DynamoDB? This was a deliberate call, not a default. ChowkChakra is fundamentally a **high-throughput write system** with mostly key-based reads. Citizens submitting reports need instant acknowledgment — no joins, no query planning. Officers querying a junction's tags need single-digit millisecond reads, not a `SELECT ... JOIN` that can degrade under concurrent load. The access patterns are well-defined and mostly PK-based. That's the DynamoDB sweet spot. Here's what I actually did with it: ### 7 tables, each with a purpose cc-junctions → junction registry + risk scores cc-tags → active incident tags (the hot table) cc-tags-history → resolved/archived tags (separate cold table) cc-push-subs → citizen push subscriptions cc-verifications → citizen verification votes cc-sessions → bot config + WebSocket sessions cc-media-queue → async media processing log Separating `cc-tags` from `cc-tags-history` was the most important schema decision. The active tags table stays lean. Historical queries only touch the archive table. No single-table design that would force a full-table scan to separate resolved from active records. ### Read-time SLA penalty (no background jobs) Different incident types carry different SLA windows — accidents: 1 hour, signal failure: 2 hours, congestion: 4 hours. Instead of running a cron that writes updated penalty scores to the database every few minutes, I calculate the penalty at read-time: const isOverdue = tag.slaDeadline ? Date.now() > new Date(tag.slaDeadline).getTime() : false; const effectiveRiskScore = isOverdue ? junction.chainRiskScore + 15 : junction.chainRiskScore; Zero write overhead. The DB stays clean. The overdue status is always accurate because it's computed from the actual current time, not a batch job's last run. ### Atomic tag resolution with TransactWriteCommand When an officer approves a resolve, three things need to happen together or not at all: 1. Update the tag status in `cc-tags` 2. Write the archive record to `cc-tags-history` 3. Increment `resolvedCount` on the junction in `cc-junctions` await dynamoClient.send(new TransactWriteCommand({ TransactItems: [ { Update: { TableName: "cc-tags", Key: { junctionId, tagId }, UpdateExpression: "SET #status = :resolved, resolvedAt = :now", ExpressionAttributeNames: { "#status": "status" }, ExpressionAttributeValues: { ":resolved": "RESOLVED", ":now": new Date().toISOString() } } }, { Put: { TableName: "cc-tags-history", Item: { ...tagRecord, archivedAt: new Date().toISOString() } } }, { Update: { TableName: "cc-junctions", Key: { junctionId }, UpdateExpression: "ADD resolvedCount :one", ExpressionAttributeValues: { ":one": 1 } } } ] })); If any one of these fails, the whole transaction rolls back. Officers can't accidentally create a state where a tag is marked resolved but the junction counter is wrong, or the archive record is missing. ### GSI for ward-level analytics The analytics dashboard needs to show congestion grades by ward — a grouped query. Instead of scanning the entire `cc-junctions` table and filtering client-side, I set up a GSI with `wardId` as the partition key and `chainRiskScore` as the sort key: const result = await dynamoClient.send(new QueryCommand({ TableName: "cc-junctions", IndexName: "wardId-chainRiskScore-index", KeyConditionExpression: "wardId = :ward", ExpressionAttributeValues: { ":ward": wardId } })); Each ward gets its own query, sorted by risk score descending. No table scan. ### DynamoDB TTL for automatic cleanup Instead of writing cron jobs to expire stale data, I set TTL on three tables: * `cc-push-subs`: 90 days (push subscriptions go stale fast) * `cc-sessions`: 24 hours (WebSocket sessions and bot config) * `cc-media-queue`: 7 days (async processing logs) DynamoDB purges these automatically. No Lambda, no scheduled job, no ops overhead. ## The Citizen PWA The citizen-facing interface is a mobile-first PWA (installable, offline-capable via service worker). Four ways to report: **1. Voice tap-to-speak** Tap the mic, describe the issue. Gemini AI parses the transcript and extracts junction name, issue type, and severity. GPS auto-tags the location. **2. Commute Mode (Gemini Live API)** Always-listening hands-free mode for reporting while driving. Audio streams to Gemini Live over a WebSocket in real time. No tapping required — you say "There's waterlogging at Kothrud junction" and it's logged. This was the hardest feature to build. Keeping a stable WebSocket connection in a mobile PWA while handling voice activity detection, audio buffering, and reconnect logic — without draining battery — took multiple iterations. The final implementation uses server-sent events to confirm each report back to the UI. **3. Photo + description** Upload a photo, type a description. Gemini Vision classifies the issue type. **4. Video with audio** Record a clip at the scene. Gemini processes both the visual and audio track. Every report lands in `cc-tags` with full metadata: junction ID, contributor user ID, SLA deadline, upvote count, and a list of contributor IDs for verification later. ### The verification loop When an officer resolves a tag, the app pushes a notification to every citizen who originally reported or upvoted it. They see a verification card in their Alerts tab: "Fixed?" → vote Fixed or Still Broken. If more than 50% of at least 3 responses say "Still Broken," the tag automatically reopens and the officer gets notified. This required careful DynamoDB design: * Contributor IDs are stored on the tag record at write time (so only real contributors get the verification card) * A separate `cc-verifications` table with composite key `tagId#userId` prevents double-voting * A 50-meter radius dedup check on report submission prevents 30 people creating 30 records for the same broken signal ## The Officer Command Center Officers use a desktop web app with five sections: **Live Heatmap** — Mapbox GL map of all junctions, color-coded by Gridlock Risk Score. Click any marker to see active tags. **Reports Review** — Two-panel workspace. Filter by ward, tag type, status (All / Open / Resolved / SLA Overdue). The detail panel shows live jam factor (HERE API), weather penalty (OpenWeatherMap), and the full incident tag list. Officers can generate PDF dispatch reports, assign tags to a specific officer, or approve and resolve. **Analytics Dashboard** — Daily report volume, ward congestion grades (A–F), root cause breakdown donut chart, top 5 recurring junctions. **Chakra AI Assistant** — Natural language chat backed by Gemini with function calling on DynamoDB data. Ask "Which junctions have overdue SLAs?" and it queries the database directly. **Social Bot Controls** — Automated X (Twitter) posts when a junction exceeds a risk threshold. Officers configure the threshold and can post manually too. ## What I Learned About DynamoDB The biggest shift: **design for access patterns, not for storage**. In a relational database, you design tables to represent entities and add indexes later when queries are slow. In DynamoDB, you start with the question "how will this data be queried?" and design the key schema around the answer. Every design decision I made — the hot/cold table split, the GSI on `wardId`, the `tagId#userId` composite key — came from mapping out the real access patterns first. The TTL feature in particular is underrated. For any data with a natural expiry (sessions, push subscriptions, processing logs), TTL is just free cleanup. The only cost is setting the attribute at write time. ## What's Next * **Multi-city support** : The junction registry and ward system are parameterized. Adding Nashik or Mumbai means importing junction data, not rewriting code. * **Predictive risk scoring** : Right now the Gridlock Risk Score is computed from active tag counts and types. Historical pattern analysis would allow predictive alerts. * **PMC SCOOT/ATCS integration** : Pune's Smart City project has signal controller data. Feeding that in would make the risk score much more precise. If you have questions about the DynamoDB schema design, the Gemini Live API streaming implementation, or the citizen verification loop — drop them in the comments. Happy to go deeper on any of it. _This post was created as part of my submission to the H0 Hackathon — #H0Hackathon_
dev.to
June 29, 2026 at 7:54 PM
Building a Legal AI Platform on Aurora DSQL and Vercel
I built this project as an entry for the H0: Hack the Zero Stack with Vercel v0 and AWS Databases Hackathon. #H0Hackathon **Inspiration** Justice moves slowly. I learned that firsthand as my family navigated a legal dispute. What struck me wasn't just the stress — it was that things were quite disorganised. Documents were paper-based or buried somewhere in emails. Updates came through WhatsApp messages. Simple documents took a really long time to draft and send. The system was fragmented and difficult to navigate. Companies like Harvey tackle document drafting well, but legal research tools and LLM wrappers can hallucinate case law, citing judgments that don't exist. I knew that if I was going to build something for this space, it had to be grounded in real, verifiable law. That led me to Laws Africa, which provides structured access to actual South African legislation and court judgments. I also noticed a problem that lawyers experience daily: the mechanical work. Logging into court portals to file a case. Hunting through OneDrive, Google Drive, and Dropbox for the right version of a document. Sifting through hundreds of emails to find something relevant to a matter. Onboarding a new client when the intake form is a PDF someone emails you. These are not AI problems. They are automation problems — and lawyers or their secretaries are doing them manually every single day. That became Agently. ## What Agently Does Agently is a legal workspace that handles the full lifecycle of a matter, from the moment a client submits an intake form to the day the case closes. **Matter Management.** Every client engagement lives in a structured matter. Documents, emails, notes, contacts, workflows, and AI conversations are all scoped to it. A lawyer can open a matter and immediately see everything relevant. **AI Agent with Real Legal Research.** The AI connects to Laws Africa's knowledge bases — South African legislation, court judgments, and municipal law — so research is grounded in actual legal sources, not guessed. Ask it to find case law on a topic and it returns real judgments with citations. **Document Work.** Upload PDFs, Word documents, and other files. The AI reads and analyses them, extracts key facts, and can generate new documents from scratch using a full rich text editor. A dedicated tabular review mode lets lawyers run structured clause-by-clause analysis across contracts. There is a separate document editor inside each matter so drafting and reviewing happen in the same place. **Browser Agent.** Agently can operate a real browser on behalf of the lawyer. It navigates court e-filing portals, fills every field using structured case data from the matter, uploads the required documents, and completes the submission — including logging in automatically using securely stored credentials. The lawyer watches the browser live and can intervene at any point. **Workflow Automation and Recording.** Lawyers can define multi-step workflows for document review, contract approval chains, and research tasks. They can also record their own browser sessions: navigating a portal, saving those actions as a reusable workflow. When they run it again, it replays deterministically. They can attach AI instructions so the agent adapts to different inputs or makes decisions at specific points during execution. **Client Intake.** When a prospective client fills out a form or emails the firm, they appear on the intake dashboard. Approving them triggers an AI agent that adds the client to the Rolodex, opens a new matter, responds to emails requesting further information and documents, drafts a letter of engagement on receipt, fills in case details from provided documents, and prepares a legal opinion summary to brief the lawyer. **Integrations.** Gmail, Google Drive, Google Docs, Google Calendar, Microsoft Outlook, Microsoft Teams, OneDrive, Dropbox, Xero, WordPress, and IMAP email. Agently connects to what the firm already uses rather than forcing them to migrate. ## How I Built It **Frontend.** React 19 with Vite and TailwindCSS v4. Tiptap powers the rich text editor for document drafting inside matters. React Router v7 handles navigation. The AI SDK's useChat hook streams Claude's responses directly to the client via SSE, with tool-call rendering so the lawyer can see what the agent is doing step by step. **Backend.** Express 5 deployed as a Vercel Serverless Function on Node 24 with Fluid Compute and a 300 second timeout. Twenty-four route modules cover the full surface area: chat, matters, files, workflows, browser automation, contracts, intake, voice transcription, PDF operations, email, calendar, and more. **Database — Amazon Aurora DSQL.** Aurora DSQL is the primary database. I chose it because it is PostgreSQL-compatible and fully serverless — no infrastructure to manage, no capacity to provision. The integration required real engineering. DSQL uses short-lived tokens with a 15 minute TTL rather than static credentials. I built a custom pool manager that refreshes tokens 2 minutes before expiry, deduplicates concurrent refresh requests so multiple in-flight queries do not each try to rotate the token simultaneously, and drains old pool connections gracefully without dropping live requests. DSQL has no support for SET LOCAL or session-level Postgres configuration, so traditional row-level security policies are not available. I replaced RLS with explicit firm_id scoping on every query. The firm_id comes only from Clerk's cryptographically signed JWT org ID — never from the request body. A lawyer from one firm cannot access another firm's data even if they manipulate the request. DSQL requires each DDL statement in its own transaction. Standard migration libraries send multiple statements in one transaction and fail. I wrote a custom runner that splits each SQL file on semicolons and executes each statement independently. Fifteen migrations handle the full schema including matters, documents, OAuth tokens, workflow runs, client intakes, firm settings, and usage counters. **AI Inference.** AWS Bedrock with Claude as the primary model, streamed to the client via Server-Sent Events using the AI SDK's streamText. A full tool-calling pipeline handles actions like file retrieval, legal research, web search, and browser control. Token usage is tracked per user per day in Aurora DSQL, with tier-based quotas enforced before any Bedrock invocation. **Browser Automation.** The browser agent uses Steel.dev to spin up a remote Chrome session in a cloud VM. Getting file uploads to work inside that remote browser required understanding something non-obvious: Chrome's CDP Input.setFileInputFiles requires the file to exist on the same machine as Chrome — which is Steel's VM, not the Node server. The pipeline: S3 GetObject fetches the file as a Buffer on the Node server. That buffer goes to the Steel Files API, which places it on the VM's filesystem and returns a path. That VM path is embedded in the agent prompt. The browser-use agent calls upload_file with that path and Chrome finds the file locally. The agent never has S3 credentials. The server handles all secure file transfer before the agent starts. The Python browser-use library with Playwright binaries exceeds Lambda's 250 MB deployment limit and court filings take 10 to 15 minutes, beyond Lambda's timeout. The solution was a containerised Python service on AWS ECS. The Vercel function creates the Steel session, pre-uploads documents, fires an async request to ECS with the task and CDP URL, then the frontend polls for events. When the system detects a court-related task, it looks up the firm's portal URL from Aurora DSQL, fetches login credentials from AWS Secrets Manager, resolves the relevant matter using a multi-factor scoring algorithm, fetches associated documents from S3, uploads them to the Steel VM, then builds a structured step-by-step agent prompt with every field value pre-filled. I also built a Playwright action recorder. A tracker script injected into the live browser via CDP captures clicks and form fills as console events and converts them to Playwright actions in real time. The recorded script streams back to the UI in real time. It can be replayed directly with no AI, handed to the AI with modification instructions, or improved automatically to replace fragile CSS selectors with robust ARIA-based selectors. **Storage and Auth.** Documents live in Amazon S3 keyed by firmId/matterId/docId, with presigned PUT and GET URLs generated server-side with a 5 minute expiry. AWS Secrets Manager stores court portal credentials per firm. Clerk handles authentication — org IDs extracted from signed JWTs become the firm_id on every database query. ## Challenges Getting browser-use onto ECS was complex. Lambda failed immediately due to package size and timeout limits. Containerising the Python service on ECS and coordinating it with the TypeScript Node server over HTTP took significant iteration, especially getting Playwright Core for recording and browser-use for execution to share the same Steel CDP session cleanly. The file upload pipeline required security consideration. Giving the agent S3 access was not acceptable. The Steel Files API solution emerged from understanding exactly how CDP file input works at the protocol level. Aurora DSQL's DDL transaction requirements were not obvious from the documentation. The first migration run failed with cryptic errors until I traced it to multi-statement transactions. Writing a custom migration runner was the fix. ## What I Learned Building for law forces a precision most apps never require. Every claim the AI makes, every document it generates, every form it fills — there is real professional consequence if it is wrong. That shaped every design decision: the lawyer-in-the-loop browser interface so the lawyer can always intervene, the Laws Africa integration for real citations instead of hallucinated ones, the explicit audit logging on every browser action. Aurora DSQL pushed me to think about data architecture more deliberately than any database has before. The constraints are real but they lead you to better, more auditable patterns. ## What's Next Real-time collaborative document editing. Native billing and time-tracking. Expanded court jurisdiction support. A client portal for document sharing and e-signatures. Deeper workflow automation so entire matter types — conveyancing, deceased estates, civil disputes — can run with minimal manual touchpoints. Try Agently: https://agently-ten.vercel.app _This post was written as part of my submission to the H0: Hack the Zero Stack with Vercel v0 and AWS Databases Hackathon. #H0Hackathon_
dev.to
June 29, 2026 at 3:56 PM
How I Built a Restaurant Waitlist App on Amazon Aurora DSQL That Cannot Double-Book a Table
I built TableTurn for the H0: Hack the Zero Stack hackathon — a restaurant waitlist and table management app on Amazon Aurora DSQL and Vercel. I created this post as part of my entry for the hackathon. #H0Hackathon Restaurants that cannot afford OpenTable or Yelp Guest Manager manage waitlists on paper or WhatsApp. TableTurn gives them a working alternative at $29/month. The hardest technical requirement was preventing double-booking. Two hosts seating two different parties at the same table at the same time must be impossible, not just unlikely. I used Aurora DSQL's serializable transactions to enforce this at the database level: await sql.begin(async (tx) => { const table = await tx`SELECT id, status FROM tables WHERE id = ${table_id} FOR UPDATE`; if (table[0].status !== "available") { throw new Error("Table just taken by another host"); } await tx`UPDATE tables SET status = 'occupied' WHERE id = ${table_id}`; await tx`UPDATE parties SET status = 'seated', table_id = ${table_id} WHERE id = ${id}`; }); I tested this directly with two curl requests targeting the same table. The first returned success. The second returned "Table just taken by another host." That is Aurora DSQL's serializable isolation working, not application-level checking that could race. Three things I had to learn the hard way: CREATE INDEX fails unless you use CREATE INDEX ASYNC, which returns a job_id and builds in the background. IAM auth tokens expire every 15 minutes, so token refresh logic is required in production. Aurora DSQL has no foreign keys, so I used TEXT for IDs and enforced relationships in the application layer. Live app: https://tableturn-steel.vercel.app GitHub: https://github.com/ekpenyongasuquo/tableturn
dev.to
June 17, 2026 at 11:51 AM