The problem
Every app has shortcuts. I remember about four.
I switch between Figma, VS Code, Chrome, Claude, Slack and a stream of video calls all day. Each has its own shortcuts, and the ones I need most are the ones I never remember.
Logitech makes a keypad for this, the MX Creative Keypad, with buttons whose labels change per app. It’s a good idea. I wanted to know whether I could build my own version, make it do more than a keyboard can, and learn hardware in the process.
The brief I started with: a small device where some buttons are fixed and some change with the app, plus a Mac app where I choose which buttons show up where and save those layouts.
I had never flashed a microcontroller. That was part of the point: this was my first physical product, and a test of how far an AI coding partner could take someone from software into hardware.
Once it worked, I wanted it to do more than shortcuts. Personal AI agents were everywhere in September (Meta had just launched Muse), but they all lived in a chat window. I wanted to see what an agent feels like with something physical: a screen that shows what it’s doing, and a button I have to hold before it sends anything.
Three keys that never move, nine that follow the app you’re in.The design’s one-line description
Designed first: the Figma layout at the real screen size, 240 × 320.What I actually use every day: my eight apps with their real icons, plus mic, volume and a focus timer. Who it’s for
I built it for myself first.
The layout on my desk right now is eight app launchers. The Figma, VS Code, Final Cut, Chrome and Zoom layouts ship as examples for the people I think it would suit next.
Primary
Me, at my Mac all day
Switching between design, code, AI tools and calls, often with several Claude Code chats running at once. I want one-tap launching, instant mute, a focus timer I don’t have to find in a menu, and a quick way to ask an agent something.
Next
Mac power users
Designers, developers, video editors and people in back-to-back calls who’d like a context-aware pad for $19 instead of $200, and who want buttons that can run scripts, not just keystrokes.
InferredNot yet
Anyone who wants to buy one
There’s no case, no packaging and setup needs a terminal. I want to be sure it’s useful to me before making it easy for anyone else to set up.
Platform
macOS only
The companion app uses macOS system APIs to see which app is in front, control audio and launch apps.
Use cases
How I use it during the day.
Switching appsAll day
One tap opens or brings forward Dia, Claude, ChatGPT, Antigravity, Slack, WhatsApp or Spotify. Each tile shows the app’s real macOS icon.
In FigmaDesigning
The grid redraws as Frame, Component, Export, Group, Mask, Flatten, with the shortcut shown in the corner of each tile, across two swipeable pages.
On a callMeetings
Tap the mic tile to mute. Hold it while muted to talk. Drag the volume tile up or down instead of hunting for a slider.
Deep workFocus blocks
The corner tile is a Pomodoro clock. Tap to start, swipe to skip or reset, swipe up or down to change between 25/5, 45/15 and 50/10. It runs on the keypad itself, even if the Mac app is closed. During a focus block, the agent checks Slack and Gmail every 10 minutes. Anything urgent shows as a blue dot, and the rest waits for a summary at the break.
Asking the agentAny time
I tap the robot tile, ask something like “what emails need a reply today?” and tap again to send. The answer is one short line on the keypad, with the details on the Mac.
ApprovingWhen the agent wants to act
Anything that sends, posts or changes something shows a card first, and I hold Approve until the button fills. Payments and permanent deletes are always refused.
My Claude Code chatsWhile I work
The Chats page lists my Claude Code chats: which need me, which are working and which are done. If one asks a question, I pick the answer on the keypad. A finished one shows a suggested next message that I can send with a tap.
Small automationsWhenever
A tile can type text, run a shell command or AppleScript, open a URL, or run a multi-step macro. The editor has a Test button to try an action before saving it.
The product
Hardware, firmware, a Mac app, an editor and two AI agents.
Four pieces had to work together at first: a touchscreen board, the code that draws on it, a Mac app that knows which app you’re in, and an editor for setting it all up. The agent work added a fifth: AI agents that run in the background on the Mac and use the keypad as their screen.
- Board
- PanelLan ZX2D80CE02S-V10, sold as a “2.8-inch ESP32-S3 touchscreen module”
- Price
- $18.99, the whole bill of materials
- Chip
- ESP32-S3, dual-core, 240 MHz, Wi-Fi and Bluetooth (unused so far)
- Screen
- 2.8" IPS, 240 × 320, 143 pixels per inch
- Touch
- Capacitive, FT6336U controller
- Connection
- USB-C, as a two-way data link to the Mac
- Layout
- 3 × 3 grid of tiles, 3-tile fixed dock, unlimited pages
- Firmware
- C++ on PlatformIO, version 3.11
- Agents
- Claude Code (Sonnet 5) and Gemini through the Antigravity CLI, both running headless on the Mac
- Voice
- Apple’s on-device speech recognition, so audio never leaves the Mac
The editor runs locally in the browser. Every tile has a label, icon, colour, action and shortcut hint, and the preview matches the device pixel for pixel.The focus timer’s full-screen alarm. It stays until you dismiss it. Rendered from the real firmware drawing code; not yet photographed on the device.53 icons, converted automatically from the design into a format the chip can draw. Follows the app in front
The Mac app checks the frontmost app four times a second and swaps in that app’s layout, or a default one.
Mic and volume, as widgets
Tap to mute, hold to talk. Volume is a drag, and the Mac app ignores its own changes for a few seconds so the tile doesn’t jump back.
Nine kinds of action
Keyboard shortcut, type text, open an app or URL, shell command, AppleScript, media key, multi-step macro and more.
Real app icons
The Mac app renders each app’s real icon at 32 × 32 and sends it to the keypad, which keeps up to 16 cached.
Starts at login
Unplug it and plug it back in, and it reconnects within two seconds.
No reflashing to change buttons
Save in the editor and the keypad updates immediately. Firmware only changes when the drawing itself changes.
A robot on the chat tile
The first two slots merged into one wide tile with a goofy robot on it. It blinks, glances at the keys I press, bounces with my voice while listening, and shows the step it’s on while it works.
Two agents, kept apart
Claude works with my Gmail, Drive, Notion and Slack. Gemini answers general questions, searches the web and can edit files in my own git projects. Each has its own tile, conversation and colour, and every card says which one answered.
The editor with the chat tile selected. The preview shows the robot listening, and the switches on the right turn any tile into a chat tile or hand it to Gemini.The robot’s moods: idle, listening, thinking, done, failed and offline. Drawn by the editor’s preview, which mirrors the firmware’s drawing code.How it works
The keypad only draws the screen, and the Mac app decides everything else.
The keypad reports taps back to the Mac. Changing anything means editing a layout on the Mac, never reflashing the keypad. The agents live on the Mac too, and every tool call they make goes through one gate that either runs it, asks the keypad, or refuses it.
1
You switch apps
macOS reports which app is in front.
2
Mac app
Picks that app’s layout plus the fixed dock and sends it down the cable.
3
Keypad
Draws 12 tiles. Runs the focus timer on its own.
4
You tap
The keypad reports which tile. The Mac runs the action.
5
Editor
Change a layout, save, and the keypad updates instantly.
ESP32-S3C++ / PlatformIOLovyanGFXPython (macOS)Plain-JS web editorUSB serial, JSON per lineClaude Code, headlessAntigravity CLIClaude Code hooks
Same keypad, different app: the VS Code layout in the editor.The editor’s Agent panel. Two requests are waiting for a hold on the keypad: a Gmail draft and a test run from a Claude Code session. Below them are the agent’s tasks with each step, and my Claude Code sessions. The editor can deny, but only the keypad can approve.Product decisions
The main decisions, and the one I’d change.
Put the logic in the Mac app
WhyChanging a button shouldn’t mean reflashing firmware. With logic on the Mac, buttons can open apps and run scripts, which a plain keyboard can’t.
Trade-offWithout the Mac app running, the keypad does nothing except the focus timer.
A data link instead of a keyboard
WhyThe design originally said “USB keyboard”. A two-way data link lets the Mac push layouts, icons and volume levels back to the keypad.
Trade-offShortcuts need macOS Accessibility permission, and it can’t act as a standalone keyboard. That’s the one I’d revisit.
The focus timer runs on the keypad
WhyA timer that stops whenever the Mac app restarts is useless, so this is the one feature that lives entirely on the keypad.
Trade-offTwo places now hold state, which the rest of the design avoided.
The alarm never dismisses itself
WhyThe first version blinked three times and started the next phase on its own, so I kept missing it. Now it stays up, shows how long it has been ringing, and waits for Start or Dismiss.
Trade-offOne more tap per session, but I stopped missing it.
Only the keypad can approve
WhyAnything the agent wants to send, post or change waits for a hold on Approve. The web editor can deny but never approve, so nothing running on the Mac can approve on my behalf. An unanswered card counts as denied after 5 minutes.
Trade-offSome tasks stall until I look at the keypad.
No shell, and a stricter rule after private data
WhyI removed the shell from the agent completely, so it works only through my connectors and the web. Once a task has read my email or files, even opening a web page needs my hold, so a malicious email can’t get it to send my data out.
Trade-offIt can’t run scripts for me, and tasks that mix email and the web ask more often.
I built activity tracking, then deleted it
WhyI tried letting it record my app time and shortcuts so it could suggest things. When I asked myself what it would actually learn from app time, the honest answer was not much, so I removed all of it the same night.
Trade-offIts suggestions are less personal.
No new macOS permissions
WhyI wanted a page for my GitHub projects, but it needed Full Disk Access, which I wasn’t willing to give a background app. I replaced it with the Chats page, which only uses what Claude Code already reports.
Trade-offNo view of my repos. The one exception I allowed was Accessibility, so a suggested reply can be typed into a chat.
Text, not voice
WhyI tried having it read answers aloud, and the Mac’s built-in voice sounded bad. Replies are now one short line on the keypad, with the details on the Mac.
Trade-offI have to look at the keypad to get an answer.
Treat the editor as a security risk
WhyA layout can contain shell commands, so the editor only accepts requests from the Mac itself and rejects anything that looks like it came from a web page.
Trade-offA little more code in something that runs on localhost.
Where it stands
It’s on my desk every day, now with the agent features.
Built
- Keypad draws the full design at 240 × 320
- Layouts that follow the frontmost app
- Mic and volume widgets
- Real app icons, cached on the device
- Local web editor with live preview
- Starts at login, reconnects on replug
- Pomodoro focus timer and full-screen alarm
- Claude agent with hold-to-approve cards
- Gemini agent for web questions and my git projects
- Chat robot with its own moods
- Claude Code chats: answer questions, send suggested replies
- Focus filter for Slack and Gmail
- 218 automated tests
Next
- Its own mic, speaker and Wi-Fi on the spare pins, so it works away from the Mac (planned, not built)
- A 3D-printed stand at about 30°
- Working as a plain USB keyboard when the Mac app isn’t running
- Now playing and next meeting tiles
~4 hrs
from the first prompt to a working device
- 16 SepUnboxed to working in one afternoon, plus app icons and start at login
- 22 Sep, 12 amNew project notes; brainstorm of what else a small screen could do
- 22 Sep, 9 pmFocus timer moves onto the device, replacing the Capture tile (firmware 2.3)
- 22 Sep, midnightFull-screen alarm redesign and smoother ring (firmware 2.4)
- 23 SepThe focus timer screens redesigned
- 25–26 SepThe AI agent: Claude Code in the background, approvals on the keypad, voice through on-device speech
- 26 SepMy Claude Code sessions on the keypad, and the focus filter
- 27 Sep, 2 amActivity tracking built, then removed
- 27 SepThe chat robot takes the first two slots; the robot follows onto the agent screen and cards
- 27 SepA second agent: Gemini, through the Antigravity CLI
- 2 OctThe Chats page replaces a GitHub page that needed Full Disk Access (firmware 3.11)
The first afternoon, 16 September
Reconstructed from the build sessions. Times are Chicago time.
2:38 pm
The brief
The board had already arrived. I described the device I wanted: some buttons fixed, some following the app, and a Mac app to manage it all.
2:51 pm
A plan and a scaffold
A six-phase plan, a hardware test program, the Mac app skeleton and an example layout.
5:16 pm
Setting up the tools
My first time installing a microcontroller toolchain. A stuck terminal and a missing command later, the board was plugged in and ready.
5:37 pm
First flash: a black screen
The pin map came from a similar-looking board, and it was wrong. Touch worked, found by probing. The screen stayed dark.
5:42 pm
A photo of the back of the board
The silkscreen revealed the real manufacturer, PanelLan. Their published library had the correct pins and the screen’s start-up sequence.
5:58 pm
It draws, but garbled
The tiles appeared, but overwrote each other. Tapping still triggered the right icons. The cause: a drawing buffer reused while the screen was still reading it.
6:11 pm
Working
Clean tiles, and the keypad switching layouts as I moved between apps. The first working build was still sitting in its shipping box.
6:27 pm
Real app icons
Eight launchers with real macOS icons. Sending all eight at once overflowed the keypad’s memory, so they now go out 80 ms apart.
6:31 pm
Starts at login
Installed as a login item. Unplug, replug, and it comes back on its own.
What I learned
What I learned doing hardware for the first time.
- My hardest bug was not knowing which board I had. A photo of its back solved in five minutes what probing the pins hadn’t.
- With hardware, problems show up on the screen. Pixels, memory buffers and USB timing all went wrong in ways I could actually see.
- I designed it for keyboard shortcuts, and within a week I was mostly using it to launch apps.
- Most of the agent work was deciding what it shouldn’t do: no shell, nothing sent without a hold, no new permissions and no activity tracking.
- AI took me from software into firmware in an afternoon. The judgment calls, like what lives on the device and what the alarm should do, were still mine to make.
About the screenshots: the editor and Agent panel shots are the real editor running on made-up demo data: Priya, the projects and the emails are fictional. Photos still to take: the focus timer, the alarm, the chat robot and the approval cards on the real device.
Next in the lab
Reachout
An outreach CRM I built for my own networking. When the AI changes a field, it quotes the sentence that made it do so, and nothing in it can send a message.