01 · THREAD iOS & Flutter · 2019 → today
The frame is sixteen milliseconds long
Every argument a mobile team has about concurrency is really an argument about one number. A screen owes a frame every 16.7 ms, and everything — async/await, actors, isolates, cancellation — is about what you are allowed to do inside it. Below is the same list loaded three ways, with the frame graph a profiler would show you.
A screen owes a frame every 16.7 ms. This one is loading twenty-four rows, and the only thing that changes between the three buttons is where the decode happens. Watch the frame graph, not the list.
on budget dropped
One frame that lasts almost three hundred milliseconds. The list does not stutter — it stops, and the operating system starts thinking about whether your app is still alive. Every mobile engineer has shipped this once.
What you just learned → The middle button is the whole chapter. await suspends a function, it does not move it — the continuation resumes on the main actor and the expensive work still lands in a frame. Async code that never leaves the main thread is the most common performance bug in mobile, and it is invisible until somebody profiles it.
Act one
The screen
A reading is worth nothing until somebody sees it, and a phone allows 16.7 milliseconds to show it. Seven years of shipping apps, native and cross-platform — and before them three years a level down, in firmware, boards and machined parts, which the Apple Developer Academy turned into this: the same engineering, moved up the stack. This act is the last few centimetres of that trip — arriving in time, staying legible under conditions nobody designed for, and reaching the right person only.
02 · GLASS Swift & Flutter · 2019 → today
The card that survives the real world
Anyone can build the card in the design. The job is building the one that is still readable when the person holding the phone has set text to the largest accessibility size, is in split view, in a language whose words run longer, reading right to left, on a connection that never delivered the image. A designer hands you one state. You ship six.
One card, two builds. On the left is the card as it comes off a comp: one row, one line each, a height in points. On the right is the same card built to survive. Turn the conditions on and watch which one is still readable — the type size a person actually set, a narrow split view, a translation that runs longer, a right-to-left locale, an image that never arrived, an error with nowhere to go.
// The whole fix is three decisions.
HStack(alignment: .firstTextBaseline, spacing: 12) {
if let image { Thumbnail(image) } // no image, no box
VStack(alignment: .leading, spacing: 2) {
Text(position.symbol)
.font(.headline)
.lineLimit(nil) // let it wrap
if let error { Text(error).foregroundStyle(.red) }
else { Text(position.summary).font(.subheadline) }
}
Spacer(minLength: 12)
Text(position.pnl, format: .currency(code: code))
.monospacedDigit()
}
.padding(.horizontal, 16).padding(.vertical, 12)
.frame(minHeight: 44) // grows, never clips
.dynamicTypeSize(...DynamicTypeSize.accessibility5)
.accessibilityElement(children: .combine)
.accessibilityLabel(position.spokenLabel) // Same three decisions, same order.
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (image != null) Thumbnail(image!),
const SizedBox(width: 12),
Expanded( // min-width: 0, in Flutter
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(p.symbol, style: t.titleMedium, softWrap: true),
error != null
? Text(error!, style: t.bodySmall!.copyWith(color: cs.error))
: Text(p.summary, style: t.bodySmall),
],
),
),
const SizedBox(width: 12),
Text(p.pnl, style: t.titleMedium),
],
)
// Directionality mirrors the row; Semantics gives it one spoken label. What you just learned → The layout drawn to the comp survives one of the seven conditions below — the one it was drawn for. The difference is not taste, it is which question the layout asks: not "is the type large?" but "does this fit?". Ask the second one and a longer translation stops being a bug report.
03 · GATE Tradx · 2022 → today
Signing in is where apps get broken into
Everything else in a mobile app fails visibly. This fails silently: the sign-in works, the user gets in, and the flow is wide open the whole time. I rebuilt this one for Tradx after reading our own implementation properly — it is the fifty lines I would ask about in any senior interview.
Signing somebody in through a browser and handing the result back to the app is the most security-sensitive fifty lines in a mobile codebase. Start from the version every tutorial shows and close it one switch at a time. The order matters more than you would like.
- The app opens the browser ASWebAuthenticationSession, not a web view — the session cookie has to be the real one
- The user signs in and the authorisation server redirects back
- The device routes the redirect to whichever app claims that address
- The app reads the callback off the URL, which is written to logs and history
- The app exchanges it for a session, over TLS, from the app itself
- Somebody presents it again the same value, a second time
- The session lands in the keychain and the user is in
- Scheme hijack any other app on the device can register tradx:// and receive the redirect instead
- Token in the logs an access token on a query string is written to system logs, browser history and the referrer
- Code replay a code that is accepted twice is an access token with extra steps
- Code interception without PKCE the exchange proves nothing about who started the flow, so whoever holds the code can spend it
- Forged callback without an unguessable state value the app accepts a sign-in the user never started
Start with the tutorial version and close it one switch at a time. Watch how far the redirect gets.
What you just learned → Swapping the token for a one-time code is the fix everybody reaches for first, and on its own it opens two holes for the one it closes. A code needs PKCE so only the app that began the flow can spend it, and single use so it cannot be spent twice. Half a security migration is worse than none, because it feels finished.
Act two
What the app talks to
Work backwards from the screen and you find the trip the number took to reach it. It has to arrive while it is still true, be found among hundreds of thousands of its siblings, hold up when everybody asks at once, and finally be worth a decision. Four chapters — and in the last one, all four decisions are mine.
04 · PULSE Tradx · 2022 → today
The client should not have to ask
Most of what an app shows is the answer to a question it asked. A live price is not: by the time the answer arrives the question has changed, and asking again spends a whole request to be told, usually, that nothing happened. This chapter is the difference between an endpoint that answers and a channel that tells you.
One minute of a price feed, replayed. Choose how the client finds out — ask on a timer, or be told — and then watch the two numbers: what the market is doing, and what the reader is actually looking at.
One connection, {bytes} for the whole minute, and all {emitted} ticks arrived. The screen showed a superseded price {behind} of the time — the flight itself, and nothing else.
What you just learned → Polling once a second cost eight times the bytes of holding one socket open, and still missed a quarter of the ticks. Frequency is not freshness — the only way to stop being late is to stop asking.
05 · SERVICE Backend · 2018 → today
Somebody has to remember all of it
A reading nobody kept is a reading that did not happen. Behind every device and every client sits an API and a database, and the difference between a fast product and a slow one is usually a single decision made here. The table below holds two hundred and forty thousand sensor readings — the kind the probe on the other page spent two years producing.
A table of sensor readings, 240,000 rows deep. Pick a query, decide which columns get an index, and run it. The strip below is the table; watch how much of it the database has to touch.
SELECT * FROM readings WHERE reading_id = 128374
index · B-tree
18 pages
table · readings · 240,000 rows
—
What you just learned → Without an index the database reads every row to answer you. With one, it jumps straight there. Same query, same data, same server — a thousand times faster.
06 · SWARM Backend · 2018 → today
One box is never enough
One reader is a query; ten thousand at once is an architecture. Traffic does not arrive politely — it arrives all at once, at 3am, on launch day. Containers and Kubernetes are how you answer that without waking up.
Traffic on this service spikes every few seconds, the way a launch day does. Add replicas and the latency falls; add too many and you are paying for idle machines. Then hand it to the autoscaler and watch where it still loses.
ready 0 starting 0
You are scaling by hand. The spike arrives whether you are ready or not.
What you just learned → You did not make the code faster — you made more of it. Horizontal scaling trades money for latency, and an autoscaler makes that trade for you, automatically.
07 · FLOW Tradx · 2022 → today
And then I built the tool
Tradx is my sandbox — the place I get to make every decision and live with all of them. A desktop trading client written in Flutter, where a strategy is a graph you wire rather than code you write, published on the Mac App Store and the Microsoft Store, about fifteen thousand downloads between them. Two of us build it: me and Luiz Veloso, on pull requests, with reviews neither of us skips.
This is what Tradx does, in miniature. You do not write the loop — you wire the logic, and the engine runs it over the candles. Move the parameters and watch the graph and the trades change together.
Two people, pull requests, and reviews neither of us skips.
Tune it until the number is beautiful. That is the easy part, and it is what every backtest screenshot on the internet is showing you.
What you just learned → A backtest is a hypothesis, not a result. Anyone can tune a curve until it is beautiful on the data they already have; the entire craft is knowing what that number is worth on the data they do not.
Act three
And the models underneath
Everything so far moves a reading that already exists. This one invents the next one — and it is where the two halves of this site turn out to be one half. I trained the model on market data; it runs here on a soil probe I buried years earlier, unchanged, because a time series does not know what it measures.
08 · MIND StockGAN · 2021
Teach the machine to guess what happens next
In 2021 I built a generative adversarial network for time series from scratch — the work where my postgraduate specialization in AI and my Master in Bioengineering stop being lines on a diploma — two networks, one trying to invent the next reading and one trying to catch it lying. I trained it on financial data. It runs here on a soil probe I designed and buried years earlier, unchanged, because a time series does not know what it measures.
In 2021 I wrote a GAN that forecasts the next value of a time series — generator, discriminator and training loop from scratch. I trained it on thirty years of daily bars, because that is where clean history was free, but the network reads log returns and has no idea what the numbers measure. So here it is running on a soil probe I built years before any of this. The part worth showing is this: the discriminator never judges a predicted reading on its own. The reading is glued onto the nine real ones before it, and the network has to say whether the whole window came off the sensor. That is the game. You are the discriminator.
StockGAN · LSTM(128) · window of 10 · trained on 30 years of daily bars, running here on soil-1
One of these two windows ends with a reading the probe actually took. The other ends with one the generator invented. Which is which?
Pick one. Nine real readings, and a tenth that is either the probe or my generator.
generator LSTM(128, stateful) → Dropout(0.3) → Dense(n_features) discriminator LSTM(128, stateful) → Dropout(0.1) → Dense(1, sigmoid) RMSprop on a polynomial decay from 2.5e-4 down to 1e-4 over a thousand steps. Cross-entropy with label smoothing at 0.2, so the discriminator is never allowed to be completely sure of anything. Both the real and the generated target multiplied by noise at σ = 0.25 while training, so it cannot win by noticing which numbers are too clean. Twelve indicators — MACD, EMA, RSI, Bollinger, ROC, volatility — cut down by PCA and XGBoost before the network ever saw them; on a soil probe those same twelve become moving averages and rate of change of moisture, which is exactly the point: the architecture changed domain and the feature pipeline barely blinked. The loss functions on this page are ports of the real ones, constants included; the generator here is a stand-in that reproduces the trade-off without carrying 128 LSTM units into your browser.
What you just learned → Two models, one loss function, and a knob between them. Grade the forecaster on being close and it answers with the average of everything, which is safe and useless. Grade it on being believable and it answers with something that moves like the sensor and knows nothing. Every model I ship is that trade made on purpose, written down, and measured.
Act four
And then it left the screen
And now the measurement is the object. At Eldorado, on macOS and visionOS, a three-dimensional thing stops being something an engineer drew and becomes something a camera measured — the same move I made in 2019, run once more, on the world itself.
09 · FIELD Eldorado · 2022 → today
And now the object is made of light
The last chapter invented the next number in a series. This one stops treating the world as something anybody draws at all. A scene stops being a surface someone modelled and becomes a cloud of oriented gaussians, fitted by gradient descent until renders of them match the photographs — which means nobody draws it, and there is no mesh left to convert. Real reconstructions start from a camera walking around a room. The demo below deliberately does not: it uses a part I designed and printed myself, because the only way to see what the method changes is to watch the same object built both ways.
One object, built twice. On the left of the switch it is the mesh the part was printed from. On the right it is a field of gaussians over that same mesh — scattered here rather than fitted, so the two can sit side by side and the difference is the method and nothing else.
loading the mesh…
997 triangles, each one placed by somebody in SolidWorks in 2017.
What you just learned → A mesh says where the surfaces are, and an engineer drew every one. A splat field says where the light is, and nobody drew any of it — it was fitted until the render stopped disagreeing with the photograph.
10 · RECORD
The receipts
Nine chapters of demonstration. This one is just evidence. Drag through the years and watch what was running at the same time — because the honest headline of this timeline is not any single job, it is how rarely there was only one.
Education
Research
Industry
Ventures
From trainee to senior in four years
I joined the Apple engagement in 2022 as a trainee and I am still on the same product, now as a senior. Nobody promotes on a schedule: each step came from taking something nobody wanted — the release process, the flaky suite, the screen everybody rewrote twice — and making it somebody else’s easy problem. Drag the year and watch the industry lane: the four bands that follow each other are the same product, and the same team, seen from four different amounts of responsibility.
Six companies
Founded or co-founded. Some grew, some did not — which is the point of founding six.
- Tradx Co-founder · fintech
- Agrom.IO Founder · agritech
- Dommuz Co-founder · smart home
- Psiu Co-founder · proptech
- Hubli Co-founder · edtech
- PreditChart Co-founder · fintech
Published and awarded
Six pieces of academic work, three of them peer-reviewed, and none of them about mobile — which is the point. They are where I learned to be wrong in public: to state a method precisely enough that somebody else can attack it, take the review, and change the claim. That habit is worth more in a code review than any of the results are.
- Instrument for Measuring Factors and/or Natural Elements of the Soil 2016 · Control & Automation Engineering, PUC-PR
- A Tool to Select FES Parameters for chronic SCI 2019 · 41st Annual International Conference of the IEEE EMBS · read the paper ↗ · doi:10.1109/EMBC.2019.8857421 · source ↗
- Indoors Wi-Fi Fall Detector Buckle for the Elderly 2019 · Advanced Materials Proceedings, 4(1), 40–45 · read the paper ↗ · doi:10.5185/amp.2019.1450 · source ↗
- Application, in silico, of electrical stimuli in a neuro-muscular model compatible with chronic spinal cord injury 2019 · M.Sc. dissertation in Bioengineering, PUC-PR · read the paper ↗ · source ↗
- Mathematical modeling of the balance maintenance system using system identification techniques 2018 · V Congresso Brasileiro de Eletromiografia e Cinesiologia · X Simpósio de Engenharia Biomédica · read the paper ↗
- Forecasting financial series with Generative Adversarial Networks 2020 · Applied AI specialization, PUC-PR
In the press
- Hubli, built at the Apple Developer Academy — Apple names me among its five creators Apple Newsroom · 2021 · read the article ↗
- Neon Wave, an iOS game covered by MacMagazine MacMagazine · 2020 · read the article ↗
I build the app, and I build what it talks to. The screen, the state on it, the API that fills it, the database under that, and the model that reads it. Most people own one of those. I have shipped all of them, and I still know which one is the hard part.
What I work with
Everything below is credited by a chapter on this page or sitting in a public repository. Nothing here is aspirational.
- iOS · Swift
- Swift · SwiftUI · UIKit · visionOS · macOS · Swift Concurrency · async/await · actors · Combine · MVVM · Coordinators · Clean Architecture · Dependency injection · SwiftData · Core Data · URLSession · Swift Package Manager · XCTest · XCUITest · Instruments · Xcode · App Store Connect · TestFlight · VoiceOver · Dynamic Type · Localization · Push notifications · Deep linking
- Cross-platform
- Flutter · Dart · Riverpod · Bloc · Provider · Kotlin · Jetpack Compose · Android · Platform channels · Offline-first · State management · Widget testing
- Shipping & release
- fastlane · Xcode Cloud · GitHub Actions · CI/CD · App Store review · Phased release · Firebase Crashlytics · Crash-free rate · Analytics · Feature flags · Semantic versioning
- Languages
- Swift · Dart · Kotlin · Java · TypeScript · JavaScript · Python · C · C++ · MATLAB · SQL
- Backend & APIs
- Node.js · NestJS · Express · Java · REST APIs · WebSockets · Microservices · OAuth 2.0 · JWT · API versioning · Distributed systems · System design · Event-driven architecture · API design
- Data
- MongoDB · PostgreSQL · Redis · BullMQ · Database design · Indexing and query planning · Time series · Data pipelines
- Cloud & DevOps
- AWS · Amazon S3 · CloudFront · Route 53 · AWS IoT Core · IAM · EC2 · Docker · Kubernetes · Terraform · Infrastructure as Code · GitHub Actions · CI/CD · OIDC · Observability · Scalability · Linux
- Web
- Astro · HTML · CSS · Accessibility · Design systems · Responsive layout
- AI & 3D
- Machine learning · PyTorch · TensorFlow · Keras · GANs · LSTM · Deep learning · Feature selection · PCA · XGBoost · Gaussian Splatting · 3DGS · CUDA · Photogrammetry · Computer vision
- Embedded & hardware
- Embedded C · ESP32 · PIC18F4550 · ATmega · PWM · ADC · MQTT · IoT · PCB design · EAGLE · Altium · SolidWorks · CAD · 3D printing · Design for manufacturing
- Ways of working
- Git · Code review · Unit testing · Technical leadership · Mentoring · Product engineering · Backtesting · Paper trading
Talk to me
If any of this is the shape of a problem you have, I would like to hear about it. Email or LinkedIn, whichever you prefer — the CV is a two-page PDF and the code is on GitHub.