
Building KiroPad with Kiro Spec Mode — What Actually Happened
TL;DR: I rebuilt a real cross-device app (Electron + React Native + WebSocket) using Kiro's spec mode. It nailed the complex stateful logic. It silently introduced a data loss bug. Here's exactly what happened — code and all.
I built KiroPad twice.
The first time, I did it the way I always do: open editor, create files, write logic, debug for four hours, ship something that mostly works. The second time, I used Kiro's spec mode. The experience was different enough that I felt compelled to write this — not as a polished endorsement, but as an honest account of what spec-driven development looks like when you're building something real and non-trivial.
What KiroPad Actually Is
KiroPad is a cross-device scratchpad — a macOS desktop app (Electron) paired with a React Native/Expo mobile client, connected over WebSocket through a Cloudflare Tunnel. The point is zero-friction capture: type something on your Mac, it appears on your phone instantly. No cloud storage. No accounts. Just a persistent, local-first, encrypted session between your own devices.
Security features that mattered:
- Six-digit pairing code for initial device handshake
- Per-session tokens regenerated on reconnect
- Tunnel-scoped access — no open ports, no static IP exposure
By v2.0.0, the architecture had grown to include connection state management, a reconnection backoff loop, and a small auth layer. This is the version I rebuilt in spec mode.
Why I Tried Kiro Spec Mode
Honest answer: I wanted to see if it would break.
I've watched spec-driven AI tooling get overhyped for a year. The pitch is always "describe what you want, get working code." The reality is usually: generated scaffolding that compiles, tests that pass, and architecture you wouldn't have chosen. Kiro's spec mode felt different on paper — structured around requirements → design → tasks rather than raw prompting — so I decided to stress-test it on something I already knew inside out.
If Kiro couldn't handle KiroPad, I'd know exactly where it failed. If it could, I'd learn something about my own process.
Writing the Spec
Kiro stores specs at
.kiro/specs/<feature-name>/. Each spec has three files:1
2
3
4
5
6
7
.kiro/
specs/
kiropad-core/
requirements.md
design.md
tasks.md
steering.md
steering.md is project-wide context — I used it to define the stack, aesthetic constraints (amber/obsidian palette, no cyberpunk), and non-negotiables like "WebSocket must reconnect silently, user never sees raw error state."requirements.md excerpt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- Desktop app (macOS, Electron) as the host node
- Mobile client (React Native/Expo) as secondary node
- Real-time bidirectional text sync over WebSocket
- Pairing via six-digit code displayed on desktop, entered on mobile
- Session token issued post-pairing; mobile stores it in SecureStore
- Auto-reconnect with exponential backoff (max 5 retries)
## Security
- No plaintext transport of session tokens
- Pairing code expires after 90 seconds
- Each session generates a new token — no persistent credentials
## Non-functional
- Sub-200ms sync latency on local network
- Mobile app must remain usable offline (queue edits, sync on reconnect)
Writing this took 40 minutes. That's longer than I expected. You can't be vague — Kiro takes your words literally, which is both the power and the trap.
design.md excerpt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# KiroPad Design
## Architecture
- Electron main process runs WebSocket server on localhost:4242
- Cloudflare Tunnel exposes port 4242 under a stable subdomain
- React Native client connects to tunnel URL stored in SecureStore
## WebSocket Protocol
- Message types: `PAIR_REQUEST`, `PAIR_CONFIRM`, `TOKEN_ISSUE`, `CONTENT_SYNC`, `HEARTBEAT`
- All messages JSON-encoded with `{ type, payload, timestamp }`
## Auth Flow
1. Desktop generates 6-digit code, starts 90s countdown
2. Mobile sends `PAIR_REQUEST` with code
3. Desktop validates, issues `TOKEN_ISSUE` with signed session token
4. Subsequent connections: mobile sends token in first message, skip pairing
tasks.md excerpt
1
2
3
4
5
6
7
8
9
# Tasks
- [ ] Scaffold Electron main process with WebSocket server
- [ ] Implement pairing code generator and 90s expiry timer
- [ ] Build token issuance and validation logic
- [ ] Create React Native screen: pairing entry + live pad
- [ ] Implement offline queue with AsyncStorage
- [ ] Add reconnection loop with backoff
- [ ] Wire Cloudflare Tunnel startup into Electron app lifecycle
What Kiro Generated — And What It Got Wrong
Kiro worked from the spec top-down. It scaffolded the Electron main process first, then the WebSocket message handlers, then the React Native screens. The generated code was structurally sound — correct file layout, sensible separation of concerns.
Three things it got wrong, one of them badly:
1. Token signing.
The spec said "signed session token." Kiro implemented this with a simple
The spec said "signed session token." Kiro implemented this with a simple
crypto.randomBytes(32).toString('hex') — random, not signed. Technically a token, not actually signed. I had to step in and swap this for jsonwebtoken with a local secret.2. Reconnection backoff.
Kiro generated a flat retry interval (1000ms, always). The spec said "exponential backoff." This was a literal miss — the word was in the requirements. I rewrote:
Kiro generated a flat retry interval (1000ms, always). The spec said "exponential backoff." This was a literal miss — the word was in the requirements. I rewrote:
1
2
3
4
5
6
7
// Kiro generated this:
setTimeout(reconnect, 1000);
// I replaced with:
const delay = Math.min(1000 * Math.pow(2, retryCount), 30000);
setTimeout(reconnect, delay);
retryCount++;
3. Offline queue logic on mobile — this one hurt.
The spec said "queue edits, sync on reconnect." Kiro generated an array push on disconnect. It did not generate the drain logic — the part that replays queued edits in order when connection restores. This would have been a silent data loss bug. You'd think your edits were syncing. They weren't.
The spec said "queue edits, sync on reconnect." Kiro generated an array push on disconnect. It did not generate the drain logic — the part that replays queued edits in order when connection restores. This would have been a silent data loss bug. You'd think your edits were syncing. They weren't.
1
2
3
4
5
6
7
8
9
10
11
12
// What I had to add:
const drainQueue = async (ws) => {
while (offlineQueue.length > 0 && ws.readyState === WebSocket.OPEN) {
const edit = offlineQueue.shift();
ws.send(JSON.stringify({ type: 'CONTENT_SYNC', payload: edit }));
}
};
ws.onopen = () => {
setConnected(true);
drainQueue(ws);
};
What Kiro Got Right
The pairing flow was generated almost perfectly. The 90-second countdown timer, the code display on desktop, the
PAIR_REQUEST handler — all correct on first generation. This was the most complex stateful piece of the app, and I would have spent two hours on it manually. Kiro did it in the time it took me to read the output.The Electron app lifecycle integration was also cleaner than my original build. Kiro structured it with proper
app.whenReady() handling and a graceful shutdown hook that closes the WebSocket server before quit — something I had simply forgotten in v1.1
2
3
4
5
6
app.on('before-quit', () => {
if (wss) {
wss.clients.forEach(client => client.close());
wss.close();
}
});
Small thing. Correct thing. I hadn't done it.
The Spec Mode Mindset Shift
The biggest change wasn't the code quality — it was where I spent my time.
Without spec mode: I spent most of my time writing code, debugging, refactoring.
With spec mode: I spent most of my time writing the spec, reviewing generated code, and patching specific gaps.
With spec mode: I spent most of my time writing the spec, reviewing generated code, and patching specific gaps.
The second distribution is better for complex projects. Writing the spec forces you to think about edge cases before you have invested three hours in an implementation you don't want to throw away. I caught the offline-queue gap during
design.md — not during a 2am debug session after shipping.The uncomfortable part: spec mode makes your vagueness visible. Every handwavy phrase in your spec becomes a bug in the generated code. "Secure token" produced an insecure implementation because I didn't say exactly what secure meant. That's on me, not Kiro.
"Spec mode doesn't replace thinking. It amplifies it. If your design is wrong, Kiro builds your wrong design faster."
Kiro in the AWS Ecosystem
One thing worth noting for AWS builders: Kiro is not a standalone IDE. It's built to work within AWS's broader developer toolchain, and the integration surface is real.
I'm currently exploring moving KiroPad's Cloudflare Tunnel dependency to AWS CloudFront + API Gateway WebSocket APIs — which would make the architecture fully AWS-native and open the door for CB-eligible write-ups on WebSocket route management, connection state with DynamoDB, and Lambda authorizers for the token validation layer. The spec-mode workflow translates cleanly: the
design.md becomes an architecture doc, the tasks.md maps directly to a Kiro task list in the IDE.If you're building with Kiro and targeting AWS Community Builder content, spec files are an underrated artifact. They're honest documentation of how you designed something, not just what you built. That's a different quality of technical writing.
What I'd Tell You Before You Start
- Allocate spec writing time. It's not a 10-minute exercise. For a real project, budget as much time writing spec as you'd spend on initial scaffolding manually.
- Treat generated code as a first draft, not a final PR. Security-sensitive logic especially — review it line by line.
steering.mdis underused. Put your architectural constraints, your stack opinions, your non-negotiables there. Kiro reads it on every generation. The more context you give it, the fewer surprises you get.- The tasks list is executable. Kiro can generate code per task, in order. Use it. Don't let it generate everything at once — review task-by-task for complex features.
- Spec mode doesn't replace thinking. It amplifies it. If your design is wrong, Kiro builds your wrong design faster.
KiroPad v2.0.0 is running. The spec that built it lives in the repo. The bugs Kiro missed made me a better reviewer of my own assumptions. That's not a complaint — that's what a good tool does.
Have you tried Kiro's spec mode on a real project? Drop your experience in the comments — especially if you hit a gap the spec didn't catch. I want to know if the offline-queue miss is a pattern or a one-off.
#AWSSBCL #BuildOnAWS #Kiro #AWS #ElectronJS #ReactNative #WebSocket #DeveloperTools #SpecDrivenDevelopment #AITooling #OpenSource #ad
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article