Here is a rewritten About section that matches the current planner (scored multi-futures, tail-break, hub thinning) and the globe work (Shift scroll-lock, roster markers, layover chains). Same voice and structure as before.
About Skyward
When a storm closes London or floodwater hits Amsterdam, the hard part is not drawing a new line on a map. It is the next twelve hours: the same tail still has to fly, connecting passengers still have to make it, and every “smart” reroute can quietly rewrite someone else’s plan. Skyward is a disaster flight-recovery desk for that moment. An operator sees legal futures, who else gets hit, and approves one card at a time. Nothing is sent to a crew.
What inspired us
The theme Into the Skies could have been a pretty globe. We wanted the feeling of a real ops floor after something breaks: a live clock, a queue that will not wait, and the quiet panic that one approval might move three other flights.
We were also stuck on the butterfly effect. Delay one inbound and the same aircraft’s next sector is late; miss a connection and a whole passenger group has to be rebooked. Hold every spoke at a closed hub and the bank never recovers. That cascade is the product. The globe is how you see it — including the selected tail’s layover chain. The card is how you decide.
The safety line came first. Skyward is decision support, not autopilot. Every plan starts pending. A human still has to say yes.
How we built it
Two processes, one story.
Backend — Node 20, ES modules, no npm dependencies. A seeded generator builds a mid-size network (~80 tails, 5 reserves, ~1,200 legs). A live ops session ticks sim time, injects ordinary gate holds, and replans after every tick or decision.
For each disrupted flight we generate a small set of legal futures — delay steps, cancel-and-rebook, a fuel-safe divert, a forced spare swap, or hold — then rank them by network passenger-minute cost, not just the local delay. Accept is by planId. Competing swap claims lock a tail so two cards cannot take the same spare. If another pending plan’s fingerprint changes, we return changedFlightIds so the UI can show the butterfly.
The queue card is the cheapest legal future; up to two scored siblings sit beside it:
$$ C = p\,d + \sum_{g} n_g\,d_g + \sum_{k} p_k\,d_k + w_{\mathrm{cx}}\,n_{\mathrm{cx}} + f_{\mathrm{swap}} + f_{\mathrm{divert}} + P_{\mathrm{break}} $$
where $$p$$ and $$d$$ are local passengers and arrival delay, each connecting group $$g$$ adds its own delay (or a cancel weight), and each unabsorbed same-tail knock-on leg $$k$$ adds $$p_k \cdot d_k$$. Swaps and diverts carry a small flat preference. Knock-on along a tail stops when turn buffer absorbs the leftover delay:
$$ d_{k+1} = \max\bigl(0,\; d_k - b_k\bigr) $$
If $$d_{k+1} = 0$$ lookahead ends. If three or more later legs still carry delay, the rotation does not recover: we slap a tail-break penalty on “just delay it,” and the scorer prefers a spare swap or a cancel that protects the rest of the day.
At a closed hub we also thin the bank: keep the highest-pax spokes, cancel the rest so the survivors can actually turn.
Frontend — React 19, Vite, a 3D globe. The dashboard is an ops desk: four hero numbers, a generated rundown, inbox/dispatch, and one approval card with swipe-out animation. Scroll the card to read it; scroll anywhere else and the story carries you into the network. Hold Shift to lock that story scroll — the globe can zoom, the desk can scroll, and you are no longer fighting the page.
The flight sheet is the full roster, with a closeable history panel per tail. Select a flight and the globe draws that tail’s layover chain, not a thousand unrelated arcs. Click “(N) flights affected” and you see how each one moved.
If the API is down, the UI falls back to frozen demo scenarios and says so out loud.
What we learned
- Legal is harder than clever. A second future is only useful if it is fuel-safe, runway-legal, and does not invent a swap that is not on the ground.
- Local cheap is network-expensive. The lowest delay on this flight can poison three later sectors. Scoring had to include knock-on pax-minutes, or the “best” card was a lie.
- A broken rotation needs a different verb. After enough unabsorbed delay, “hold the tail” is not recovery. Swap or cancel becomes the honest option, and the card should say the rotation does not recover.
- Scale is a product problem. A thousand arcs freeze a globe.
scope=activecompact markers,scope=rosterspreadsheet rows, and layover chains for the selected tail exist because “send everything” does not. - Operators do not want a dashboard of widgets. They want a queue, a reason, and a next card.
- Scroll has to know which job it is doing. Wheel on the card reads the card. Wheel on the desk tells the story. Shift locks the story so the globe can be a globe.
- Additive APIs keep a hackathon from eating itself. Existing
/apishapes stayed put; futures, layers, andchangedFlightIdshung off the side. - Human-in-the-loop is a feature. The moment you pretend the model can dispatch, you have built the wrong thing.
Challenges we faced
The planner wanted to be a search engine. Full beam search over delay steps, swaps, and passenger itineraries is the right end state. In the time we had, a scored fan-out of legal futures — delay steps, divert, swap, cancel, hold — plus competing-claim locks and hub thinning was honest. We documented the rest instead of faking a smarter brain.
The butterfly is often empty. If you accept a hold that does not rewrite other fingerprints, nothing else “changed.” We still show predicted same-tail knock-on on the card so the operator is not flying blind.
Thinning feels brutal until you watch the alternative. Cancelling a low-pax spoke at a closed hub looks harsh on one card. Leaving every spoke delayed leaves no bank. The copy has to say protect the network, or the operator thinks the model gave up.
The sheet fought the layout. A half-screen history drawer overlapped the table; the title, back link, and search sat on top of each other. We ended on a stacked header and a side panel you can close.
Scroll had three jobs. Wheel over most of the dashboard should take you to the globe. Wheel over the approval card should read the card. Wheel over the globe, once you are looking at a layover chain, should zoom. One global hijack made that feel broken until we split the first two paths and added a Shift lock for the third.
Demo vs live. Judges will not always have the API. Frozen snapshots and a loud demo banner mattered as much as the planner.
Skyward is still a prototype.
Built With
- custom-recovery-api
- node-20-(no-backend-deps)
- optional-opensky
- ourairports
- react-+-vite-+-three.js-globe
Log in or sign up for Devpost to join the conversation.