i(Dara,me) want to write this to you(agent). Icarus is mine, and we take care of it together. This branch is where Icarus learns to live online.
Icarus is a Valorant strategy and lineup tool: a desktop Flutter app where players, igl's, and coaches draw plans on maps, place agents and abilities, and build a library of their tactical work. The shipped version keeps everything on the user's machine. This branch adds the cloud: accounts, a Convex backend, sync, and shared strategies. The web beta is public and real users keep their libraries on the production deployment. We are still finding the right shape for these features, but the shape now has people living in it.
Quick glossary of relevant parties in this document:
you - the agent reading this document and working on Icarus directly. me/we/us - the humans contributing to Icarus. This is the party talking to you as we build. users - Valorant players and coaches. They are not developers. They will never read an error log, they will only feel whether the app worked.
And the words we use when we work:
simple - how cleanly the logic breaks down. each step follows from the last, no step doing two jobs. obvious - the next reader never asks "why is this here?". measured by the reader. not always simple; sometimes obvious has more parts. the library - a user's saved strategies, folders, and lineups. in local mode it lives only on their machine; in cloud mode a copy lives on the server. both copies are the user's work. round-trip - export then import with nothing lost. local mode - the shipped behavior: signed out, library on disk, exactly what main's users have today. cloud mode - signed in, library synced to Convex. op - one queued change to cloud data. an op lands when the server accepts it.
The domain vocabulary (strategy, page, lineup, .ica file, and friends) lives in CONTEXT.md, use those words exactly. DESIGN.md holds the rules for how we build UI (the values themselves live in lib/const/settings.dart), read it before touching UI. PRODUCT.md holds who this is for and how it must feel.
Here's the philosophy we work by:
After audits, investigations, or substantial testing runs, read answers.md before reporting the outcome.
Before changing view cones, map blockers, or elevation handling, read docs/vision-model.md. It defines which data controls wall placement and height.
Corrupted or dropped library data is unrecoverable. In local mode nothing here changes: a change to the Hive models means source models and generated adapters, plus a migration (lib/migrations/) whenever data written by a past version would not load correctly in this one. A new field whose default reproduces the old behavior needs no migration; a test that reads a record written without the field proves it. In cloud mode the op queue holds work the user believes is saved. Every op either lands or the user is told, on screen, before they walk away. When a write path is uncertain, fail loudly without saving rather than save something wrong.
Size is part of placement. A placed item saves the top-left of its box, and the box comes from its definition in lib/const/, so changing an ability's size, shape or anchor moves every saved one on screen without changing a byte. Keep the box the saved positions were placed in and change only what is drawn inside it (CircleAbility.storedSize, storedAgentAnchor), then prove it with a render of an old placement that lands on the same pixel. When the new drawing cannot fit the old box, it needs a migration, like Paranoia's (#222).
The chip that says synced is the app promising the work is on the server. A conflict resolved in silence makes the app lie. So does an op dropped after retries, and so do offline edits with no badge. A user who catches the app lying once stops trusting it with their library. Every state the user's work can be in has a face on screen, and when the true state is uncertain, show uncertainty.
The clay hardened: production Convex holds real users' libraries, and a cloud library is as sacred as a local one. Never wipe a deployment that users write to. A schema change ships as a migration: widen the schema so old and new rows both validate, backfill in pages that reschedule themselves and record when they finish (backfillAssetReferences in convex/maintenance.ts is the pattern), and only narrow the schema once the backfill is done everywhere. Merging convex/ changes deploys to production, so every server change must keep working with the web build that is live and every desktop build we shipped; gate new server strictness behind something new clients opt into. The dev deployment still holds only our data and can be reshaped freely.
Every .ica file and library backup from every version we ever shipped must round-trip. When you change what a strategy contains, export and import change with it in the same commit.
On main we build the data layer as if a server were already reading it. Here one is. convex/ reads every byte the client writes and has never seen our widgets, and that stays true: payloads carry a payloadVersion, rows carry a revision, JSON is canonical, and nothing under lib/collab/ or convex/ imports UI. Cloud came after the desktop app, so people expect it to behave like the local app they already know. When the data model makes that hard, the gap is worth bringing to me.
People use Icarus while their tactical idea is still hot, the interface must never make them wait or wonder. The network is never in that loop. Edits hit the canvas immediately and the queue catches up behind them. When a feature works but feels wrong, it is not done.
Measure twice, cut once: understand the problem fully before building, because cleverness is what gets written when you haven't. The biggest simplicity win is refusing to solve problems we don't have. Good code is the most simple thing that delivers full functionality, nothing traded away, nothing bolted on. Push back when you see a more obvious way.
These steer us in the right direction. They are not hard-set, but default to following them; if you think one should be ignored, be very loud about it and get approval from us first.
- Never edit generated files (
*.g.dart,convex/_generated/). Edit the source models or schema, then regenerate (dart run build_runner build --delete-conflicting-outputs; Convex regenerates vianpx convex dev). - Convex behavior is defined by the current schema, source, generated types, and tests in this repository. If an API detail is uncertain, check the current official Convex documentation before editing, then prove the change with
npx tsc --noEmitandnpm run test:convex. - Each thread owns one running instance of the app, and never touches another's. Start yours once, keyed by your worktree or branch name:
flutter run -d macos --pid-file /tmp/icarus-<key>.pid --dart-entrypoint-args "--hive-store-dir=<dir>", with<dir>=~/Library/Containers/xyz.icarus-strats/Data/Library/Application Support/icarus-<key>so its library is yours alone, launched from an unsandboxed shell or it draws but takes no clicks. Instances share a window frame, so move yours aside before you click. After every edit, hot reload it withkill -USR1 $(cat /tmp/icarus-<key>.pid); the tool's stdin is not a terminal, so typingrdoes nothing. Keep it running for the thread, and kill it when the thread ends. The signed-in session is shared across instances: sign out from yours and you sign everyone out. - A PR picks its own CI. Put a
ci:line in its description naming the lanes your change can break (ci: test web), orci: skipwhen your local checks already cover it. The lanes are listed above thechangesjob in.github/workflows/ci.yml. Lanes that guard production and the library run anyway when their files change. - Merge your own PRs once checks pass, the review is clean, and the change has been seen and used in a running build. Anything touching the library format, migrations or the sync protocol is a one-way door: staged rollout, and I look first.
- Test accounts live outside the repo, on my Windows PC in
~/.agents/secrets/icarus/test-accounts.md;make-test-user.mjsbeside it makes more. Never copy a password into the repo, a PR or a report. Use two accounts when more than one person is involved. - Do not add generated agent guidance or bundled skills back to the repository.